[Patchew-devel] [PATCH 10/12] complete switch to database-based Results

Paolo Bonzini pbonzini at redhat.com
Tue May 22 06:57:38 UTC 2018


All results are stored in the database now and there is no need for the
ResultTuple infrastructure.  Remove all the code invoking the
rest_results_hook and switch ResultsViewSet and ResultSerializer to
be model-based.

Signed-off-by: Paolo Bonzini <pbonzini at redhat.com>
---
 api/models.py   | 46 ---------------------------------
 api/rest.py     | 68 +++++++++++++++++++------------------------------
 mods/git.py     |  9 +++----
 mods/testing.py |  9 +++----
 www/views.py    | 17 +++++--------
 5 files changed, 38 insertions(+), 111 deletions(-)

diff --git a/api/models.py b/api/models.py
index ee54d04..f7065ca 100644
--- a/api/models.py
+++ b/api/models.py
@@ -16,7 +16,6 @@ import re
 
 from django.core import validators
 from django.db import models
-from django.db.models import Q
 from django.contrib.auth.models import User
 from django.urls import reverse
 import jsonfield
@@ -99,14 +98,6 @@ class Result(models.Model):
             log_url = request.build_absolute_uri(log_url)
         return log_url
 
-    @staticmethod
-    def get_result_tuples(obj, module, results):
-        name_filter = Q(name=module) | Q(name__startswith=module + '.')
-        renderer = mod.get_module(module)
-        for r in obj.results.filter(name_filter):
-            results.append(ResultTuple(name=r.name, obj=obj, status=r.status,
-                                       log=r.log, data=r.data, renderer=renderer))
-
 class Project(models.Model):
     name = models.CharField(max_length=1024, db_index=True, unique=True,
                             help_text="""The name of the project""")
@@ -678,40 +669,3 @@ class Module(models.Model):
 
     def __str__(self):
         return self.name
-
-class ResultTuple(namedtuple("ResultTuple", "name status log obj data renderer")):
-    __slots__ = ()
-
-    def __new__(cls, name, status, obj, log=None, data=None, renderer=None):
-        if status not in Result.VALID_STATUSES:
-            raise ValueError("invalid value '%s' for status field" % status)
-        return super(cls, ResultTuple).__new__(cls, status=status, log=log,
-                                          obj=obj, data=data, name=name, renderer=renderer)
-
-    def is_success(self):
-        return self.status == Result.SUCCESS
-
-    def is_failure(self):
-        return self.status == Result.FAILURE
-
-    def is_completed(self):
-        return self.is_success() or self.is_failure()
-
-    def is_pending(self):
-        return self.status == Result.PENDING
-
-    def is_running(self):
-        return self.status == Result.RUNNING
-
-    def render(self):
-        if self.renderer is None:
-            return None
-        return self.renderer.render_result(self)
-
-    def get_log_url(self, request=None):
-        if not self.is_completed() or self.renderer is None:
-            return None
-        log_url = self.renderer.get_result_log_url(self.obj, self.name)
-        if log_url is not None and request is not None:
-            log_url = request.build_absolute_uri(log_url)
-        return log_url
diff --git a/api/rest.py b/api/rest.py
index f36cf28..0a85f10 100644
--- a/api/rest.py
+++ b/api/rest.py
@@ -14,7 +14,7 @@ from django.http import Http404
 from django.template import loader
 
 from mod import dispatch_module_hook
-from .models import Project, Message
+from .models import Project, Message, Result
 from .search import SearchEngine
 from rest_framework import (permissions, serializers, viewsets, filters,
     mixins, generics, renderers, status)
@@ -416,7 +416,7 @@ class MessagesViewSet(BaseMessageViewSet):
 
 class HyperlinkedResultField(HyperlinkedIdentityField):
     def get_url(self, result, view_name, request, format):
-        obj = result.obj
+        obj = self.context['parent_obj']
         kwargs = {'name': result.name}
         if isinstance(obj, Message):
             kwargs['projects_pk'] = obj.project_id
@@ -425,65 +425,49 @@ class HyperlinkedResultField(HyperlinkedIdentityField):
             kwargs['projects_pk'] = obj.id
         return self.reverse(view_name, kwargs=kwargs, request=request, format=format)
 
-class ResultSerializer(serializers.Serializer):
+class ResultSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Result
+        fields = ('resource_uri', 'name', 'status', 'last_update', 'data', 'log_url')
+
     resource_uri = HyperlinkedResultField(view_name='results-detail')
-    name = CharField()
-    status = CharField() # one of 'failure', 'success', 'pending', 'running'
     log_url = SerializerMethodField(required=False)
     data = JSONField(required=False)
 
     def get_log_url(self, obj):
+        parent_obj = self.context['parent_obj']
         request = self.context['request']
-        return obj.get_log_url(request)
+        return obj.get_log_url(parent_obj, request)
 
 class ResultSerializerFull(ResultSerializer):
+    class Meta:
+        model = Result
+        fields = ResultSerializer.Meta.fields + ('log',)
+
+    # The database field is log_xz, so this is needed here
     log = CharField(required=False)
 
-class ResultsViewSet(viewsets.ViewSet, generics.GenericAPIView):
+class ResultsViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin,
+                         viewsets.GenericViewSet):
     lookup_field = 'name'
     lookup_value_regex = '[^/]+'
-    permission_classes = (PatchewPermission,)
+
+    def get_queryset(self):
+        return self.get_parent_object().results.all().order_by('name')
+
+    def get_serializer_context(self):
+        return {'parent_obj': self.get_parent_object(), 'request': self.request}
 
     def get_serializer_class(self, *args, **kwargs):
         if self.lookup_field in self.kwargs:
             return ResultSerializerFull
         return ResultSerializer
 
-    def get_results(self, detailed):
-        queryset = self.get_queryset()
-        try:
-            obj = queryset[0]
-        except IndexError:
-            raise Http404
-        results = []
-        dispatch_module_hook("rest_results_hook", obj=obj, results=results,
-                             detailed=detailed)
-        return {x.name: x for x in results}
-
-    def list(self, request, *args, **kwargs):
-        results = self.get_results(detailed=False).values()
-        serializer = self.get_serializer(results, many=True)
-        # Fake paginator response for forwards-compatibility, in case
-        # this ViewSet becomes model-based
-        return Response(OrderedDict([
-            ('count', len(results)),
-            ('results', serializer.data)
-        ]))
-
-    def retrieve(self, request, name, *args, **kwargs):
-        results = self.get_results(detailed=True)
-        try:
-            result = results[name]
-        except KeyError:
-            raise Http404
-        serializer = self.get_serializer(result)
-        return Response(serializer.data)
-
 class ProjectResultsViewSet(ResultsViewSet):
-    def get_queryset(self):
-        return Project.objects.filter(id=self.kwargs['projects_pk'])
+    def get_parent_object(self):
+        return Project.objects.filter(id=self.kwargs['projects_pk']).first()
 
 class SeriesResultsViewSet(ResultsViewSet):
-    def get_queryset(self):
+    def get_parent_object(self):
         return Message.objects.filter(project=self.kwargs['projects_pk'],
-                                      message_id=self.kwargs['series_message_id'])
+                                      message_id=self.kwargs['series_message_id']).first()
diff --git a/mods/git.py b/mods/git.py
index d77ab0e..67ef723 100644
--- a/mods/git.py
+++ b/mods/git.py
@@ -18,7 +18,7 @@ from django.core.exceptions import PermissionDenied
 from django.utils.html import format_html
 from mod import PatchewModule
 from event import declare_event, register_handler, emit_event
-from api.models import Message, MessageProperty, Project, Result, ResultTuple
+from api.models import Message, MessageProperty, Project, Result
 from api.rest import PluginMethodField
 from api.views import APILoginRequiredView, prepare_series
 from patchew.logviewer import LogView
@@ -141,9 +141,6 @@ class GitModule(PatchewModule):
     def rest_series_fields_hook(self, request, fields, detailed):
         fields['based_on'] = PluginMethodField(obj=self, required=False)
 
-    def rest_results_hook(self, obj, results, detailed=False):
-        Result.get_result_tuples(obj, "git", results)
-
     def prepare_message_hook(self, request, message, detailed):
         if not message.is_series_head:
             return
@@ -182,11 +179,11 @@ class GitModule(PatchewModule):
                                           "class": "warning",
                                          })
 
-    def render_result(self, result):
+    def render_result(self, result, obj):
         if not result.is_completed():
             return None
 
-        log_url = result.get_log_url()
+        log_url = result.get_log_url(obj)
         html_log_url = log_url + "?html=1"
         colorbox_a = format_html('<a class="cbox-log" data-link="{}" href="{}">apply log</a>',
                                  html_log_url, log_url)
diff --git a/mods/testing.py b/mods/testing.py
index ed40a92..b09989b 100644
--- a/mods/testing.py
+++ b/mods/testing.py
@@ -18,7 +18,7 @@ from mod import PatchewModule
 import time
 import math
 from api.views import APILoginRequiredView
-from api.models import Message, MessageProperty, Project, Result, ResultTuple
+from api.models import Message, MessageProperty, Project, Result
 from api.search import SearchEngine
 from event import emit_event, declare_event, register_handler
 from patchew.logviewer import LogView
@@ -275,9 +275,6 @@ class TestingModule(PatchewModule):
                         })
         return ret
 
-    def rest_results_hook(self, obj, results, detailed=False):
-        Result.get_result_tuples(obj, "testing", results)
-
     def prepare_message_hook(self, request, message, detailed):
         if not message.is_series_head:
             return
@@ -308,12 +305,12 @@ class TestingModule(PatchewModule):
         tn = name[len("testing."):]
         return self.reverse_testing_log(obj, tn, html=False)
 
-    def render_result(self, result):
+    def render_result(self, result, obj):
         if not result.is_completed():
             return None
         pn = result.name
         tn = pn[len("testing."):]
-        log_url = result.get_log_url()
+        log_url = result.get_log_url(obj)
         html_log_url = log_url + '&html=1'
         passed_str = "failed" if result.is_failure() else "passed"
         return format_html('Test <b>{}</b> <a class="cbox-log" data-link="{}" href="{}">{}</a>',
diff --git a/www/views.py b/www/views.py
index c2010c5..5685f23 100644
--- a/www/views.py
+++ b/www/views.py
@@ -92,19 +92,14 @@ def prepare_series(request, s, skip_patches=False):
     return r
 
 def prepare_results(request, obj):
-    results = []
-    dispatch_module_hook("rest_results_hook", obj=obj,
-                         results=results, detailed=False)
-
-    results_dicts = []
-    for result in results:
-        html = result.render()
+    rendered_results = []
+    for result in obj.results.all():
+        html = result.render(obj)
         if html is None:
             continue
-        d = result._asdict()
-        d['html'] = html
-        results_dicts.append(d)
-    return results_dicts
+        result.html = html
+        rendered_results.append(result)
+    return rendered_results
 
 def prepare_series_list(request, sl):
     return [prepare_message(request, s.project, s, False) for s in sl]
-- 
2.17.0





More information about the Patchew-devel mailing list