Ticket #10505: admin-actions.6.diff
File admin-actions.6.diff, 29.9 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 c79523c..ef2c52d 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 def get_action_choices(self, default_choices=BLANK_CHOICE_DASH): 419 choices = [] + default_choices 420 for action in getattr(self, 'actions', []): 421 func, name, description, instance_action = self.get_action(action) 422 choice = (name, description % model_format_dict(self.opts)) 423 choices.append(choice) 424 return choices 425 426 def get_action(self, action): 427 is_instance_action = False 428 if callable(action): 429 func = action 430 action = action.__name__ 431 elif hasattr(self, action): 432 func = getattr(self, action) 433 elif hasattr(self.model, action): 434 func = getattr(self.model, action) 435 is_instance_action = True 436 else: 437 callable_actions = {} 438 for item in getattr(self, 'actions', []): 439 if callable(item): 440 callable_actions[item.__name__] = item 441 if action in callable_actions: 442 return self.get_action(callable_actions[action]) 443 raise AttributeError, \ 444 "'%s' model or '%s' have no action '%s'" % \ 445 (self.opts.object_name, self.__class__.__name__, action) 446 if hasattr(func, 'short_description'): 447 description = func.short_description 448 else: 449 description = capfirst(action.replace('_', ' ')) 450 return func, action, description, is_instance_action 451 452 def delete_selected(self, request, selected): 453 """ 454 Default action which deletes the selected objects. 455 """ 456 opts = self.model._meta 457 app_label = opts.app_label 458 459 if not self.has_delete_permission(request): 460 raise PermissionDenied 461 462 # Populate deleted_objects, a data structure of all related objects that 463 # will also be deleted. 464 465 # deleted_objects must be a list if we want to use '|unordered_list' in the template 466 deleted_objects = [] 467 for obj in selected: 468 deleted_objects.append([mark_safe(u'%s: <a href="%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), obj.pk, escape(obj))), []]) 469 perms_needed = set() 470 i = 0 471 for d in deleted_objects: 472 # FIXME: the urlpath to the detail-view of the related objects are hardcoded as "../../../../" 473 # which is wrong from this changelist_view. Is there a admin-reverse-urlconf refactor? 474 get_deleted_objects(deleted_objects[i], perms_needed, request.user, selected[i], opts, 1, self.admin_site) 475 i=i+1 476 477 # The user has already confirmed the deletion. 478 if request.POST.get('post'): 479 if perms_needed: 480 raise PermissionDenied 481 n = selected.count() 482 if n: 483 for obj in selected: 484 obj_display = force_unicode(obj) 485 self.log_deletion(request, obj, obj_display) 486 selected.delete() 487 self.message_user(request, _("Successfully deleted %d %s.") % ( 488 n, model_ngettext(self.opts, n) 489 )) 490 return None 491 492 context = { 493 "title": _("Are you sure?"), 494 "object_name": force_unicode(opts.verbose_name), 495 "deleted_objects": deleted_objects, 496 'selected': selected, 497 "perms_lacking": perms_needed, 498 "opts": opts, 499 "root_path": self.admin_site.root_path, 500 "app_label": app_label, 501 } 502 return render_to_response(self.delete_confirmation_template or [ 503 "admin/%s/%s/delete_selected_confirmation.html" % (app_label, opts.object_name.lower()), 504 "admin/%s/delete_selected_confirmation.html" % app_label, 505 "admin/delete_selected_confirmation.html" 506 ], context, context_instance=template.RequestContext(request)) 507 508 delete_selected.short_description = ugettext_lazy("Delete selected %(verbose_name_plural)s") 393 509 394 510 def construct_change_message(self, request, form, formsets): 395 511 """ … … class ModelAdmin(BaseModelAdmin): 528 644 else: 529 645 self.message_user(request, msg) 530 646 return HttpResponseRedirect("../") 647 648 def response_action(self, request, queryset): 649 if request.method == 'POST': 650 # There can be multiple action forms on the page (at the top 651 # and bottom of the change list, for example). Get the action 652 # whose button was pushed. 653 try: 654 action_index = int(request.POST.get('index', 0)) 655 except ValueError: 656 action_index = 0 657 data = {} 658 for key in request.POST: 659 if key not in (helpers.ACTION_CHECKBOX_NAME, 'index'): 660 data[key] = request.POST.getlist(key)[action_index] 661 action_form = self.action_form(data, auto_id=None) 662 action_form.fields['action'].choices = self.get_action_choices() 663 664 if action_form.is_valid(): 665 action = action_form.cleaned_data['action'] 666 func, name, description, instance_action = self.get_action(action) 667 selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME) 668 results = queryset.filter(pk__in=selected) 669 response = None 670 if callable(func): 671 if instance_action: 672 for obj in results: 673 getattr(obj, name)(request) 674 else: 675 response = func(request, results) 676 if isinstance(response, HttpResponse): 677 return response 678 else: 679 redirect_to = request.META.get('HTTP_REFERER') or "." 680 return HttpResponseRedirect(redirect_to) 681 else: 682 action_form = self.action_form(auto_id=None) 683 action_form.fields['action'].choices = self.get_action_choices() 684 return action_form 531 685 532 686 def add_view(self, request, form_url='', extra_context=None): 533 687 "The 'add' admin view for this model." … … class ModelAdmin(BaseModelAdmin): 721 875 return render_to_response('admin/invalid_setup.html', {'title': _('Database error')}) 722 876 return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1') 723 877 878 action_form_or_response = self.response_action(request, queryset=cl.get_query_set()) 879 if isinstance(action_form_or_response, HttpResponse): 880 return action_form_or_response 881 724 882 # If we're allowing changelist editing, we need to construct a formset 725 883 # for the changelist given all the fields to be edited. Then we'll 726 884 # use the formset to validate/process POSTed data. … … class ModelAdmin(BaseModelAdmin): 761 919 if formset: 762 920 media = self.media + formset.media 763 921 else: 764 media = None765 922 media = self.media 923 766 924 context = { 767 925 'title': cl.title, 768 926 'is_popup': cl.is_popup, … … class ModelAdmin(BaseModelAdmin): 771 929 'has_add_permission': self.has_add_permission(request), 772 930 'root_path': self.admin_site.root_path, 773 931 'app_label': app_label, 932 'action_form': action_form_or_response, 933 'actions_on_top': self.actions_on_top, 934 'actions_on_bottom': self.actions_on_bottom, 774 935 } 775 936 context.update(extra_context or {}) 776 937 return render_to_response(self.change_list_template or [ -
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..7c2b56b 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 u gettext as _7 from django.utils.translation import ungettext, ugettext as _ 8 8 9 9 def quote(s): 10 10 """ … … def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_ 155 155 p = u'%s.%s' % (related.opts.app_label, related.opts.get_change_permission()) 156 156 if not user.has_perm(p): 157 157 perms_needed.add(related.opts.verbose_name) 158 159 def model_format_dict(obj): 160 """ 161 Return a `dict` with keys 'verbose_name' and 'verbose_name_plural', 162 typically for use with string formatting. 163 164 `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance. 165 166 """ 167 if isinstance(obj, (models.Model, models.base.ModelBase)): 168 opts = obj._meta 169 elif isinstance(obj, models.query.QuerySet): 170 opts = obj.model._meta 171 else: 172 opts = obj 173 return { 174 'verbose_name': force_unicode(opts.verbose_name), 175 'verbose_name_plural': force_unicode(opts.verbose_name_plural) 176 } 177 178 def model_ngettext(obj, n=None): 179 """ 180 Return the appropriate `verbose_name` or `verbose_name_plural` for `obj` 181 depending on the count `n`. 182 183 `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance. 184 If `obj` is a `QuerySet` instance, `n` is optional and the length of the 185 `QuerySet` is used. 186 187 """ 188 if isinstance(obj, models.query.QuerySet): 189 if n is None: 190 n = obj.count() 191 obj = obj.model 192 d = model_format_dict(obj) 193 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..1f6cc7f
- + 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.directsubscriber"> 12 <field type="CharField" name="name">John Doe</field> 13 <field type="CharField" name="email">john@example.org</field> 14 <field type="BooleanField" name="paid">True</field> 15 </object> 16 <object pk="1" model="admin_views.externalsubscriber"> 17 <field type="CharField" name="name">John Doe</field> 18 <field type="CharField" name="email">john@example.org</field> 19 </object> 20 </django-objects> 21 No newline at end of file -
tests/regressiontests/admin_views/models.py
diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py index eeaf039..0f53230 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 DirectSubscriber(Subscriber): 222 paid = models.BooleanField(default=False) 223 224 def direct_mail(self, request): 225 EmailMessage( 226 'Greetings from a model action', 227 'This is the test email from a model action', 228 'from@example.com', 229 [self.email] 230 ).send() 231 232 class DirectSubscriberAdmin(admin.ModelAdmin): 233 actions = ['direct_mail'] 234 235 class ExternalSubscriber(Subscriber): 236 pass 237 238 def external_mail(request, selected): 239 EmailMessage( 240 'Greetings from a function action', 241 'This is the test email from a function action', 242 'from@example.com', 243 ['to@example.com'] 244 ).send() 245 246 def redirect_to(request, selected): 247 from django.http import HttpResponseRedirect 248 return HttpResponseRedirect('/some-where-else/') 249 250 class ExternalSubscriberAdmin(admin.ModelAdmin): 251 actions = [external_mail, redirect_to] 202 252 203 253 admin.site.register(Article, ArticleAdmin) 204 254 admin.site.register(CustomArticle, CustomArticleAdmin) … … admin.site.register(Color) 208 258 admin.site.register(Thing, ThingAdmin) 209 259 admin.site.register(Person, PersonAdmin) 210 260 admin.site.register(Persona, PersonaAdmin) 261 admin.site.register(Subscriber, SubscriberAdmin) 262 admin.site.register(DirectSubscriber, DirectSubscriberAdmin) 263 admin.site.register(ExternalSubscriber, ExternalSubscriberAdmin) 211 264 212 265 # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. 213 266 # 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..8be78a7 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, DirectSubscriber, ExternalSubscriber 15 16 16 17 try: 17 18 set … … 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_model_instance_action(self): 922 "Tests a custom action defined in a model method" 923 action_data = { 924 ACTION_CHECKBOX_NAME: [1], 925 'action' : 'direct_mail', 926 'index': 0, 927 'paid': 1, 928 } 929 response = self.client.post('/test_admin/admin/admin_views/directsubscriber/', action_data) 930 self.assertEquals(len(mail.outbox), 1) 931 self.assertEquals(mail.outbox[0].subject, 'Greetings from a model action') 932 self.assertEquals(mail.outbox[0].to, [u'john@example.org']) 933 934 def test_custom_function_mail_action(self): 935 "Tests a custom action defined in a function" 936 action_data = { 937 ACTION_CHECKBOX_NAME: [1], 938 'action' : 'external_mail', 939 'index': 0, 940 } 941 response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data) 942 self.assertEquals(len(mail.outbox), 1) 943 self.assertEquals(mail.outbox[0].subject, 'Greetings from a function action') 944 945 def test_custom_function_action_with_redirect(self): 946 "Tests a custom action defined in a function" 947 action_data = { 948 ACTION_CHECKBOX_NAME: [1], 949 'action' : 'redirect_to', 950 'index': 0, 951 } 952 response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data) 953 self.failUnlessEqual(response.status_code, 302)