[bitbake-devel] [PATCH 02/19] convert all project-based view to JSON APIs

Alex DAMIAN alexandru.damian at intel.com
Wed Jun 10 14:38:50 UTC 2015


From: Alexandru DAMIAN <alexandru.damian at intel.com>

This patch converts all-project views that are REST-style URLs
to support JSON encoding if the "format=json" parameter is supplied.

The formatting code is enhanced to prevent following Django foreign
keys, to convert enum-values from int to string, and support for
timedelta.

Signed-off-by: Alexandru DAMIAN <alexandru.damian at intel.com>
---
 lib/toaster/toastergui/views.py | 164 ++++++++++++++++++++++++----------------
 1 file changed, 99 insertions(+), 65 deletions(-)

diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index d4a9b4c..f2626f8 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -87,6 +87,55 @@ def _project_recent_build_list(prj):
             list(prj.buildrequest_set.filter(state__in=[BuildRequest.REQ_COMPLETED, BuildRequest.REQ_FAILED]).order_by("-pk")[:3]))
 
 
+def _template_renderer(template):
+    def func_wrapper(view):
+        def returned_wrapper(request, *args, **kwargs):
+            try:
+                context = view(request, *args, **kwargs)
+            except RedirectException as e:
+                return e.get_redirect_response()
+
+            if request.GET.get('format', None) == 'json':
+                # objects is a special keyword - it's a Page, but we need the actual objects here
+                # in XHR, the objects come in the "list" property
+                if "objects" in context:
+                    context["list"] = context["objects"].object_list
+                    del context["objects"]
+
+                # we're about to return; to keep up with the XHR API, we set the error to OK
+                context["error"] = "ok"
+                def _objtojson(obj):
+                    from django.db.models.query import QuerySet
+                    from django.db.models import Model, IntegerField
+                    if isinstance(obj, datetime):
+                        return obj.isoformat()
+                    elif isinstance(obj, timedelta):
+                        return obj.total_seconds()
+                    elif isinstance(obj, QuerySet) or isinstance(obj, set):
+                        return list(obj)
+                    elif hasattr( obj, '__dict__'):
+                        d = obj.__dict__
+                        nd = dict(d)
+                        for di in d:
+                            if di.startswith("_"):
+                                del nd[di]
+                            elif isinstance(d[di], Model):
+                                nd[di] = d[di].pk
+                            elif isinstance(d[di], int) and hasattr(obj, "get_%s_display" % di):
+                                nd[di] = getattr(obj, "get_%s_display" % di)()
+                        return nd
+                    else:
+                        raise TypeError("Unserializable object %s of type %s" % ( obj, type(obj)))
+
+                return HttpResponse(jsonfilter(context, default=_objtojson ),
+                            content_type = "application/json; charset=utf-8")
+            else:
+                return render(request, template, context)
+        return returned_wrapper
+    return func_wrapper
+
+
+
 def _build_page_range(paginator, index = 1):
     try:
         page = paginator.page(index)
@@ -129,7 +178,7 @@ def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs):
         if not i in params:
             params[i] = urllib.unquote(str(mandatory_parameters[i]))
 
-    return redirect(url + "?%s" % urllib.urlencode(params), permanent = False, *args, **kwargs)
+    return redirect(url + "?%s" % urllib.urlencode(params), permanent = False, **kwargs)
 
 class RedirectException(Exception):
     def __init__(self, view, g, mandatory_parameters, *args, **kwargs):
@@ -141,7 +190,7 @@ class RedirectException(Exception):
         self.okwargs = kwargs
 
     def get_redirect_response(self):
-        return _redirect_parameters(self.view, self.g, self.mandatory_parameters, self.oargs, self.okwargs)
+        return _redirect_parameters(self.view, self.g, self.mandatory_parameters, self.oargs, **self.okwargs)
 
 FIELD_SEPARATOR = ":"
 AND_VALUE_SEPARATOR = "!"
@@ -1839,8 +1888,8 @@ if toastermain.settings.MANAGED:
 
 
     # shows the "all builds" page for managed mode; it displays build requests (at least started!) instead of actual builds
+    @_template_renderer("managed_builds.html")
     def builds(request):
-        template = 'managed_builds.html'
         # define here what parameters the view needs in the GET portion in order to
         # be able to display something.  'count' and 'page' are mandatory for all views
         # that use paginators.
@@ -1850,12 +1899,10 @@ if toastermain.settings.MANAGED:
         try:
             context, pagesize, orderby = _build_list_helper(request, buildrequests, True)
         except InvalidRequestException as e:
-            return _redirect_parameters( builds, request.GET, e.response)
+            raise RedirectException( builds, request.GET, e.response)
 
-        response = render(request, template, context)
         _set_parameters_values(pagesize, orderby, request)
-        return response
-
+        return context
 
 
     # helper function, to be used on "all builds" and "project builds" pages
@@ -2151,8 +2198,8 @@ if toastermain.settings.MANAGED:
 
 
     # Shows the edit project page
+    @_template_renderer('project.html')
     def project(request, pid):
-        template = "project.html"
         try:
             prj = Project.objects.get(id = pid)
         except Project.DoesNotExist:
@@ -2207,10 +2254,7 @@ if toastermain.settings.MANAGED:
         except ProjectVariable.DoesNotExist:
             context["distro"] = "-- not set yet"
 
-        response = render(request, template, context)
-        response['Cache-Control'] = "no-cache, must-revalidate, no-store"
-        response['Pragma'] = "no-cache"
-        return response
+        return context
 
 
     def xhr_projectbuild(request, pid):
@@ -2680,8 +2724,8 @@ if toastermain.settings.MANAGED:
 
         return(vars_managed,sorted(vars_fstypes),vars_blacklist)
 
+    @_template_renderer("projectconf.html")
     def projectconf(request, pid):
-        template = "projectconf.html"
 
         try:
             prj = Project.objects.get(id = pid)
@@ -2730,21 +2774,20 @@ if toastermain.settings.MANAGED:
         except ProjectVariable.DoesNotExist:
             pass
 
-        return render(request, template, context)
+        return context
 
+    @_template_renderer('projectbuilds.html')
     def projectbuilds(request, pid):
-        template = 'projectbuilds.html'
         buildrequests = BuildRequest.objects.filter(project_id = pid).exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED)
 
         try:
             context, pagesize, orderby = _build_list_helper(request, buildrequests, False)
         except InvalidRequestException as e:
-            return _redirect_parameters(projectbuilds, request.GET, e.response, pid = pid)
+            raise RedirectException('projectbuilds', request.GET, e.response, pid = pid)
 
-        response = render(request, template, context)
         _set_parameters_values(pagesize, orderby, request)
 
-        return response
+        return context
 
 
     def _file_name_for_artifact(b, artifact_type, artifact_id):
@@ -2861,32 +2904,8 @@ if toastermain.settings.MANAGED:
         return build_mru
 
 
-    def template_renderer(template):
-        def func_wrapper(view):
-            def returned_wrapper(request, *args, **kwargs):
-                try:
-                    context = view(request, *args, **kwargs)
-                except RedirectException as e:
-                    return e.get_redirect_response()
-
-                if request.GET.get('format', None) == 'json':
-                    # objects is a special keyword - it's a Page, but we need the actual objects here
-                    # in XHR, the objects come in the "list" property
-                    if "objects" in context:
-                        context["list"] = context["objects"].object_list
-                        del context["objects"]
-
-                    # we're about to return; to keep up with the XHR API, we set the error to OK
-                    context["error"] = "ok"
-
-                    return HttpResponse(jsonfilter(context, default=lambda obj: obj.isoformat() if isinstance(obj, datetime) else obj.__dict__ ),
-                                content_type = "application/json; charset=utf-8")
-                else:
-                    return render(request, template, context)
-            return returned_wrapper
-        return func_wrapper
 
-    @template_renderer("projects.html")
+    @_template_renderer("projects.html")
     def projects(request):
         (pagesize, orderby) = _get_parameters_values(request, 10, 'updated:-')
         mandatory_parameters = { 'count': pagesize,  'page' : 1, 'orderby' : orderby }
@@ -2995,18 +3014,19 @@ if toastermain.settings.MANAGED:
         _set_parameters_values(pagesize, orderby, request)
         return context
 
+    @_template_renderer("buildrequestdetails.html")
     def buildrequestdetails(request, pid, brid):
-        template = "buildrequestdetails.html"
         context = {
             'buildrequest' : BuildRequest.objects.get(pk = brid, project_id = pid)
         }
-        return render(request, template, context)
+        return context
 
 
 else:
     # shows the "all builds" page for interactive mode; this is the old code, simply moved
+
+    @_template_renderer('build.html')
     def builds(request):
-        template = 'build.html'
         # define here what parameters the view needs in the GET portion in order to
         # be able to display something.  'count' and 'page' are mandatory for all views
         # that use paginators.
@@ -3014,7 +3034,7 @@ else:
         mandatory_parameters = { 'count': pagesize,  'page' : 1, 'orderby' : orderby }
         retval = _verify_parameters( request.GET, mandatory_parameters )
         if retval:
-            return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters)
+            raise RedirectException( 'all-builds', request.GET, mandatory_parameters)
 
         # boilerplate code that takes a request for an object type and returns a queryset
         # for that object type. copypasta for all needed table searches
@@ -3194,55 +3214,69 @@ else:
 
         # merge daterange values
         context.update(context_date)
-
-        response = render(request, template, context)
         _set_parameters_values(pagesize, orderby, request)
-        return response
+
+        return context
 
 
 
 
+    @_template_renderer('landing_not_managed.html')
     def newproject(request):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def project(request, pid):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def xhr_projectbuild(request, pid):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def xhr_projectinfo(request):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def xhr_projectedit(request, pid):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def xhr_datatypeahead(request):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def xhr_configvaredit(request, pid):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def importlayer(request):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def projectconf(request, pid):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def projectbuilds(request, pid):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def build_artifact(request, build_id, artifact_type, artifact_id):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def projects(request):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def xhr_importlayer(request):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def xhr_updatelayer(request):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def buildrequestdetails(request, pid, brid):
-        return render(request, 'landing_not_managed.html')
+        return {}
-- 
1.9.1




More information about the bitbake-devel mailing list