From 5cc28dc752c3ae78456bb835c3ba195489fc26d7 Mon Sep 17 00:00:00 2001 From: Raffaele Salmaso <raffaele@salmaso.org> Date: Thu, 12 Jan 2017 17:06:00 +0100 Subject: [PATCH] Fixed #27728 -- Allowed overriding admin templatetags templates. --- .../admin/templates/admin/actions.html | 8 +++ .../admin/templates/admin/change_form.html | 6 +- .../admin/change_form_object_tools.html | 8 +++ .../admin/templates/admin/change_list.html | 9 +-- .../admin/change_list_object_tools.html | 12 ++++ .../admin/templates/admin/date_hierarchy.html | 6 ++ .../admin/templates/admin/submit_line.html | 2 + .../contrib/admin/templatetags/admin_list.py | 63 +++++++++++++++++-- .../admin/templatetags/admin_modify.py | 30 ++++++++- django/contrib/admin/templatetags/base.py | 31 +++++++++ docs/ref/contrib/admin/index.txt | 16 +++++ docs/releases/2.1.txt | 8 +++ tests/admin_changelist/tests.py | 10 +-- .../admin/admin_views/article/actions.html | 6 ++ .../article/change_form_object_tools.html | 7 +++ .../article/change_list_object_tools.html | 7 +++ .../article/change_list_results.html | 38 +++++++++++ .../admin_views/article/date_hierarchy.html | 9 +++ .../admin/admin_views/article/pagination.html | 12 ++++ .../article/prepopulated_fields_js.html | 7 +++ .../admin_views/article/search_form.html | 16 +++++ .../admin_views/article/submit_line.html | 7 +++ tests/admin_views/test_templatetags.py | 45 ++++++++++++- 23 files changed, 336 insertions(+), 27 deletions(-) create mode 100644 django/contrib/admin/templates/admin/change_form_object_tools.html create mode 100644 django/contrib/admin/templates/admin/change_list_object_tools.html create mode 100644 django/contrib/admin/templatetags/base.py create mode 100644 tests/admin_views/templates/admin/admin_views/article/actions.html create mode 100644 tests/admin_views/templates/admin/admin_views/article/change_form_object_tools.html create mode 100644 tests/admin_views/templates/admin/admin_views/article/change_list_object_tools.html create mode 100644 tests/admin_views/templates/admin/admin_views/article/change_list_results.html create mode 100644 tests/admin_views/templates/admin/admin_views/article/date_hierarchy.html create mode 100644 tests/admin_views/templates/admin/admin_views/article/pagination.html create mode 100644 tests/admin_views/templates/admin/admin_views/article/prepopulated_fields_js.html create mode 100644 tests/admin_views/templates/admin/admin_views/article/search_form.html create mode 100644 tests/admin_views/templates/admin/admin_views/article/submit_line.html diff --git a/django/contrib/admin/templates/admin/actions.html b/django/contrib/admin/templates/admin/actions.html index 80ffa066ed..ef2232e13f 100644 --- a/django/contrib/admin/templates/admin/actions.html +++ b/django/contrib/admin/templates/admin/actions.html @@ -1,7 +1,13 @@ {% load i18n %} <div class="actions"> + {% block actions %} + {% block actions-form %} {% for field in action_form %}{% if field.label %}<label>{{ field.label }} {% endif %}{{ field }}{% if field.label %}</label>{% endif %}{% endfor %} + {% endblock %} + {% block actions-submit %} <button type="submit" class="button" title="{% trans "Run the selected action" %}" name="index" value="{{ action_index|default:0 }}">{% trans "Go" %}</button> + {% endblock %} + {% block actions-counter %} {% if actions_selection_counter %} <span class="action-counter" data-actions-icnt="{{ cl.result_list|length }}">{{ selection_note }}</span> {% if cl.result_count != cl.result_list|length %} @@ -12,4 +18,6 @@ <span class="clear"><a href="#">{% trans "Clear selection" %}</a></span> {% endif %} {% endif %} + {% endblock %} + {% endblock %} </div> diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index f77e50a130..604747e6d9 100644 --- a/django/contrib/admin/templates/admin/change_form.html +++ b/django/contrib/admin/templates/admin/change_form.html @@ -28,11 +28,7 @@ {% if change %}{% if not is_popup %} <ul class="object-tools"> {% block object-tools-items %} - <li> - {% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %} - <a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a> - </li> - {% if has_absolute_url %}<li><a href="{{ absolute_url }}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif %} + {% change_form_object_tools %} {% endblock %} </ul> {% endif %}{% endif %} diff --git a/django/contrib/admin/templates/admin/change_form_object_tools.html b/django/contrib/admin/templates/admin/change_form_object_tools.html new file mode 100644 index 0000000000..32487493a2 --- /dev/null +++ b/django/contrib/admin/templates/admin/change_form_object_tools.html @@ -0,0 +1,8 @@ +{% load i18n admin_urls %} +{% block object-tools-items %} +<li> + {% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %} + <a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a> +</li> +{% if has_absolute_url %}<li><a href="{{ absolute_url }}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif %} +{% endblock %} diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html index 34a4031799..0ffca672c0 100644 --- a/django/contrib/admin/templates/admin/change_list.html +++ b/django/contrib/admin/templates/admin/change_list.html @@ -42,14 +42,7 @@ {% block object-tools %} <ul class="object-tools"> {% block object-tools-items %} - {% if has_add_permission %} - <li> - {% url cl.opts|admin_urlname:'add' as add_url %} - <a href="{% add_preserved_filters add_url is_popup to_field %}" class="addlink"> - {% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %} - </a> - </li> - {% endif %} + {% change_list_object_tools %} {% endblock %} </ul> {% endblock %} diff --git a/django/contrib/admin/templates/admin/change_list_object_tools.html b/django/contrib/admin/templates/admin/change_list_object_tools.html new file mode 100644 index 0000000000..5d6d458276 --- /dev/null +++ b/django/contrib/admin/templates/admin/change_list_object_tools.html @@ -0,0 +1,12 @@ +{% load i18n admin_urls %} + +{% block object-tools-items %} + {% if has_add_permission %} + <li> + {% url cl.opts|admin_urlname:'add' as add_url %} + <a href="{% add_preserved_filters add_url is_popup to_field %}" class="addlink"> + {% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %} + </a> + </li> + {% endif %} +{% endblock %} diff --git a/django/contrib/admin/templates/admin/date_hierarchy.html b/django/contrib/admin/templates/admin/date_hierarchy.html index ecbd2a1a00..65ae800134 100644 --- a/django/contrib/admin/templates/admin/date_hierarchy.html +++ b/django/contrib/admin/templates/admin/date_hierarchy.html @@ -1,10 +1,16 @@ {% if show %} <div class="xfull"> <ul class="toplinks"> +{% block date-hierarchy-toplinks %} +{% block date-hierarchy-back %} {% if back %}<li class="date-back"><a href="{{ back.link }}">‹ {{ back.title }}</a></li>{% endif %} +{% endblock %} +{% block date-hierarchy-choices %} {% for choice in choices %} <li> {% if choice.link %}<a href="{{ choice.link }}">{% endif %}{{ choice.title }}{% if choice.link %}</a>{% endif %}</li> {% endfor %} +{% endblock %} +{% endblock %} </ul><br class="clear"> </div> {% endif %} diff --git a/django/contrib/admin/templates/admin/submit_line.html b/django/contrib/admin/templates/admin/submit_line.html index 2e6cf057f0..26f3920ffa 100644 --- a/django/contrib/admin/templates/admin/submit_line.html +++ b/django/contrib/admin/templates/admin/submit_line.html @@ -1,5 +1,6 @@ {% load i18n admin_urls %} <div class="submit-row"> +{% block submit-row %} {% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save">{% endif %} {% if show_delete_link %} {% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %} @@ -8,4 +9,5 @@ {% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew">{% endif %} {% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother">{% endif %} {% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue">{% endif %} +{% endblock %} </div> diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index eeb56c0aa4..ff04faed94 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -19,6 +19,8 @@ from django.utils.safestring import mark_safe from django.utils.text import capfirst from django.utils.translation import gettext as _ +from .base import InclusionAdminNode + register = Library() DOT = '.' @@ -40,7 +42,6 @@ def paginator_number(cl, i): i + 1) -@register.inclusion_tag('admin/pagination.html') def pagination(cl): """ Generate the series of links to the pages in a paginated list. @@ -89,6 +90,16 @@ def pagination(cl): } +@register.tag(name='pagination') +def pagination_tag(parser, token): + return InclusionAdminNode( + parser, token, + func=pagination, + template_name='pagination.html', + takes_context=False, + ) + + def result_headers(cl): """ Generate the list column headers. @@ -314,7 +325,6 @@ def result_hidden_fields(cl): yield mark_safe(form[cl.model._meta.pk.name]) -@register.inclusion_tag("admin/change_list_results.html") def result_list(cl): """ Display the headers and data list together. @@ -331,7 +341,16 @@ def result_list(cl): 'results': list(results(cl))} -@register.inclusion_tag('admin/date_hierarchy.html') +@register.tag(name='result_list') +def result_list_tag(parser, token): + return InclusionAdminNode( + parser, token, + func=result_list, + template_name='change_list_results.html', + takes_context=False, + ) + + def date_hierarchy(cl): """ Display the date hierarchy for date drill-down functionality. @@ -406,7 +425,16 @@ def date_hierarchy(cl): } -@register.inclusion_tag('admin/search_form.html') +@register.tag(name='date_hierarchy') +def date_hierarchy_tag(parser, token): + return InclusionAdminNode( + parser, token, + func=date_hierarchy, + template_name='date_hierarchy.html', + takes_context=False, + ) + + def search_form(cl): """ Display a search form for searching the list. @@ -418,6 +446,11 @@ def search_form(cl): } +@register.tag(name='search_form') +def search_form_tag(parser, token): + return InclusionAdminNode(parser, token, func=search_form, template_name='search_form.html', takes_context=False) + + @register.simple_tag def admin_list_filter(cl, spec): tpl = get_template(spec.template) @@ -428,7 +461,6 @@ def admin_list_filter(cl, spec): }) -@register.inclusion_tag('admin/actions.html', takes_context=True) def admin_actions(context): """ Track the number of times the action field has been rendered on the page, @@ -436,3 +468,24 @@ def admin_actions(context): """ context['action_index'] = context.get('action_index', -1) + 1 return context + + +@register.tag(name='admin_actions') +def admin_actions_tag(parser, token): + return InclusionAdminNode(parser, token, func=admin_actions, template_name='actions.html') + + +def change_list_object_tools(context): + """ + Displays the row of change list object tools. + """ + return context + + +@register.tag(name='change_list_object_tools') +def change_list_object_tools_tag(parser, token): + return InclusionAdminNode( + parser, token, + func=change_list_object_tools, + template_name='change_list_object_tools.html', + ) diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py index 50ec00bb25..95f0fc977c 100644 --- a/django/contrib/admin/templatetags/admin_modify.py +++ b/django/contrib/admin/templatetags/admin_modify.py @@ -3,10 +3,11 @@ import json from django import template from django.template.context import Context +from .base import InclusionAdminNode + register = template.Library() -@register.inclusion_tag('admin/prepopulated_fields_js.html', takes_context=True) def prepopulated_fields_js(context): """ Create a list of prepopulated_fields that should render Javascript for @@ -39,7 +40,11 @@ def prepopulated_fields_js(context): return context -@register.inclusion_tag('admin/submit_line.html', takes_context=True) +@register.tag(name='prepopulated_fields_js') +def prepopulated_fields_js_tag(parser, token): + return InclusionAdminNode(parser, token, func=prepopulated_fields_js, template_name="prepopulated_fields_js.html") + + def submit_row(context): """ Display the row of buttons for delete and save. @@ -66,6 +71,27 @@ def submit_row(context): return ctx +@register.tag(name='submit_row') +def submit_row_tag(parser, token): + return InclusionAdminNode(parser, token, func=submit_row, template_name='submit_line.html') + + +def change_form_object_tools(context): + """ + Displays the row of change form object tools. + """ + return context + + +@register.tag(name='change_form_object_tools') +def change_form_object_tools_tag(parser, token): + return InclusionAdminNode( + parser, token, + func=change_form_object_tools, + template_name='change_form_object_tools.html', + ) + + @register.filter def cell_count(inline_admin_form): """Return the number of cells used in a tabular inline.""" diff --git a/django/contrib/admin/templatetags/base.py b/django/contrib/admin/templatetags/base.py new file mode 100644 index 0000000000..a26a84d0de --- /dev/null +++ b/django/contrib/admin/templatetags/base.py @@ -0,0 +1,31 @@ +from inspect import getfullargspec + +from django.template.library import InclusionNode, parse_bits + + +class InclusionAdminNode(InclusionNode): + def __init__(self, parser, token, func, template_name, takes_context=True): + self.template_name = template_name + + params, varargs, varkw, defaults, kwonly, kwonly_defaults, _ = getfullargspec(func) + if len(params) > 0 and params[0] == 'self': + params = params[1:] # ignore 'self' + bits = token.split_contents() + args, kwargs = parse_bits( + parser, bits[1:], params, varargs, varkw, defaults, kwonly, kwonly_defaults, takes_context, bits[0] + ) + super().__init__( + func=func, takes_context=takes_context, args=args, kwargs=kwargs, filename=None + ) + + def render(self, context): + opts = context['opts'] + app_label = opts.app_label.lower() + object_name = opts.object_name.lower() + self.filename = [ + 'admin/%s/%s/%s' % (app_label, object_name, self.template_name), + 'admin/%s/%s' % (app_label, self.template_name), + 'admin/%s' % (self.template_name,), + ] + + return super().render(context) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index d1138076ff..5ea78d2278 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -2680,12 +2680,28 @@ Templates which may be overridden per app or model Not every template in ``contrib/admin/templates/admin`` may be overridden per app or per model. The following can: +* ``actions.html`` * ``app_index.html`` * ``change_form.html`` +* ``change_form_object_tools.html`` * ``change_list.html`` +* ``change_list_object_tools.html`` +* ``change_list_results.html`` +* ``date_hierarchy.html`` * ``delete_confirmation.html`` * ``object_history.html`` +* ``pagination.html`` * ``popup_response.html`` +* ``prepopulated_fields_js.html`` +* ``search_form.html`` +* ``submit_line.html`` + +.. versionchanged:: 2.1 + + The ability to override the ``actions.html``, ``change_form_object_tools.html``, + ``change_list_object_tools.html``, ``change_list_results.html``, + ``date_hierarchy.html``, ``pagination.html``, ``prepopulated_fields_js.html``, + ``search_form.html``, ``submit_line.html`` templates were added. For those templates that cannot be overridden in this way, you may still override them for your entire project. Just place the new version in your diff --git a/docs/releases/2.1.txt b/docs/releases/2.1.txt index 65b41b2eed..d6cb44caa1 100644 --- a/docs/releases/2.1.txt +++ b/docs/releases/2.1.txt @@ -52,6 +52,14 @@ Minor features * The new :meth:`.ModelAdmin.get_deleted_objects()` method allows customizing the deletion process of the delete view and the "delete selected" action. +* The ``actions.html``, ``change_list_results.html``, ``date_hierarchy.html``, + ``pagination.html``, ``prepopulated_fields_js.html``, ``search_form.html`` + and ``submit_line.html`` templates can be overridden even per app or + per model, other than globally. + +* The admin change list and change form object tools can now be overridden per app, + per model or globally with ``change_list_object_tools.html`` and + ``change_form_object_tools.html`` templates. :mod:`django.contrib.admindocs` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index 5be70ee6ad..f37e3fa039 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -119,7 +119,7 @@ class ChangeListTests(TestCase): cl = m.get_changelist_instance(request) cl.formset = None template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') - context = Context({'cl': cl}) + context = Context({'cl': cl, 'opts': Child._meta}) table_output = template.render(context) link = reverse('admin:admin_changelist_child_change', args=(new_child.id,)) row_html = build_tbody_html(new_child.id, link, '<td class="field-parent nowrap">-</td>') @@ -137,7 +137,7 @@ class ChangeListTests(TestCase): cl = m.get_changelist_instance(request) cl.formset = None template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') - context = Context({'cl': cl}) + context = Context({'cl': cl, 'opts': Child._meta}) table_output = template.render(context) link = reverse('admin:admin_changelist_child_change', args=(new_child.id,)) row_html = build_tbody_html(new_child.id, link, '<td class="field-parent nowrap">???</td>') @@ -153,7 +153,7 @@ class ChangeListTests(TestCase): cl = m.get_changelist_instance(request) cl.formset = None template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') - context = Context({'cl': cl}) + context = Context({'cl': cl, 'opts': Child._meta}) table_output = template.render(context) link = reverse('admin:admin_changelist_child_change', args=(new_child.id,)) row_html = build_tbody_html( @@ -176,7 +176,7 @@ class ChangeListTests(TestCase): cl = m.get_changelist_instance(request) cl.formset = None template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') - context = Context({'cl': cl}) + context = Context({'cl': cl, 'opts': Child._meta}) table_output = template.render(context) link = reverse('admin:admin_changelist_child_change', args=(new_child.id,)) row_html = build_tbody_html(new_child.id, link, '<td class="field-parent nowrap">%s</td>' % new_parent) @@ -204,7 +204,7 @@ class ChangeListTests(TestCase): FormSet = m.get_changelist_formset(request) cl.formset = FormSet(queryset=cl.result_list) template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') - context = Context({'cl': cl}) + context = Context({'cl': cl, 'opts': Child._meta}) table_output = template.render(context) # make sure that hidden fields are in the correct place hiddenfields_div = ( diff --git a/tests/admin_views/templates/admin/admin_views/article/actions.html b/tests/admin_views/templates/admin/admin_views/article/actions.html new file mode 100644 index 0000000000..9aa238fd2a --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/actions.html @@ -0,0 +1,6 @@ +{% extends "admin/actions.html" %} +{% load i18n %} + +{% block actions-submit %} +<button type="submit" class="button override-actions" title="{% trans "Run the selected action" %}" name="index" value="{{ action_index|default:0 }}">{% trans "Go" %}</button> +{% endblock %} diff --git a/tests/admin_views/templates/admin/admin_views/article/change_form_object_tools.html b/tests/admin_views/templates/admin/admin_views/article/change_form_object_tools.html new file mode 100644 index 0000000000..609974bb95 --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/change_form_object_tools.html @@ -0,0 +1,7 @@ +{% extends "admin/change_form_object_tools.html" %} +{% load i18n admin_urls %} + +{% block object-tools-items %} +<li><a href="#" id="change-form-export" class="override-change_form_object_tools change-form-object-tools-item">{% trans "Export" %}</a></li> +{{ block.super }} +{% endblock %} diff --git a/tests/admin_views/templates/admin/admin_views/article/change_list_object_tools.html b/tests/admin_views/templates/admin/admin_views/article/change_list_object_tools.html new file mode 100644 index 0000000000..1a9be91952 --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/change_list_object_tools.html @@ -0,0 +1,7 @@ +{% extends "admin/change_list_object_tools.html" %} +{% load i18n admin_urls %} + +{% block object-tools-items %} +<li><a href="#" id="change-list-export" class="override-change_list_object_tools change-list-object-tools-item">{% trans "Export" %}</a></li> +{{ block.super }} +{% endblock %} diff --git a/tests/admin_views/templates/admin/admin_views/article/change_list_results.html b/tests/admin_views/templates/admin/admin_views/article/change_list_results.html new file mode 100644 index 0000000000..ceb581f8ef --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/change_list_results.html @@ -0,0 +1,38 @@ +{% load i18n static %} +{% if result_hidden_fields %} +<div class="hiddenfields">{# DIV for HTML validation #} +{% for item in result_hidden_fields %}{{ item }}{% endfor %} +</div> +{% endif %} +{% if results %} +<div class="results override-change_list_results"> +<table id="result_list"> +<thead> +<tr> +{% for header in result_headers %} +<th scope="col" {{ header.class_attrib }}> + {% if header.sortable %} + {% if header.sort_priority > 0 %} + <div class="sortoptions"> + <a class="sortremove" href="{{ header.url_remove }}" title="{% trans "Remove from sorting" %}"></a> + {% if num_sorted_fields > 1 %}<span class="sortpriority" title="{% blocktrans with priority_number=header.sort_priority %}Sorting priority: {{ priority_number }}{% endblocktrans %}">{{ header.sort_priority }}</span>{% endif %} + <a href="{{ header.url_toggle }}" class="toggle {% if header.ascending %}ascending{% else %}descending{% endif %}" title="{% trans "Toggle sorting" %}"></a> + </div> + {% endif %} + {% endif %} + <div class="text">{% if header.sortable %}<a href="{{ header.url_primary }}">{{ header.text|capfirst }}</a>{% else %}<span>{{ header.text|capfirst }}</span>{% endif %}</div> + <div class="clear"></div> +</th>{% endfor %} +</tr> +</thead> +<tbody> +{% for result in results %} +{% if result.form.non_field_errors %} + <tr><td colspan="{{ result|length }}">{{ result.form.non_field_errors }}</td></tr> +{% endif %} +<tr class="{% cycle 'row1' 'row2' %}">{% for item in result %}{{ item }}{% endfor %}</tr> +{% endfor %} +</tbody> +</table> +</div> +{% endif %} diff --git a/tests/admin_views/templates/admin/admin_views/article/date_hierarchy.html b/tests/admin_views/templates/admin/admin_views/article/date_hierarchy.html new file mode 100644 index 0000000000..de1cb747b5 --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/date_hierarchy.html @@ -0,0 +1,9 @@ +{% extends "admin/date_hierarchy.html" %} +{% load i18n %} + +{% block date-hierarchy-choices %} +<select id="date-selector" class="override-date_hierarchy">{% for choice in choices %} + <option{% if choice.link %} value="{{ choice.link }}"{% endif %}>{{ choice.title }}</option> +{% endfor %}</select> +<button id="date-selected">{% trans "Go" %}</button> +{% endblock %} diff --git a/tests/admin_views/templates/admin/admin_views/article/pagination.html b/tests/admin_views/templates/admin/admin_views/article/pagination.html new file mode 100644 index 0000000000..e072cacd3c --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/pagination.html @@ -0,0 +1,12 @@ +{% load admin_list %} +{% load i18n %} +<p class="paginator override-pagination"> +{% if pagination_required %} +{% for i in page_range %} + {% paginator_number cl i %} +{% endfor %} +{% endif %} +{{ cl.result_count }} {% if cl.result_count == 1 %}{{ cl.opts.verbose_name }}{% else %}{{ cl.opts.verbose_name_plural }}{% endif %} +{% if show_all_url %} <a href="{{ show_all_url }}" class="showall">{% trans 'Show all' %}</a>{% endif %} +{% if cl.formset and cl.result_count %}<input type="submit" name="_save" class="default" value="{% trans 'Save' %}"/>{% endif %} +</p> diff --git a/tests/admin_views/templates/admin/admin_views/article/prepopulated_fields_js.html b/tests/admin_views/templates/admin/admin_views/article/prepopulated_fields_js.html new file mode 100644 index 0000000000..0ee8c7a06c --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/prepopulated_fields_js.html @@ -0,0 +1,7 @@ +{% load l10n static %} +<script type="text/javascript" + id="django-admin-prepopulated-fields-constants" + class="override-prepopulated_fields_js" + src="{% static "admin/js/prepopulate_init.js" %}" + data-prepopulated-fields="{{ prepopulated_fields_json }}"> +</script> diff --git a/tests/admin_views/templates/admin/admin_views/article/search_form.html b/tests/admin_views/templates/admin/admin_views/article/search_form.html new file mode 100644 index 0000000000..5b5e6a58f6 --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/search_form.html @@ -0,0 +1,16 @@ +{% load i18n static %} +{% if cl.search_fields %} +<div id="toolbar" class="override-search_form"><form id="changelist-search" method="get"> +<div><!-- DIV needed for valid HTML --> +<label for="searchbar"><img src="{% static "admin/img/search.svg" %}" alt="Search" /></label> +<input type="text" size="40" name="{{ search_var }}" value="{{ cl.query }}" id="searchbar" autofocus /> +<input type="submit" value="{% trans 'Search' %}" /> +{% if show_result_count %} + <span class="small quiet">{% blocktrans count counter=cl.result_count %}{{ counter }} result{% plural %}{{ counter }} results{% endblocktrans %} (<a href="?{% if cl.is_popup %}_popup=1{% endif %}">{% if cl.show_full_result_count %}{% blocktrans with full_result_count=cl.full_result_count %}{{ full_result_count }} total{% endblocktrans %}{% else %}{% trans "Show all" %}{% endif %}</a>)</span> +{% endif %} +{% for pair in cl.params.items %} + {% if pair.0 != search_var %}<input type="hidden" name="{{ pair.0 }}" value="{{ pair.1 }}"/>{% endif %} +{% endfor %} +</div> +</form></div> +{% endif %} diff --git a/tests/admin_views/templates/admin/admin_views/article/submit_line.html b/tests/admin_views/templates/admin/admin_views/article/submit_line.html new file mode 100644 index 0000000000..4a2ca08890 --- /dev/null +++ b/tests/admin_views/templates/admin/admin_views/article/submit_line.html @@ -0,0 +1,7 @@ +{% extends "admin/submit_line.html" %} +{% load i18n admin_urls %} + +{% block submit-row %} +{% if show_publish %}<input type="submit" value="{% trans 'Publish' %}" class="default" name="_publish" />{% endif %} +{{ block.super }} +{% endblock %} diff --git a/tests/admin_views/test_templatetags.py b/tests/admin_views/test_templatetags.py index 44a08f32cd..db78636d5d 100644 --- a/tests/admin_views/test_templatetags.py +++ b/tests/admin_views/test_templatetags.py @@ -7,9 +7,10 @@ from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User from django.test import RequestFactory, TestCase from django.urls import reverse +from django.utils.encoding import force_text -from .admin import site -from .models import Question +from .admin import ArticleAdmin, site +from .models import Article, Question from .tests import AdminViewBasicTestCase @@ -28,6 +29,46 @@ class AdminTemplateTagsTest(AdminViewBasicTestCase): self.assertIs(template_context['extra'], True) self.assertIs(template_context['show_save'], True) + def test_can_override_change_form_templatetags(self): + """ + admin_modify templatetags can follow the 'standard' search patter admin/app_label/model/template.html + """ + factory = RequestFactory() + article = Article.objects.all()[0] + request = factory.get(reverse('admin:admin_views_article_change', args=[article.pk])) + request.user = self.superuser + admin = ArticleAdmin(Article, site) + extra_context = {'show_publish': True, 'extra': True} + response = admin.change_view(request, str(article.pk), extra_context=extra_context) + response.render() + self.assertIs(response.context_data['show_publish'], True) + self.assertIs(response.context_data['extra'], True) + content = force_text(response.content) + self.assertIs('name="_save"' in content, True) + self.assertIs('name="_publish"' in content, True) + self.assertIs('override-change_form_object_tools' in content, True) + self.assertIs('override-prepopulated_fields_js' in content, True) + + def test_can_override_change_list_templatetags(self): + """ + admin_list templatetags can follow the 'standard' search patter admin/app_label/model/template.html + """ + factory = RequestFactory() + request = factory.get(reverse('admin:admin_views_article_changelist')) + request.user = self.superuser + admin = ArticleAdmin(Article, site) + admin.date_hierarchy = 'date' + admin.search_fields = ('title', 'content',) + response = admin.changelist_view(request) + response.render() + content = force_text(response.content) + self.assertIs('override-actions' in content, True) + self.assertIs('override-change_list_object_tools' in content, True) + self.assertIs('override-change_list_results' in content, True) + self.assertIs('override-date_hierarchy' in content, True) + self.assertIs('override-pagination' in content, True) + self.assertIs('override-search_form' in content, True) + class DateHierarchyTests(TestCase): factory = RequestFactory()