Ticket #10505: admin-actions.diff
File admin-actions.diff, 34.7 KB (added by , 16 years ago) |
---|
-
django/contrib/admin/helpers.py
diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index aaa2e30..aac8911 100644
a b from django.utils.safestring import mark_safe 6 6 from django.utils.encoding import force_unicode 7 7 from django.contrib.admin.util import flatten_fieldsets 8 8 from django.contrib.contenttypes.models import ContentType 9 from django.utils.translation import ugettext_lazy as _ 10 11 ACTION_CHECKBOX_NAME = 'selected' 12 13 class ActionForm(forms.Form): 14 action = forms.ChoiceField(label=_('Action:')) 15 16 checkbox = forms.CheckboxInput({'class': 'action-select'}, lambda value: False) 9 17 10 18 class AdminForm(object): 11 19 def __init__(self, form, fieldsets, prepopulated_fields): -
django/contrib/admin/media/css/changelists.css
diff --git a/django/contrib/admin/media/css/changelists.css b/django/contrib/admin/media/css/changelists.css index 40142f5..649cff7 100644
a b 50 50 51 51 #changelist table thead th { 52 52 white-space: nowrap; 53 vertical-align: middle; 54 } 55 56 #changelist table thead th:first-child { 57 width: 1.5em; 58 text-align: center; 53 59 } 54 60 55 61 #changelist table tbody td { 56 62 border-left: 1px solid #ddd; 57 63 } 58 64 65 #changelist table tbody td:first-child { 66 border-left: 0; 67 border-right: 1px solid #ddd; 68 text-align: center; 69 } 70 59 71 #changelist table tfoot { 60 72 color: #666; 61 73 } … … 209 221 border-color: #036; 210 222 } 211 223 224 /* ACTIONS */ 225 226 .filtered .actions { 227 margin-right: 160px !important; 228 border-right: 1px solid #ddd; 229 } 230 231 #changelist .actions { 232 color: #666; 233 padding: 3px; 234 border-bottom: 1px solid #ddd; 235 background: #e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; 236 } 237 238 #changelist .actions:last-child { 239 border-bottom: none; 240 } 241 242 #changelist .actions select { 243 border: 1px solid #aaa; 244 margin: 0 0.5em; 245 padding: 1px 2px; 246 } 247 248 #changelist .actions label { 249 font-size: 11px; 250 margin: 0 0.5em; 251 } 252 253 #changelist #action-toggle { 254 display: none; 255 } -
new file django/contrib/admin/media/js/actions.js
diff --git a/django/contrib/admin/media/js/actions.js b/django/contrib/admin/media/js/actions.js new file mode 100644 index 0000000..febb0c1
- + 1 var Actions = { 2 init: function() { 3 selectAll = document.getElementById('action-toggle'); 4 if (selectAll) { 5 selectAll.style.display = 'inline'; 6 addEvent(selectAll, 'change', function() { 7 Actions.checker(this.checked); 8 }); 9 } 10 }, 11 checker: function(checked) { 12 actionCheckboxes = document.getElementsBySelector('tr input.action-select'); 13 for(var i = 0; i < actionCheckboxes.length; i++) { 14 actionCheckboxes[i].checked = checked; 15 } 16 } 17 } 18 19 addEvent(window, 'load', Actions.init); -
django/contrib/admin/options.py
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 69f52aa..096960d 100644
a b from django.forms.models import BaseInlineFormSet 5 5 from django.contrib.contenttypes.models import ContentType 6 6 from django.contrib.admin import widgets 7 7 from django.contrib.admin import helpers 8 from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects 8 from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict 9 9 from django.core.exceptions import PermissionDenied 10 10 from django.db import models, transaction 11 from django.db.models.fields import BLANK_CHOICE_DASH 11 12 from django.http import Http404, HttpResponse, HttpResponseRedirect 12 13 from django.shortcuts import get_object_or_404, render_to_response 13 14 from django.utils.functional import update_wrapper … … from django.utils.safestring import mark_safe 16 17 from django.utils.functional import curry 17 18 from django.utils.text import capfirst, get_text_list 18 19 from django.utils.translation import ugettext as _ 19 from django.utils.translation import ngettext 20 from django.utils.translation import ngettext, ugettext_lazy 20 21 from django.utils.encoding import force_unicode 21 22 try: 22 23 set … … class ModelAdmin(BaseModelAdmin): 173 174 "Encapsulates all admin options and functionality for a given model." 174 175 __metaclass__ = forms.MediaDefiningClass 175 176 176 list_display = (' __str__',)177 list_display = ('action_checkbox', '__str__',) 177 178 list_display_links = () 178 179 list_filter = () 179 180 list_select_related = False … … class ModelAdmin(BaseModelAdmin): 192 193 delete_confirmation_template = None 193 194 object_history_template = None 194 195 196 # Actions 197 actions = ['delete_selected'] 198 action_form = helpers.ActionForm 199 actions_on_top = False 200 actions_on_bottom = True 201 195 202 def __init__(self, model, admin_site): 196 203 self.model = model 197 204 self.opts = model._meta … … class ModelAdmin(BaseModelAdmin): 200 207 for inline_class in self.inlines: 201 208 inline_instance = inline_class(self.model, self.admin_site) 202 209 self.inline_instances.append(inline_instance) 210 if 'action_checkbox' not in self.list_display: 211 self.list_display = list(self.list_display) 212 self.list_display.insert(0, 'action_checkbox') 213 if not self.list_display_links: 214 for name in self.list_display: 215 if name != 'action_checkbox': 216 self.list_display_links = [name] 217 break 203 218 super(ModelAdmin, self).__init__() 204 219 205 220 def get_urls(self): … … class ModelAdmin(BaseModelAdmin): 239 254 from django.conf import settings 240 255 241 256 js = ['js/core.js', 'js/admin/RelatedObjectLookups.js'] 257 if self.actions: 258 js.extend(['js/getElementsBySelector.js', 'js/actions.js']) 242 259 if self.prepopulated_fields: 243 260 js.append('js/urlify.js') 244 261 if self.opts.get_ordered_objects(): … … class ModelAdmin(BaseModelAdmin): 390 407 action_flag = DELETION 391 408 ) 392 409 410 def action_checkbox(self, obj): 411 """ 412 A list_display column containing a checkbox widget. 413 """ 414 return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, force_unicode(obj.pk)) 415 action_checkbox.short_description = mark_safe('<input type="checkbox" id="action-toggle" />') 416 action_checkbox.allow_tags = True 417 418 419 def get_actions(self, request=None): 420 actions = {} 421 for klass in self.__class__.mro(): 422 for action in getattr(klass, 'actions', []): 423 func, name, description = self.get_action(action) 424 actions[name] = (func, name, description) 425 return actions 426 427 def get_action_choices(self, request=None, default_choices=BLANK_CHOICE_DASH): 428 choices = [] + default_choices 429 for func, name, description in self.get_actions(request).itervalues(): 430 choice = (name, description % model_format_dict(self.opts)) 431 choices.append(choice) 432 return choices 433 434 def get_action(self, action): 435 if callable(action): 436 func = action 437 action = action.__name__ 438 elif hasattr(self, action): 439 func = getattr(self, action) 440 if hasattr(func, 'short_description'): 441 description = func.short_description 442 else: 443 description = capfirst(action.replace('_', ' ')) 444 return func, action, description 445 446 def delete_selected(self, request, selected): 447 """ 448 Default action which deletes the selected objects. 449 """ 450 opts = self.model._meta 451 app_label = opts.app_label 452 453 if not self.has_delete_permission(request): 454 raise PermissionDenied 455 456 # Populate deleted_objects, a data structure of all related objects that 457 # will also be deleted. 458 459 # deleted_objects must be a list if we want to use '|unordered_list' in the template 460 deleted_objects = [] 461 perms_needed = set() 462 i = 0 463 for obj in selected: 464 deleted_objects.append([mark_safe(u'%s: <a href="%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), obj.pk, escape(obj))), []]) 465 get_deleted_objects(deleted_objects[i], perms_needed, request.user, obj, opts, 1, self.admin_site, levels_to_root=2) 466 i=i+1 467 468 # The user has already confirmed the deletion. 469 if request.POST.get('post'): 470 if perms_needed: 471 raise PermissionDenied 472 n = selected.count() 473 if n: 474 for obj in selected: 475 obj_display = force_unicode(obj) 476 self.log_deletion(request, obj, obj_display) 477 selected.delete() 478 self.message_user(request, _("Successfully deleted %d %s.") % ( 479 n, model_ngettext(self.opts, n) 480 )) 481 return None 482 483 context = { 484 "title": _("Are you sure?"), 485 "object_name": force_unicode(opts.verbose_name), 486 "deleted_objects": deleted_objects, 487 'selected': selected, 488 "perms_lacking": perms_needed, 489 "opts": opts, 490 "root_path": self.admin_site.root_path, 491 "app_label": app_label, 492 } 493 return render_to_response(self.delete_confirmation_template or [ 494 "admin/%s/%s/delete_selected_confirmation.html" % (app_label, opts.object_name.lower()), 495 "admin/%s/delete_selected_confirmation.html" % app_label, 496 "admin/delete_selected_confirmation.html" 497 ], context, context_instance=template.RequestContext(request)) 498 499 delete_selected.short_description = ugettext_lazy("Delete selected %(verbose_name_plural)s") 393 500 394 501 def construct_change_message(self, request, form, formsets): 395 502 """ … … class ModelAdmin(BaseModelAdmin): 529 636 self.message_user(request, msg) 530 637 return HttpResponseRedirect("../") 531 638 639 def response_action(self, request, queryset): 640 if request.method == 'POST': 641 # There can be multiple action forms on the page (at the top 642 # and bottom of the change list, for example). Get the action 643 # whose button was pushed. 644 try: 645 action_index = int(request.POST.get('index', 0)) 646 except ValueError: 647 action_index = 0 648 data = {} 649 for key in request.POST: 650 if key not in (helpers.ACTION_CHECKBOX_NAME, 'index'): 651 data[key] = request.POST.getlist(key)[action_index] 652 action_form = self.action_form(data, auto_id=None) 653 action_form.fields['action'].choices = self.get_action_choices(request) 654 655 if action_form.is_valid(): 656 action = action_form.cleaned_data['action'] 657 func, name, description = self.get_actions(request)[action] 658 selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME) 659 results = queryset.filter(pk__in=selected) 660 response = None 661 if callable(func): 662 response = func(request, results) 663 if isinstance(response, HttpResponse): 664 return response 665 else: 666 redirect_to = request.META.get('HTTP_REFERER') or "." 667 return HttpResponseRedirect(redirect_to) 668 else: 669 action_form = self.action_form(auto_id=None) 670 action_form.fields['action'].choices = self.get_action_choices(request) 671 return action_form 672 532 673 def add_view(self, request, form_url='', extra_context=None): 533 674 "The 'add' admin view for this model." 534 675 model = self.model … … class ModelAdmin(BaseModelAdmin): 721 862 return render_to_response('admin/invalid_setup.html', {'title': _('Database error')}) 722 863 return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1') 723 864 865 action_form_or_response = self.response_action(request, queryset=cl.get_query_set()) 866 if isinstance(action_form_or_response, HttpResponse): 867 return action_form_or_response 868 724 869 # If we're allowing changelist editing, we need to construct a formset 725 870 # for the changelist given all the fields to be edited. Then we'll 726 871 # use the formset to validate/process POSTed data. … … class ModelAdmin(BaseModelAdmin): 764 909 if formset: 765 910 media = self.media + formset.media 766 911 else: 767 media = None912 media = self.media 768 913 769 914 context = { 770 915 'title': cl.title, … … class ModelAdmin(BaseModelAdmin): 774 919 'has_add_permission': self.has_add_permission(request), 775 920 'root_path': self.admin_site.root_path, 776 921 'app_label': app_label, 922 'action_form': action_form_or_response, 923 'actions_on_top': self.actions_on_top, 924 'actions_on_bottom': self.actions_on_bottom, 777 925 } 778 926 context.update(extra_context or {}) 779 927 return render_to_response(self.change_list_template or [ -
django/contrib/admin/sites.py
diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index 5171e71..ebcf886 100644
a b class AdminSite(object): 44 44 else: 45 45 name += '_' 46 46 self.name = name 47 48 self.actions = [] 47 49 48 50 def register(self, model_or_iterable, admin_class=None, **options): 49 51 """ … … class AdminSite(object): 81 83 options['__module__'] = __name__ 82 84 admin_class = type("%sAdmin" % model.__name__, (admin_class,), options) 83 85 86 for action in self.actions: 87 admin_class.actions.append(action) 88 84 89 # Validate (which might be a no-op) 85 90 validate(admin_class, model) 86 91 … … class AdminSite(object): 100 105 raise NotRegistered('The model %s is not registered' % model.__name__) 101 106 del self._registry[model] 102 107 108 def add_action(self, action): 109 if not callable(action): 110 raise TypeError("You can only register callable actions through an admin site") 111 self.actions.append(action) 112 for klass in self._registery.itervalues(): 113 klass.actions.append(action) 114 103 115 def has_permission(self, request): 104 116 """ 105 117 Returns True if the given HttpRequest has permission to view -
new file django/contrib/admin/templates/admin/actions.html
diff --git a/django/contrib/admin/templates/admin/actions.html b/django/contrib/admin/templates/admin/actions.html new file mode 100644 index 0000000..bf4b975
- + 1 {% load i18n %} 2 <div class="actions"> 3 {% for field in action_form %}<label>{{ field.label }} {{ field }}</label>{% endfor %} 4 <button type="submit" class="button" title="{% trans "Run the selected action" %}" name="index" value="{{ action_index|default:0 }}">{% trans "Go" %}</button> 5 </div> -
django/contrib/admin/templates/admin/change_list.html
diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html index dca5b80..63254b8 100644
a b 7 7 {% if cl.formset %} 8 8 <link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" /> 9 9 <script type="text/javascript" src="../../jsi18n/"></script> 10 {{ media }}11 10 {% endif %} 11 {{ media }} 12 12 {% endblock %} 13 13 14 14 {% block bodyclass %}change-list{% endblock %} … … 63 63 {% endif %} 64 64 {% endblock %} 65 65 66 <form action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}> 66 67 {% if cl.formset %} 67 <form action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}>68 68 {{ cl.formset.management_form }} 69 69 {% endif %} 70 70 71 {% block result_list %}{% result_list cl %}{% endblock %} 71 {% block result_list %} 72 {% if actions_on_top and cl.full_result_count %}{% admin_actions %}{% endif %} 73 {% result_list cl %} 74 {% if actions_on_bottom and cl.full_result_count %}{% admin_actions %}{% endif %} 75 {% endblock %} 72 76 {% block pagination %}{% pagination cl %}{% endblock %} 73 {% if cl.formset %}</form>{% endif %}77 </form> 74 78 </div> 75 79 </div> 76 80 {% endblock %} -
new file django/contrib/admin/templates/admin/delete_selected_confirmation.html
diff --git a/django/contrib/admin/templates/admin/delete_selected_confirmation.html b/django/contrib/admin/templates/admin/delete_selected_confirmation.html new file mode 100644 index 0000000..183134d
- + 1 {% extends "admin/base_site.html" %} 2 {% load i18n %} 3 4 {% block breadcrumbs %} 5 <div class="breadcrumbs"> 6 <a href="../../">{% trans "Home" %}</a> › 7 <a href="../">{{ app_label|capfirst }}</a> › 8 <a href="./">{{ opts.verbose_name_plural|capfirst }}</a> › 9 {% trans 'Delete multiple objects' %} 10 </div> 11 {% endblock %} 12 13 {% block content %} 14 {% if perms_lacking %} 15 <p>{% blocktrans %}Deleting the {{ object_name }} would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}</p> 16 <ul> 17 {% for obj in perms_lacking %} 18 <li>{{ obj }}</li> 19 {% endfor %} 20 </ul> 21 {% else %} 22 <p>{% blocktrans %}Are you sure you want to delete the selected {{ object_name }} objects? All of the following objects and it's related items will be deleted:{% endblocktrans %}</p> 23 {% for d in deleted_objects %} 24 <ul>{{ d|unordered_list }}</ul> 25 {% endfor %} 26 <form action="" method="post"> 27 <div> 28 {% for s in selected %} 29 <input type="hidden" name="selected" value="{{ s.pk }}" /> 30 {% endfor %} 31 <input type="hidden" name="action" value="delete_selected" /> 32 <input type="hidden" name="post" value="yes" /> 33 <input type="submit" value="{% trans "Yes, I'm sure" %}" /> 34 </div> 35 </form> 36 {% endif %} 37 {% endblock %} 38 No newline at end of file -
django/contrib/admin/templatetags/admin_list.py
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 063ef0e..a374bf5 100644
a b search_form = register.inclusion_tag('admin/search_form.html')(search_form) 325 325 def admin_list_filter(cl, spec): 326 326 return {'title': spec.title(), 'choices' : list(spec.choices(cl))} 327 327 admin_list_filter = register.inclusion_tag('admin/filter.html')(admin_list_filter) 328 329 def admin_actions(context): 330 """ 331 Track the number of times the action field has been rendered on the page, 332 so we know which value to use. 333 """ 334 context['action_index'] = context.get('action_index', -1) + 1 335 return context 336 admin_actions = register.inclusion_tag("admin/actions.html", takes_context=True)(admin_actions) -
django/contrib/admin/util.py
diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index 4164c8a..38f86e3 100644
a b from django.utils.html import escape 4 4 from django.utils.safestring import mark_safe 5 5 from django.utils.text import capfirst 6 6 from django.utils.encoding import force_unicode 7 from django.utils.translation import ugettext as _ 7 from django.utils.translation import ungettext, ugettext as _ 8 from django.core.urlresolvers import reverse, NoReverseMatch 8 9 9 10 def quote(s): 10 11 """ … … def _nest_help(obj, depth, val): 60 61 current = current[-1] 61 62 current.append(val) 62 63 63 def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_depth, admin_site): 64 "Helper function that recursively populates deleted_objects." 64 def get_change_view_url(app_label, module_name, pk, admin_site, levels_to_root): 65 """ 66 Returns the url to the admin change view for the given app_label, 67 module_name and primary key. 68 """ 69 try: 70 return reverse('%sadmin_%s_%s_change' % (admin_site.name, app_label, module_name), None, (pk,)) 71 except NoReverseMatch: 72 return '%s%s/%s/%s/' % ('../'*levels_to_root, app_label, module_name, pk) 73 74 def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_depth, admin_site, levels_to_root=4): 75 """ 76 Helper function that recursively populates deleted_objects. 77 78 `levels_to_root` defines the number of directories (../) to reach the 79 admin root path. In a change_view this is 4, in a change_list view 2. 80 81 This is for backwards compatibility since the options.delete_selected 82 method uses this function also from a change_list view. 83 This will not be used if we can reverse the URL. 84 """ 65 85 nh = _nest_help # Bind to local variable for performance 66 86 if current_depth > 16: 67 87 return # Avoid recursing too deep. … … def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_ 91 111 [u'%s: %s' % (capfirst(related.opts.verbose_name), force_unicode(sub_obj)), []]) 92 112 else: 93 113 # Display a link to the admin page. 94 nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href=" ../../../../%s/%s/%s/">%s</a>' %114 nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="%s">%s</a>' % 95 115 (escape(capfirst(related.opts.verbose_name)), 96 related.opts.app_label, 97 related.opts.object_name.lower(), 98 sub_obj._get_pk_val(), 116 get_change_view_url(related.opts.app_label, 117 related.opts.object_name.lower(), 118 sub_obj._get_pk_val(), 119 admin_site, 120 levels_to_root), 99 121 escape(sub_obj))), []]) 100 122 get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2, admin_site) 101 123 else: … … def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_ 109 131 [u'%s: %s' % (capfirst(related.opts.verbose_name), force_unicode(sub_obj)), []]) 110 132 else: 111 133 # Display a link to the admin page. 112 nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href=" ../../../../%s/%s/%s/">%s</a>' %134 nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="%s">%s</a>' % 113 135 (escape(capfirst(related.opts.verbose_name)), 114 related.opts.app_label, 115 related.opts.object_name.lower(), 116 sub_obj._get_pk_val(), 136 get_change_view_url(related.opts.app_label, 137 related.opts.object_name.lower(), 138 sub_obj._get_pk_val(), 139 admin_site, 140 levels_to_root), 117 141 escape(sub_obj))), []]) 118 142 get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2, admin_site) 119 143 # If there were related objects, and the user doesn't have … … def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_ 147 171 # Display a link to the admin page. 148 172 nh(deleted_objects, current_depth, [ 149 173 mark_safe((_('One or more %(fieldname)s in %(name)s:') % {'fieldname': escape(force_unicode(related.field.verbose_name)), 'name': escape(force_unicode(related.opts.verbose_name))}) + \ 150 (u' <a href="../../../../%s/%s/%s/">%s</a>' % \ 151 (related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(sub_obj)))), []]) 174 (u' <a href="%s">%s</a>' % \ 175 (get_change_view_url(related.opts.app_label, 176 related.opts.object_name.lower(), 177 sub_obj._get_pk_val(), 178 admin_site, 179 levels_to_root), 180 escape(sub_obj)))), []]) 152 181 # If there were related objects, and the user doesn't have 153 182 # permission to change them, add the missing perm to perms_needed. 154 183 if has_admin and has_related_objs: 155 184 p = u'%s.%s' % (related.opts.app_label, related.opts.get_change_permission()) 156 185 if not user.has_perm(p): 157 186 perms_needed.add(related.opts.verbose_name) 187 188 def model_format_dict(obj): 189 """ 190 Return a `dict` with keys 'verbose_name' and 'verbose_name_plural', 191 typically for use with string formatting. 192 193 `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance. 194 195 """ 196 if isinstance(obj, (models.Model, models.base.ModelBase)): 197 opts = obj._meta 198 elif isinstance(obj, models.query.QuerySet): 199 opts = obj.model._meta 200 else: 201 opts = obj 202 return { 203 'verbose_name': force_unicode(opts.verbose_name), 204 'verbose_name_plural': force_unicode(opts.verbose_name_plural) 205 } 206 207 def model_ngettext(obj, n=None): 208 """ 209 Return the appropriate `verbose_name` or `verbose_name_plural` for `obj` 210 depending on the count `n`. 211 212 `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance. 213 If `obj` is a `QuerySet` instance, `n` is optional and the length of the 214 `QuerySet` is used. 215 216 """ 217 if isinstance(obj, models.query.QuerySet): 218 if n is None: 219 n = obj.count() 220 obj = obj.model 221 d = model_format_dict(obj) 222 return ungettext(d['verbose_name'], d['verbose_name_plural'], n or 0) -
tests/regressiontests/admin_registration/models.py
diff --git a/tests/regressiontests/admin_registration/models.py b/tests/regressiontests/admin_registration/models.py index fdfa369..35cf8af 100644
a b AlreadyRegistered: The model Person is already registered 49 49 >>> site._registry[Person].search_fields 50 50 ['name'] 51 51 >>> site._registry[Person].list_display 52 [' __str__']52 ['action_checkbox', '__str__'] 53 53 >>> site._registry[Person].save_on_top 54 54 True 55 55 -
new file tests/regressiontests/admin_views/fixtures/admin-views-actions.xml
diff --git a/tests/regressiontests/admin_views/fixtures/admin-views-actions.xml b/tests/regressiontests/admin_views/fixtures/admin-views-actions.xml new file mode 100644 index 0000000..316e750
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <django-objects version="1.0"> 3 <object pk="1" model="admin_views.subscriber"> 4 <field type="CharField" name="name">John Doe</field> 5 <field type="CharField" name="email">john@example.org</field> 6 </object> 7 <object pk="2" model="admin_views.subscriber"> 8 <field type="CharField" name="name">Max Mustermann</field> 9 <field type="CharField" name="email">max@example.org</field> 10 </object> 11 <object pk="1" model="admin_views.externalsubscriber"> 12 <field type="CharField" name="name">John Doe</field> 13 <field type="CharField" name="email">john@example.org</field> 14 </object> 15 </django-objects> -
tests/regressiontests/admin_views/models.py
diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py index eeaf039..e5e112f 100644
a b 1 1 # -*- coding: utf-8 -*- 2 2 from django.db import models 3 3 from django.contrib import admin 4 from django.core.mail import EmailMessage 4 5 5 6 class Section(models.Model): 6 7 """ … … class PersonaAdmin(admin.ModelAdmin): 199 200 BarAccountAdmin 200 201 ) 201 202 203 class Subscriber(models.Model): 204 name = models.CharField(blank=False, max_length=80) 205 email = models.EmailField(blank=False, max_length=175) 206 207 def __unicode__(self): 208 return "%s (%s)" % (self.name, self.email) 209 210 class SubscriberAdmin(admin.ModelAdmin): 211 actions = ['delete_selected', 'mail_admin'] 212 213 def mail_admin(self, request, selected): 214 EmailMessage( 215 'Greetings from a ModelAdmin action', 216 'This is the test email from a admin action', 217 'from@example.com', 218 ['to@example.com'] 219 ).send() 220 221 class ExternalSubscriber(Subscriber): 222 pass 223 224 def external_mail(request, selected): 225 EmailMessage( 226 'Greetings from a function action', 227 'This is the test email from a function action', 228 'from@example.com', 229 ['to@example.com'] 230 ).send() 231 232 def redirect_to(request, selected): 233 from django.http import HttpResponseRedirect 234 return HttpResponseRedirect('/some-where-else/') 235 236 class ExternalSubscriberAdmin(admin.ModelAdmin): 237 actions = [external_mail, redirect_to] 202 238 203 239 admin.site.register(Article, ArticleAdmin) 204 240 admin.site.register(CustomArticle, CustomArticleAdmin) … … admin.site.register(Color) 208 244 admin.site.register(Thing, ThingAdmin) 209 245 admin.site.register(Person, PersonAdmin) 210 246 admin.site.register(Persona, PersonaAdmin) 247 admin.site.register(Subscriber, SubscriberAdmin) 248 admin.site.register(ExternalSubscriber, ExternalSubscriberAdmin) 211 249 212 250 # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. 213 251 # That way we cover all four cases: -
tests/regressiontests/admin_views/tests.py
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 33000d4..49e0628 100644
a b from django.contrib.contenttypes.models import ContentType 8 8 from django.contrib.admin.models import LogEntry 9 9 from django.contrib.admin.sites import LOGIN_FORM_KEY 10 10 from django.contrib.admin.util import quote 11 from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME 11 12 from django.utils.html import escape 12 13 13 14 # local test models 14 from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey, Person, Persona, FooAccount, BarAccount 15 from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey, Person, Persona, FooAccount, BarAccount, Subscriber, ExternalSubscriber 15 16 16 17 try: 17 18 set … … class AdminViewStringPrimaryKeyTest(TestCase): 516 517 def test_changelist_to_changeform_link(self): 517 518 "The link from the changelist referring to the changeform of the object should be quoted" 518 519 response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/') 519 should_contain = """<t r class="row1"><th><a href="%s/">%s</a></th></tr>""" % (quote(self.pk), escape(self.pk))520 should_contain = """<th><a href="%s/">%s</a></th></tr>""" % (quote(self.pk), escape(self.pk)) 520 521 self.assertContains(response, should_contain) 521 522 522 523 def test_recentactions_link(self): … … class AdminViewListEditable(TestCase): 743 744 response = self.client.get('/test_admin/admin/admin_views/person/') 744 745 # 2 inputs per object(the field and the hidden id field) = 6 745 746 # 2 management hidden fields = 2 747 # 4 action inputs (3 regular checkboxes, 1 checkbox to select all) 746 748 # main form submit button = 1 747 749 # search field and search submit button = 2 748 750 # 6 + 2 + 1 + 2 = 11 inputs 749 self.failUnlessEqual(response.content.count("<input"), 1 1)751 self.failUnlessEqual(response.content.count("<input"), 15) 750 752 # 1 select per object = 3 selects 751 self.failUnlessEqual(response.content.count("<select"), 3)753 self.failUnlessEqual(response.content.count("<select"), 4) 752 754 753 755 def test_post_submission(self): 754 756 data = { … … class AdminInheritedInlinesTest(TestCase): 875 877 self.failUnlessEqual(FooAccount.objects.all()[0].username, "%s-1" % foo_user) 876 878 self.failUnlessEqual(BarAccount.objects.all()[0].username, "%s-1" % bar_user) 877 879 self.failUnlessEqual(Persona.objects.all()[0].accounts.count(), 2) 880 881 from django.core import mail 882 883 class AdminActionsTest(TestCase): 884 fixtures = ['admin-views-users.xml', 'admin-views-actions.xml'] 885 886 def setUp(self): 887 self.client.login(username='super', password='secret') 888 889 def tearDown(self): 890 self.client.logout() 891 892 def test_model_admin_custom_action(self): 893 "Tests a custom action defined in a ModelAdmin method" 894 action_data = { 895 ACTION_CHECKBOX_NAME: [1], 896 'action' : 'mail_admin', 897 'index': 0, 898 } 899 response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data) 900 self.assertEquals(len(mail.outbox), 1) 901 self.assertEquals(mail.outbox[0].subject, 'Greetings from a ModelAdmin action') 902 903 def test_model_admin_default_delete_action(self): 904 "Tests the default delete action defined as a ModelAdmin method" 905 action_data = { 906 ACTION_CHECKBOX_NAME: [1, 2], 907 'action' : 'delete_selected', 908 'index': 0, 909 } 910 delete_confirmation_data = { 911 ACTION_CHECKBOX_NAME: [1, 2], 912 'action' : 'delete_selected', 913 'index': 0, 914 'post': 'yes', 915 } 916 confirmation = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data) 917 self.assertContains(confirmation, "Are you sure you want to delete the selected subscriber objects") 918 response = self.client.post('/test_admin/admin/admin_views/subscriber/', delete_confirmation_data) 919 self.failUnlessEqual(Subscriber.objects.count(), 0) 920 921 def test_custom_function_mail_action(self): 922 "Tests a custom action defined in a function" 923 action_data = { 924 ACTION_CHECKBOX_NAME: [1], 925 'action' : 'external_mail', 926 'index': 0, 927 } 928 response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data) 929 self.assertEquals(len(mail.outbox), 1) 930 self.assertEquals(mail.outbox[0].subject, 'Greetings from a function action') 931 932 def test_custom_function_action_with_redirect(self): 933 "Tests a custom action defined in a function" 934 action_data = { 935 ACTION_CHECKBOX_NAME: [1], 936 'action' : 'redirect_to', 937 'index': 0, 938 } 939 response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data) 940 self.failUnlessEqual(response.status_code, 302)