summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/toaster/toastergui
diff options
context:
space:
mode:
authorDavid Reyna <David.Reyna@windriver.com>2015-02-26 21:42:00 +0000
committerRichard Purdie <richard.purdie@linuxfoundation.org>2015-02-27 07:36:08 +0000
commit8c476c27bb532b33004cba363bdf5794bba3a6f7 (patch)
treea95b3b6e89d70be7490d39aa9d15c20096463de0 /bitbake/lib/toaster/toastergui
parent6768a3069da45b3512601d8361bf64f06ee11e6f (diff)
downloadpoky-8c476c27bb532b33004cba363bdf5794bba3a6f7.tar.gz
bitbake: toaster: all projects data and sorts
Implement the 'last build' data methods, enhance variable display, add empty page and empty sort support. [YOCTO #6682] (Bitbake rev: cc6ca17e80844ecb4a777276d5f5177ebbcd93f9) Signed-off-by: David Reyna <David.Reyna@windriver.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/toaster/toastergui')
-rw-r--r--bitbake/lib/toaster/toastergui/templates/managed_builds.html22
-rw-r--r--bitbake/lib/toaster/toastergui/templates/projects.html60
-rwxr-xr-xbitbake/lib/toaster/toastergui/views.py97
3 files changed, 126 insertions, 53 deletions
diff --git a/bitbake/lib/toaster/toastergui/templates/managed_builds.html b/bitbake/lib/toaster/toastergui/templates/managed_builds.html
index a4db55b967..e23b832bae 100644
--- a/bitbake/lib/toaster/toastergui/templates/managed_builds.html
+++ b/bitbake/lib/toaster/toastergui/templates/managed_builds.html
@@ -56,6 +56,13 @@
56 </td> 56 </td>
57 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td> 57 <td class="target">{% for t in build.target_set.all %} <a href="{% url "builddashboard" build.id %}"> {{t.target}} </a> <br />{% endfor %}</td>
58 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td> 58 <td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td>
59 {% if MANAGED %}
60 <td class="project">
61 {% if build.project %}
62 <a href="{% url 'project' build.project.id %}">{{build.project.name}}</a>
63 {% endif %}
64 </td>
65 {% endif %}
59 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td> 66 <td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on|date:"d/m/y H:i"}}</a></td>
60 <td class="completed_on"><a href="{% url "builddashboard" build.id %}">{{build.completed_on|date:"d/m/y H:i"}}</a></td> 67 <td class="completed_on"><a href="{% url "builddashboard" build.id %}">{{build.completed_on|date:"d/m/y H:i"}}</a></td>
61 <td class="failed_tasks error"> 68 <td class="failed_tasks error">
@@ -91,13 +98,6 @@
91 <a href="{%url "builddashboard" build.id%}#images">{{fstypes|get_dict_value:build.id}}</a> 98 <a href="{%url "builddashboard" build.id%}#images">{{fstypes|get_dict_value:build.id}}</a>
92 {% endif %} 99 {% endif %}
93 </td> 100 </td>
94 {% if MANAGED %}
95 <td class="project">
96 {% if build.project %}
97 <a href="{% url 'project' build.project.id %}">{{build.project.name}}</a>
98 {% endif %}
99 </td>
100 {% endif %}
101 </tr> 101 </tr>
102 102
103 103
@@ -114,6 +114,11 @@
114 <td class="machine"> 114 <td class="machine">
115 <a href="{% url "buildrequestdetails" buildrequest.project.id buildrequest.id %}">{{buildrequest.machine}}</a> 115 <a href="{% url "buildrequestdetails" buildrequest.project.id buildrequest.id %}">{{buildrequest.machine}}</a>
116 </td> 116 </td>
117 {% if MANAGED %}
118 <td class="project">
119 <a href="{% url 'project' buildrequest.project.id %}">{{buildrequest.project.name}}</a>
120 </td>
121 {% endif %}
117 <td class="started_on"> 122 <td class="started_on">
118 <a href="{% url "buildrequestdetails" buildrequest.project.id buildrequest.id %}">{{buildrequest.created|date:"d/m/y H:i"}}</a> 123 <a href="{% url "buildrequestdetails" buildrequest.project.id buildrequest.id %}">{{buildrequest.created|date:"d/m/y H:i"}}</a>
119 </td> 124 </td>
@@ -132,9 +137,6 @@
132 </td> 137 </td>
133 <td class="output"> {# we have no output here #} 138 <td class="output"> {# we have no output here #}
134 </td> 139 </td>
135 <td class="project">
136 <a href="{% url 'project' buildrequest.project.id %}">{{buildrequest.project.name}}</a>
137 </td>
138 </tr> 140 </tr>
139 {%endif%} 141 {%endif%}
140 {% endfor %} 142 {% endfor %}
diff --git a/bitbake/lib/toaster/toastergui/templates/projects.html b/bitbake/lib/toaster/toastergui/templates/projects.html
index 0396e25a3a..88d5bd32df 100644
--- a/bitbake/lib/toaster/toastergui/templates/projects.html
+++ b/bitbake/lib/toaster/toastergui/templates/projects.html
@@ -12,25 +12,61 @@
12 12
13 <div class="page-header top-air"> 13 <div class="page-header top-air">
14 <h1> 14 <h1>
15 All projects 15 {% if request.GET.filter and objects.paginator.count > 0 or request.GET.search and objects.paginator.count > 0 %}
16 {{objects.paginator.count}} project{{objects.paginator.count|pluralize}} found
17 {%elif request.GET.filter and objects.paginator.count == 0 or request.GET.search and objects.paginator.count == 0 %}
18 No projects found
19 {%else%}
20 All projects
21 {%endif%}
16 </h1> 22 </h1>
17 </div> 23 </div>
18 24
19{% include "basetable_top_projectbuilds.html" %} 25 {% if objects.paginator.count == 0 %}
26 <div class="row-fluid">
27 <div class="alert">
28 <form class="no-results input-append" id="searchform">
29 <input id="search" name="search" class="input-xxlarge" type="text" value="{{request.GET.search}}"/>{% if request.GET.search %}<a href="javascript:$('#search').val('');searchform.submit()" class="add-on btn" tabindex="-1"><i class="icon-remove"></i></a>{% endif %}
30 <button class="btn" type="submit" value="Search">Search</button>
31 <button class="btn btn-link" onclick="javascript:$('#search').val('');searchform.submit()">Show all projects</button>
32 </form>
33 </div>
34 </div>
35
36 {% else %} {# We have builds to display #}
37 {% include "basetable_top_projectbuilds.html" %}
20 {% for o in objects %} 38 {% for o in objects %}
21 <tr class="data"> 39 <tr class="data">
22 <td><a href="{% url 'project' o.id %}">{{o.name}}</a></td> 40 <td><a href="{% url 'project' o.id %}">{{o.name}}</a></td>
23 <td><a href="{% url 'project' o.id %}">{{o.release.name}}</a></td> 41 <td><a href="{% url 'project' o.id %}#project-details">{{o.release.name}}</a></td>
24 <td>{{o.get_current_machine_name}}</td> 42 <td><a href="{% url 'project' o.id %}#machine-distro">{{o.get_current_machine_name}}</a></td>
25 <td>{{o.get_number_of_builds}}</td> 43 {% if o.get_number_of_builds == 0 %}
26 <td class="loutcome">{{o.get_last_outcome}}</td> 44 <td class="muted">{{o.get_number_of_builds}}</td>
27 <td class="ltarget">{{o.get_last_target}}</td> 45 <td class="updated"></td>
28 <td class="lerrors">{{o.get_last_errors}}</td> 46 <td class="loutcome"></td>
29 <td class="lwarnings">{{o.get_last_warnings}}</td> 47 <td class="ltarget"></td>
30 <td class="limagefiles">{{o.get_last_imgfiles}}</td> 48 <td class="lerrors"></td>
31 <td class="updated">{{o.updated|date:"d/m/y H:i"}}</td> 49 <td class="lwarnings"></td>
50 <td class="limagefiles"></td>
51 {% else %}
52 <td><a href="{% url 'projectbuilds' o.id %}">{{o.get_number_of_builds}}</a></td>
53 <td class="updated"><a href="{% url "builddashboard" o.get_last_build_id %}">{{o.updated|date:"d/m/y H:i"}}</a></td>
54 <td class="loutcome"><a href="{% url "builddashboard" o.get_last_build_id %}">{%if o.get_last_outcome == build_SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif o.get_last_outcome == build_FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a></td>
55 <td class="ltarget"><a href="{% url "builddashboard" o.get_last_build_id %}">{{o.get_last_target}} </a></td>
56 <td class="lerrors">{% if o.get_last_errors %}<a class="errors_no error" href="{% url "builddashboard" o.get_last_build_id %}#errors">{{o.get_last_errors}} error{{o.get_last_errors|pluralize}}</a>{%endif%}</td>
57 <td class="lwarnings">{% if o.get_last_warnings %}<a class="warnings_no warning" href="{% url "builddashboard" o.get_last_build_id %}#warnings">{{o.get_last_warnings}} warning{{o.get_last_warnings|pluralize}}</a>{%endif%}</td>
58 <td class="limagefiles">
59 {% if o.get_last_outcome == build_SUCCEEDED %}
60 <a href="{%url "builddashboard" o.get_last_build_id %}#images">{{fstypes|get_dict_value:o.id}}</a>
61 {% endif %}
62 </td>
63
64 {% endif %}
32 </tr> 65 </tr>
33 {% endfor %} 66 {% endfor %}
34{% include "basetable_bottom.html" %} 67 {% include "basetable_bottom.html" %}
68 {% endif %} {# empty #}
35 69
36{% endblock %} 70{% endblock %}
71
72
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index a206f80b64..4f4ae67ca7 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -1758,20 +1758,10 @@ if toastermain.settings.MANAGED:
1758 buildrequests = BuildRequest.objects.exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED) 1758 buildrequests = BuildRequest.objects.exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED)
1759 1759
1760 try: 1760 try:
1761 context, pagesize, orderby = _build_list_helper(request, buildrequests) 1761 context, pagesize, orderby = _build_list_helper(request, buildrequests, True)
1762 except InvalidRequestException as e: 1762 except InvalidRequestException as e:
1763 return _redirect_parameters( builds, request.GET, e.response) 1763 return _redirect_parameters( builds, request.GET, e.response)
1764 1764
1765 context['tablecols'].append(
1766 {'name': 'Project', 'clclass': 'projectx',
1767 'filter': {'class': 'project',
1768 'label': 'Project:',
1769 'options': map(lambda x: (x.name,'project:%d' % x.id,x.build_set.filter(outcome__lt=BuildRequest.REQ_INPROGRESS).count()), Project.objects.all()),
1770
1771 }
1772 }
1773 )
1774
1775 response = render(request, template, context) 1765 response = render(request, template, context)
1776 _save_parameters_cookies(response, pagesize, orderby, request) 1766 _save_parameters_cookies(response, pagesize, orderby, request)
1777 return response 1767 return response
@@ -1779,7 +1769,7 @@ if toastermain.settings.MANAGED:
1779 1769
1780 1770
1781 # helper function, to be used on "all builds" and "project builds" pages 1771 # helper function, to be used on "all builds" and "project builds" pages
1782 def _build_list_helper(request, buildrequests): 1772 def _build_list_helper(request, buildrequests, insert_projects):
1783 # ATTN: we use here the ordering parameters for interactive mode; the translation for BuildRequest fields will happen below 1773 # ATTN: we use here the ordering parameters for interactive mode; the translation for BuildRequest fields will happen below
1784 default_orderby = 'completed_on:-' 1774 default_orderby = 'completed_on:-'
1785 (pagesize, orderby) = _get_parameters_values(request, 10, default_orderby) 1775 (pagesize, orderby) = _get_parameters_values(request, 10, default_orderby)
@@ -1893,6 +1883,16 @@ if toastermain.settings.MANAGED:
1893 'ordericon':_get_toggle_order_icon(request, "build__machine"), 1883 'ordericon':_get_toggle_order_icon(request, "build__machine"),
1894 'dclass': 'span3' 1884 'dclass': 'span3'
1895 }, # a slightly wider column 1885 }, # a slightly wider column
1886 ]
1887 }
1888
1889 if (insert_projects):
1890 context['tablecols'].append(
1891 {'name': 'Project', 'clclass': 'project',
1892 }
1893 )
1894
1895 context['tablecols'].append(
1896 {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column 1896 {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column
1897 'qhelp': "The date and time you started the build", 1897 'qhelp': "The date and time you started the build",
1898 'orderfield': _get_toggle_order(request, "created", True), 1898 'orderfield': _get_toggle_order(request, "created", True),
@@ -1905,7 +1905,9 @@ if toastermain.settings.MANAGED:
1905 ("This week's builds", 'created__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_all.filter(created__gte=(timezone.now()-timedelta(days=7))).count()), 1905 ("This week's builds", 'created__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_all.filter(created__gte=(timezone.now()-timedelta(days=7))).count()),
1906 ] 1906 ]
1907 } 1907 }
1908 }, 1908 }
1909 )
1910 context['tablecols'].append(
1909 {'name': 'Completed on', 1911 {'name': 'Completed on',
1910 'qhelp': "The date and time the build finished", 1912 'qhelp': "The date and time the build finished",
1911 'orderfield': _get_toggle_order(request, "updated", True), 1913 'orderfield': _get_toggle_order(request, "updated", True),
@@ -1919,7 +1921,9 @@ if toastermain.settings.MANAGED:
1919 ("This week's builds", 'updated__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_all.filter(updated__gte=(timezone.now()-timedelta(days=7))).count()), 1921 ("This week's builds", 'updated__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_all.filter(updated__gte=(timezone.now()-timedelta(days=7))).count()),
1920 ] 1922 ]
1921 } 1923 }
1922 }, 1924 }
1925 )
1926 context['tablecols'].append(
1923 {'name': 'Failed tasks', 'clclass': 'failed_tasks', # specifing a clclass will enable the checkbox 1927 {'name': 'Failed tasks', 'clclass': 'failed_tasks', # specifing a clclass will enable the checkbox
1924 'qhelp': "How many tasks failed during the build", 1928 'qhelp': "How many tasks failed during the build",
1925 'filter' : {'class' : 'failed_tasks', 1929 'filter' : {'class' : 'failed_tasks',
@@ -1931,7 +1935,9 @@ if toastermain.settings.MANAGED:
1931 queryset_all.filter(~Q(build__task_build__outcome=Task.OUTCOME_FAILED)).count()), 1935 queryset_all.filter(~Q(build__task_build__outcome=Task.OUTCOME_FAILED)).count()),
1932 ] 1936 ]
1933 } 1937 }
1934 }, 1938 }
1939 )
1940 context['tablecols'].append(
1935 {'name': 'Errors', 'clclass': 'errors_no', 1941 {'name': 'Errors', 'clclass': 'errors_no',
1936 'qhelp': "How many errors were encountered during the build (if any)", 1942 'qhelp': "How many errors were encountered during the build (if any)",
1937 'orderfield': _get_toggle_order(request, "build__errors_no", True), 1943 'orderfield': _get_toggle_order(request, "build__errors_no", True),
@@ -1946,7 +1952,9 @@ if toastermain.settings.MANAGED:
1946 queryset_all.filter(build__errors_no=0).count()), 1952 queryset_all.filter(build__errors_no=0).count()),
1947 ] 1953 ]
1948 } 1954 }
1949 }, 1955 }
1956 )
1957 context['tablecols'].append(
1950 {'name': 'Warnings', 'clclass': 'warnings_no', 1958 {'name': 'Warnings', 'clclass': 'warnings_no',
1951 'qhelp': "How many warnings were encountered during the build (if any)", 1959 'qhelp': "How many warnings were encountered during the build (if any)",
1952 'orderfield': _get_toggle_order(request, "build__warnings_no", True), 1960 'orderfield': _get_toggle_order(request, "build__warnings_no", True),
@@ -1959,19 +1967,23 @@ if toastermain.settings.MANAGED:
1959 ('Builds without warnings','build__warnings_no:0', queryset_all.filter(build__warnings_no=0).count()), 1967 ('Builds without warnings','build__warnings_no:0', queryset_all.filter(build__warnings_no=0).count()),
1960 ] 1968 ]
1961 } 1969 }
1962 }, 1970 }
1971 )
1972 context['tablecols'].append(
1963 {'name': 'Time', 'clclass': 'time', 'hidden' : 1, 1973 {'name': 'Time', 'clclass': 'time', 'hidden' : 1,
1964 'qhelp': "How long it took the build to finish", 1974 'qhelp': "How long it took the build to finish",
1965# 'orderfield': _get_toggle_order(request, "timespent", True), 1975# 'orderfield': _get_toggle_order(request, "timespent", True),
1966# 'ordericon':_get_toggle_order_icon(request, "timespent"), 1976# 'ordericon':_get_toggle_order_icon(request, "timespent"),
1967 'orderkey' : 'timespent', 1977 'orderkey' : 'timespent',
1968 }, 1978 }
1979 )
1980 context['tablecols'].append(
1969 {'name': 'Image files', 'clclass': 'output', 1981 {'name': 'Image files', 'clclass': 'output',
1970 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory", 1982 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory",
1971 # TODO: compute image fstypes from Target_Image_File 1983 # TODO: compute image fstypes from Target_Image_File
1972 }, 1984 }
1973 ] 1985 )
1974 } 1986
1975 return context, pagesize, orderby 1987 return context, pagesize, orderby
1976 1988
1977 # new project 1989 # new project
@@ -2898,7 +2910,7 @@ if toastermain.settings.MANAGED:
2898 buildrequests = BuildRequest.objects.filter(project_id = pid).exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED) 2910 buildrequests = BuildRequest.objects.filter(project_id = pid).exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED)
2899 2911
2900 try: 2912 try:
2901 context, pagesize, orderby = _build_list_helper(request, buildrequests) 2913 context, pagesize, orderby = _build_list_helper(request, buildrequests, False)
2902 except InvalidRequestException as e: 2914 except InvalidRequestException as e:
2903 return _redirect_parameters(projectbuilds, request.GET, e.response, pid = pid) 2915 return _redirect_parameters(projectbuilds, request.GET, e.response, pid = pid)
2904 2916
@@ -3019,7 +3031,7 @@ if toastermain.settings.MANAGED:
3019 def projects(request): 3031 def projects(request):
3020 template="projects.html" 3032 template="projects.html"
3021 3033
3022 (pagesize, orderby) = _get_parameters_values(request, 10, 'updated:+') 3034 (pagesize, orderby) = _get_parameters_values(request, 10, 'updated:-')
3023 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby } 3035 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
3024 retval = _verify_parameters( request.GET, mandatory_parameters ) 3036 retval = _verify_parameters( request.GET, mandatory_parameters )
3025 if retval: 3037 if retval:
@@ -3039,7 +3051,27 @@ if toastermain.settings.MANAGED:
3039 # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds) 3051 # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
3040 build_mru = Build.objects.order_by("-started_on")[:3] 3052 build_mru = Build.objects.order_by("-started_on")[:3]
3041 3053
3042 3054 # translate the project's build target strings
3055 fstypes_map = {};
3056 for project in project_info:
3057 try:
3058 targets = Target.objects.filter( build_id = project.get_last_build_id() )
3059 comma = "";
3060 extensions = "";
3061 for t in targets:
3062 if ( not t.is_image ):
3063 continue
3064 tif = Target_Image_File.objects.filter( target_id = t.id )
3065 for i in tif:
3066 s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name)
3067 if s == i.file_name:
3068 s=re.sub('.*\.', '', i.file_name)
3069 if None == re.search(s,extensions):
3070 extensions += comma + s
3071 comma = ", "
3072 fstypes_map[project.id]=extensions
3073 except (Target.DoesNotExist,IndexError):
3074 fstypes_map[project.id]=project.get_last_imgfiles
3043 3075
3044 context = { 3076 context = {
3045 'mru' : build_mru, 3077 'mru' : build_mru,
@@ -3049,6 +3081,9 @@ if toastermain.settings.MANAGED:
3049 'default_orderby' : 'id:-', 3081 'default_orderby' : 'id:-',
3050 'search_term' : search_term, 3082 'search_term' : search_term,
3051 'total_count' : queryset_with_search.count(), 3083 'total_count' : queryset_with_search.count(),
3084 'fstypes' : fstypes_map,
3085 'build_FAILED' : Build.FAILED,
3086 'build_SUCCEEDED' : Build.SUCCEEDED,
3052 'tablecols': [ 3087 'tablecols': [
3053 {'name': 'Project', 3088 {'name': 'Project',
3054 'orderfield': _get_toggle_order(request, "name"), 3089 'orderfield': _get_toggle_order(request, "name"),
@@ -3067,6 +3102,11 @@ if toastermain.settings.MANAGED:
3067 {'name': 'Number of builds', 3102 {'name': 'Number of builds',
3068 'qhelp': "How many builds have been run for the project", 3103 'qhelp': "How many builds have been run for the project",
3069 }, 3104 },
3105 {'name': 'Last build', 'clclass': 'updated',
3106 'orderfield': _get_toggle_order(request, "updated", True),
3107 'ordericon':_get_toggle_order_icon(request, "updated"),
3108 'orderkey' : 'updated',
3109 },
3070 {'name': 'Last outcome', 'clclass': 'loutcome', 3110 {'name': 'Last outcome', 'clclass': 'loutcome',
3071 'qhelp': "Tells you if the last project build completed successfully or failed", 3111 'qhelp': "Tells you if the last project build completed successfully or failed",
3072 }, 3112 },
@@ -3082,11 +3122,6 @@ if toastermain.settings.MANAGED:
3082 {'name': 'Last image files', 'clclass': 'limagefiles', 'hidden': 1, 3122 {'name': 'Last image files', 'clclass': 'limagefiles', 'hidden': 1,
3083 'qhelp': "The root file system types produced by the last project build", 3123 'qhelp': "The root file system types produced by the last project build",
3084 }, 3124 },
3085 {'name': 'Last updated', 'clclass': 'updated',
3086 'orderfield': _get_toggle_order(request, "updated"),
3087 'ordericon':_get_toggle_order_icon(request, "updated"),
3088 'orderkey' : 'updated',
3089 }
3090 ] 3125 ]
3091 } 3126 }
3092 return render(request, template, context) 3127 return render(request, template, context)