diff options
| author | Alexandru DAMIAN <alexandru.damian@intel.com> | 2015-06-02 12:23:12 +0100 |
|---|---|---|
| committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2015-06-12 00:01:47 +0100 |
| commit | 751e9182ac7f37506c3d85cade00ef6f3986eb7a (patch) | |
| tree | f668ca76c534a09b5e3fc43b3bb7b67eb0c7416b | |
| parent | afe06e313eac61f598f65e622ceb5951a9fabc9a (diff) | |
| download | poky-751e9182ac7f37506c3d85cade00ef6f3986eb7a.tar.gz | |
bitbake: convert all project-based view to JSON APIs
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.
(Bitbake rev: 7a6eb36b82c5f2e342777e479d9c401ded42453d)
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
| -rwxr-xr-x | bitbake/lib/toaster/toastergui/views.py | 164 |
1 files changed, 99 insertions, 65 deletions
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py index d4a9b4ca0d..f2626f8521 100755 --- a/bitbake/lib/toaster/toastergui/views.py +++ b/bitbake/lib/toaster/toastergui/views.py | |||
| @@ -87,6 +87,55 @@ def _project_recent_build_list(prj): | |||
| 87 | list(prj.buildrequest_set.filter(state__in=[BuildRequest.REQ_COMPLETED, BuildRequest.REQ_FAILED]).order_by("-pk")[:3])) | 87 | list(prj.buildrequest_set.filter(state__in=[BuildRequest.REQ_COMPLETED, BuildRequest.REQ_FAILED]).order_by("-pk")[:3])) |
| 88 | 88 | ||
| 89 | 89 | ||
| 90 | def _template_renderer(template): | ||
| 91 | def func_wrapper(view): | ||
| 92 | def returned_wrapper(request, *args, **kwargs): | ||
| 93 | try: | ||
| 94 | context = view(request, *args, **kwargs) | ||
| 95 | except RedirectException as e: | ||
| 96 | return e.get_redirect_response() | ||
| 97 | |||
| 98 | if request.GET.get('format', None) == 'json': | ||
| 99 | # objects is a special keyword - it's a Page, but we need the actual objects here | ||
| 100 | # in XHR, the objects come in the "list" property | ||
| 101 | if "objects" in context: | ||
| 102 | context["list"] = context["objects"].object_list | ||
| 103 | del context["objects"] | ||
| 104 | |||
| 105 | # we're about to return; to keep up with the XHR API, we set the error to OK | ||
| 106 | context["error"] = "ok" | ||
| 107 | def _objtojson(obj): | ||
| 108 | from django.db.models.query import QuerySet | ||
| 109 | from django.db.models import Model, IntegerField | ||
| 110 | if isinstance(obj, datetime): | ||
| 111 | return obj.isoformat() | ||
| 112 | elif isinstance(obj, timedelta): | ||
| 113 | return obj.total_seconds() | ||
| 114 | elif isinstance(obj, QuerySet) or isinstance(obj, set): | ||
| 115 | return list(obj) | ||
| 116 | elif hasattr( obj, '__dict__'): | ||
| 117 | d = obj.__dict__ | ||
| 118 | nd = dict(d) | ||
| 119 | for di in d: | ||
| 120 | if di.startswith("_"): | ||
| 121 | del nd[di] | ||
| 122 | elif isinstance(d[di], Model): | ||
| 123 | nd[di] = d[di].pk | ||
| 124 | elif isinstance(d[di], int) and hasattr(obj, "get_%s_display" % di): | ||
| 125 | nd[di] = getattr(obj, "get_%s_display" % di)() | ||
| 126 | return nd | ||
| 127 | else: | ||
| 128 | raise TypeError("Unserializable object %s of type %s" % ( obj, type(obj))) | ||
| 129 | |||
| 130 | return HttpResponse(jsonfilter(context, default=_objtojson ), | ||
| 131 | content_type = "application/json; charset=utf-8") | ||
| 132 | else: | ||
| 133 | return render(request, template, context) | ||
| 134 | return returned_wrapper | ||
| 135 | return func_wrapper | ||
| 136 | |||
| 137 | |||
| 138 | |||
| 90 | def _build_page_range(paginator, index = 1): | 139 | def _build_page_range(paginator, index = 1): |
| 91 | try: | 140 | try: |
| 92 | page = paginator.page(index) | 141 | page = paginator.page(index) |
| @@ -129,7 +178,7 @@ def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs): | |||
| 129 | if not i in params: | 178 | if not i in params: |
| 130 | params[i] = urllib.unquote(str(mandatory_parameters[i])) | 179 | params[i] = urllib.unquote(str(mandatory_parameters[i])) |
| 131 | 180 | ||
| 132 | return redirect(url + "?%s" % urllib.urlencode(params), permanent = False, *args, **kwargs) | 181 | return redirect(url + "?%s" % urllib.urlencode(params), permanent = False, **kwargs) |
| 133 | 182 | ||
| 134 | class RedirectException(Exception): | 183 | class RedirectException(Exception): |
| 135 | def __init__(self, view, g, mandatory_parameters, *args, **kwargs): | 184 | def __init__(self, view, g, mandatory_parameters, *args, **kwargs): |
| @@ -141,7 +190,7 @@ class RedirectException(Exception): | |||
| 141 | self.okwargs = kwargs | 190 | self.okwargs = kwargs |
| 142 | 191 | ||
| 143 | def get_redirect_response(self): | 192 | def get_redirect_response(self): |
| 144 | return _redirect_parameters(self.view, self.g, self.mandatory_parameters, self.oargs, self.okwargs) | 193 | return _redirect_parameters(self.view, self.g, self.mandatory_parameters, self.oargs, **self.okwargs) |
| 145 | 194 | ||
| 146 | FIELD_SEPARATOR = ":" | 195 | FIELD_SEPARATOR = ":" |
| 147 | AND_VALUE_SEPARATOR = "!" | 196 | AND_VALUE_SEPARATOR = "!" |
| @@ -1839,8 +1888,8 @@ if toastermain.settings.MANAGED: | |||
| 1839 | 1888 | ||
| 1840 | 1889 | ||
| 1841 | # shows the "all builds" page for managed mode; it displays build requests (at least started!) instead of actual builds | 1890 | # shows the "all builds" page for managed mode; it displays build requests (at least started!) instead of actual builds |
| 1891 | @_template_renderer("managed_builds.html") | ||
| 1842 | def builds(request): | 1892 | def builds(request): |
| 1843 | template = 'managed_builds.html' | ||
| 1844 | # define here what parameters the view needs in the GET portion in order to | 1893 | # define here what parameters the view needs in the GET portion in order to |
| 1845 | # be able to display something. 'count' and 'page' are mandatory for all views | 1894 | # be able to display something. 'count' and 'page' are mandatory for all views |
| 1846 | # that use paginators. | 1895 | # that use paginators. |
| @@ -1850,12 +1899,10 @@ if toastermain.settings.MANAGED: | |||
| 1850 | try: | 1899 | try: |
| 1851 | context, pagesize, orderby = _build_list_helper(request, buildrequests, True) | 1900 | context, pagesize, orderby = _build_list_helper(request, buildrequests, True) |
| 1852 | except InvalidRequestException as e: | 1901 | except InvalidRequestException as e: |
| 1853 | return _redirect_parameters( builds, request.GET, e.response) | 1902 | raise RedirectException( builds, request.GET, e.response) |
| 1854 | 1903 | ||
| 1855 | response = render(request, template, context) | ||
| 1856 | _set_parameters_values(pagesize, orderby, request) | 1904 | _set_parameters_values(pagesize, orderby, request) |
| 1857 | return response | 1905 | return context |
| 1858 | |||
| 1859 | 1906 | ||
| 1860 | 1907 | ||
| 1861 | # helper function, to be used on "all builds" and "project builds" pages | 1908 | # helper function, to be used on "all builds" and "project builds" pages |
| @@ -2151,8 +2198,8 @@ if toastermain.settings.MANAGED: | |||
| 2151 | 2198 | ||
| 2152 | 2199 | ||
| 2153 | # Shows the edit project page | 2200 | # Shows the edit project page |
| 2201 | @_template_renderer('project.html') | ||
| 2154 | def project(request, pid): | 2202 | def project(request, pid): |
| 2155 | template = "project.html" | ||
| 2156 | try: | 2203 | try: |
| 2157 | prj = Project.objects.get(id = pid) | 2204 | prj = Project.objects.get(id = pid) |
| 2158 | except Project.DoesNotExist: | 2205 | except Project.DoesNotExist: |
| @@ -2207,10 +2254,7 @@ if toastermain.settings.MANAGED: | |||
| 2207 | except ProjectVariable.DoesNotExist: | 2254 | except ProjectVariable.DoesNotExist: |
| 2208 | context["distro"] = "-- not set yet" | 2255 | context["distro"] = "-- not set yet" |
| 2209 | 2256 | ||
| 2210 | response = render(request, template, context) | 2257 | return context |
| 2211 | response['Cache-Control'] = "no-cache, must-revalidate, no-store" | ||
| 2212 | response['Pragma'] = "no-cache" | ||
| 2213 | return response | ||
| 2214 | 2258 | ||
| 2215 | 2259 | ||
| 2216 | def xhr_projectbuild(request, pid): | 2260 | def xhr_projectbuild(request, pid): |
| @@ -2680,8 +2724,8 @@ if toastermain.settings.MANAGED: | |||
| 2680 | 2724 | ||
| 2681 | return(vars_managed,sorted(vars_fstypes),vars_blacklist) | 2725 | return(vars_managed,sorted(vars_fstypes),vars_blacklist) |
| 2682 | 2726 | ||
| 2727 | @_template_renderer("projectconf.html") | ||
| 2683 | def projectconf(request, pid): | 2728 | def projectconf(request, pid): |
| 2684 | template = "projectconf.html" | ||
| 2685 | 2729 | ||
| 2686 | try: | 2730 | try: |
| 2687 | prj = Project.objects.get(id = pid) | 2731 | prj = Project.objects.get(id = pid) |
| @@ -2730,21 +2774,20 @@ if toastermain.settings.MANAGED: | |||
| 2730 | except ProjectVariable.DoesNotExist: | 2774 | except ProjectVariable.DoesNotExist: |
| 2731 | pass | 2775 | pass |
| 2732 | 2776 | ||
| 2733 | return render(request, template, context) | 2777 | return context |
| 2734 | 2778 | ||
| 2779 | @_template_renderer('projectbuilds.html') | ||
| 2735 | def projectbuilds(request, pid): | 2780 | def projectbuilds(request, pid): |
| 2736 | template = 'projectbuilds.html' | ||
| 2737 | buildrequests = BuildRequest.objects.filter(project_id = pid).exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED) | 2781 | buildrequests = BuildRequest.objects.filter(project_id = pid).exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED) |
| 2738 | 2782 | ||
| 2739 | try: | 2783 | try: |
| 2740 | context, pagesize, orderby = _build_list_helper(request, buildrequests, False) | 2784 | context, pagesize, orderby = _build_list_helper(request, buildrequests, False) |
| 2741 | except InvalidRequestException as e: | 2785 | except InvalidRequestException as e: |
| 2742 | return _redirect_parameters(projectbuilds, request.GET, e.response, pid = pid) | 2786 | raise RedirectException('projectbuilds', request.GET, e.response, pid = pid) |
| 2743 | 2787 | ||
| 2744 | response = render(request, template, context) | ||
| 2745 | _set_parameters_values(pagesize, orderby, request) | 2788 | _set_parameters_values(pagesize, orderby, request) |
| 2746 | 2789 | ||
| 2747 | return response | 2790 | return context |
| 2748 | 2791 | ||
| 2749 | 2792 | ||
| 2750 | def _file_name_for_artifact(b, artifact_type, artifact_id): | 2793 | def _file_name_for_artifact(b, artifact_type, artifact_id): |
| @@ -2861,32 +2904,8 @@ if toastermain.settings.MANAGED: | |||
| 2861 | return build_mru | 2904 | return build_mru |
| 2862 | 2905 | ||
| 2863 | 2906 | ||
| 2864 | def template_renderer(template): | ||
| 2865 | def func_wrapper(view): | ||
| 2866 | def returned_wrapper(request, *args, **kwargs): | ||
| 2867 | try: | ||
| 2868 | context = view(request, *args, **kwargs) | ||
| 2869 | except RedirectException as e: | ||
| 2870 | return e.get_redirect_response() | ||
| 2871 | |||
| 2872 | if request.GET.get('format', None) == 'json': | ||
| 2873 | # objects is a special keyword - it's a Page, but we need the actual objects here | ||
| 2874 | # in XHR, the objects come in the "list" property | ||
| 2875 | if "objects" in context: | ||
| 2876 | context["list"] = context["objects"].object_list | ||
| 2877 | del context["objects"] | ||
| 2878 | |||
| 2879 | # we're about to return; to keep up with the XHR API, we set the error to OK | ||
| 2880 | context["error"] = "ok" | ||
| 2881 | |||
| 2882 | return HttpResponse(jsonfilter(context, default=lambda obj: obj.isoformat() if isinstance(obj, datetime) else obj.__dict__ ), | ||
| 2883 | content_type = "application/json; charset=utf-8") | ||
| 2884 | else: | ||
| 2885 | return render(request, template, context) | ||
| 2886 | return returned_wrapper | ||
| 2887 | return func_wrapper | ||
| 2888 | 2907 | ||
| 2889 | @template_renderer("projects.html") | 2908 | @_template_renderer("projects.html") |
| 2890 | def projects(request): | 2909 | def projects(request): |
| 2891 | (pagesize, orderby) = _get_parameters_values(request, 10, 'updated:-') | 2910 | (pagesize, orderby) = _get_parameters_values(request, 10, 'updated:-') |
| 2892 | mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby } | 2911 | mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby } |
| @@ -2995,18 +3014,19 @@ if toastermain.settings.MANAGED: | |||
| 2995 | _set_parameters_values(pagesize, orderby, request) | 3014 | _set_parameters_values(pagesize, orderby, request) |
| 2996 | return context | 3015 | return context |
| 2997 | 3016 | ||
| 3017 | @_template_renderer("buildrequestdetails.html") | ||
| 2998 | def buildrequestdetails(request, pid, brid): | 3018 | def buildrequestdetails(request, pid, brid): |
| 2999 | template = "buildrequestdetails.html" | ||
| 3000 | context = { | 3019 | context = { |
| 3001 | 'buildrequest' : BuildRequest.objects.get(pk = brid, project_id = pid) | 3020 | 'buildrequest' : BuildRequest.objects.get(pk = brid, project_id = pid) |
| 3002 | } | 3021 | } |
| 3003 | return render(request, template, context) | 3022 | return context |
| 3004 | 3023 | ||
| 3005 | 3024 | ||
| 3006 | else: | 3025 | else: |
| 3007 | # shows the "all builds" page for interactive mode; this is the old code, simply moved | 3026 | # shows the "all builds" page for interactive mode; this is the old code, simply moved |
| 3027 | |||
| 3028 | @_template_renderer('build.html') | ||
| 3008 | def builds(request): | 3029 | def builds(request): |
| 3009 | template = 'build.html' | ||
| 3010 | # define here what parameters the view needs in the GET portion in order to | 3030 | # define here what parameters the view needs in the GET portion in order to |
| 3011 | # be able to display something. 'count' and 'page' are mandatory for all views | 3031 | # be able to display something. 'count' and 'page' are mandatory for all views |
| 3012 | # that use paginators. | 3032 | # that use paginators. |
| @@ -3014,7 +3034,7 @@ else: | |||
| 3014 | mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby } | 3034 | mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby } |
| 3015 | retval = _verify_parameters( request.GET, mandatory_parameters ) | 3035 | retval = _verify_parameters( request.GET, mandatory_parameters ) |
| 3016 | if retval: | 3036 | if retval: |
| 3017 | return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters) | 3037 | raise RedirectException( 'all-builds', request.GET, mandatory_parameters) |
| 3018 | 3038 | ||
| 3019 | # boilerplate code that takes a request for an object type and returns a queryset | 3039 | # boilerplate code that takes a request for an object type and returns a queryset |
| 3020 | # for that object type. copypasta for all needed table searches | 3040 | # for that object type. copypasta for all needed table searches |
| @@ -3194,55 +3214,69 @@ else: | |||
| 3194 | 3214 | ||
| 3195 | # merge daterange values | 3215 | # merge daterange values |
| 3196 | context.update(context_date) | 3216 | context.update(context_date) |
| 3197 | |||
| 3198 | response = render(request, template, context) | ||
| 3199 | _set_parameters_values(pagesize, orderby, request) | 3217 | _set_parameters_values(pagesize, orderby, request) |
| 3200 | return response | 3218 | |
| 3219 | return context | ||
| 3201 | 3220 | ||
| 3202 | 3221 | ||
| 3203 | 3222 | ||
| 3204 | 3223 | ||
| 3224 | @_template_renderer('landing_not_managed.html') | ||
| 3205 | def newproject(request): | 3225 | def newproject(request): |
| 3206 | return render(request, 'landing_not_managed.html') | 3226 | return {} |
| 3207 | 3227 | ||
| 3228 | @_template_renderer('landing_not_managed.html') | ||
| 3208 | def project(request, pid): | 3229 | def project(request, pid): |
| 3209 | return render(request, 'landing_not_managed.html') | 3230 | return {} |
| 3210 | 3231 | ||
| 3232 | @_template_renderer('landing_not_managed.html') | ||
| 3211 | def xhr_projectbuild(request, pid): | 3233 | def xhr_projectbuild(request, pid): |
| 3212 | return render(request, 'landing_not_managed.html') | 3234 | return {} |
| 3213 | 3235 | ||
| 3236 | @_template_renderer('landing_not_managed.html') | ||
| 3214 | def xhr_projectinfo(request): | 3237 | def xhr_projectinfo(request): |
| 3215 | return render(request, 'landing_not_managed.html') | 3238 | return {} |
| 3216 | 3239 | ||
| 3240 | @_template_renderer('landing_not_managed.html') | ||
| 3217 | def xhr_projectedit(request, pid): | 3241 | def xhr_projectedit(request, pid): |
| 3218 | return render(request, 'landing_not_managed.html') | 3242 | return {} |
| 3219 | 3243 | ||
| 3244 | @_template_renderer('landing_not_managed.html') | ||
| 3220 | def xhr_datatypeahead(request): | 3245 | def xhr_datatypeahead(request): |
| 3221 | return render(request, 'landing_not_managed.html') | 3246 | return {} |
| 3222 | 3247 | ||
| 3248 | @_template_renderer('landing_not_managed.html') | ||
| 3223 | def xhr_configvaredit(request, pid): | 3249 | def xhr_configvaredit(request, pid): |
| 3224 | return render(request, 'landing_not_managed.html') | 3250 | return {} |
| 3225 | 3251 | ||
| 3252 | @_template_renderer('landing_not_managed.html') | ||
| 3226 | def importlayer(request): | 3253 | def importlayer(request): |
| 3227 | return render(request, 'landing_not_managed.html') | 3254 | return {} |
| 3228 | 3255 | ||
| 3256 | @_template_renderer('landing_not_managed.html') | ||
| 3229 | def projectconf(request, pid): | 3257 | def projectconf(request, pid): |
| 3230 | return render(request, 'landing_not_managed.html') | 3258 | return {} |
| 3231 | 3259 | ||
| 3260 | @_template_renderer('landing_not_managed.html') | ||
| 3232 | def projectbuilds(request, pid): | 3261 | def projectbuilds(request, pid): |
| 3233 | return render(request, 'landing_not_managed.html') | 3262 | return {} |
| 3234 | 3263 | ||
| 3264 | @_template_renderer('landing_not_managed.html') | ||
| 3235 | def build_artifact(request, build_id, artifact_type, artifact_id): | 3265 | def build_artifact(request, build_id, artifact_type, artifact_id): |
| 3236 | return render(request, 'landing_not_managed.html') | 3266 | return {} |
| 3237 | 3267 | ||
| 3268 | @_template_renderer('landing_not_managed.html') | ||
| 3238 | def projects(request): | 3269 | def projects(request): |
| 3239 | return render(request, 'landing_not_managed.html') | 3270 | return {} |
| 3240 | 3271 | ||
| 3272 | @_template_renderer('landing_not_managed.html') | ||
| 3241 | def xhr_importlayer(request): | 3273 | def xhr_importlayer(request): |
| 3242 | return render(request, 'landing_not_managed.html') | 3274 | return {} |
| 3243 | 3275 | ||
| 3276 | @_template_renderer('landing_not_managed.html') | ||
| 3244 | def xhr_updatelayer(request): | 3277 | def xhr_updatelayer(request): |
| 3245 | return render(request, 'landing_not_managed.html') | 3278 | return {} |
| 3246 | 3279 | ||
| 3280 | @_template_renderer('landing_not_managed.html') | ||
| 3247 | def buildrequestdetails(request, pid, brid): | 3281 | def buildrequestdetails(request, pid, brid): |
| 3248 | return render(request, 'landing_not_managed.html') | 3282 | return {} |
