Ticket #10505: admin-actions.1.diff
File admin-actions.1.diff, 15.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..335a44e 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() 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..6294a28 100644
a b 50 50 51 51 #changelist table thead th { 52 52 white-space: nowrap; 53 vertical-align: middle; 53 54 } 54 55 55 56 #changelist table tbody td { … … 209 210 border-color: #036; 210 211 } 211 212 213 .filtered #action-form .actions { 214 margin-right: 160px !important; 215 border-right: 1px solid #ddd; 216 } 217 218 #action-form .actions { 219 color: #666; 220 padding: 3px; 221 font-weight: bold; 222 background: #efefef url(../img/admin/nav-bg.gif); 223 } 224 225 #action-form .actions:last-child { 226 border-bottom: none; 227 } 228 229 #action-form .actions select { 230 border: 1px solid #aaa; 231 margin: 0 0.5em; 232 } 233 234 #action-toggle { 235 display: none; 236 } 237 238 #action-form .actions label { 239 font-size: 11px; 240 margin-left: 0.5em; 241 } 242 243 #action-form tbody tr input.action-select { 244 margin: 0; 245 } 246 247 #action-form thead th:first-child { 248 width: 1.5em; 249 text-align: center; 250 } 251 252 #action-form tbody td:first-child { 253 border-left: 0; 254 border-right: 1px solid #ddd; 255 text-align: center; 256 } -
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 859229e..ece2e7a 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.html import escape 15 16 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 from django.utils.translation import ugettext as _ 19 from django.utils.translation import ugettext as _, ugettext_lazy 19 20 from django.utils.encoding import force_unicode 20 21 try: 21 22 set … … class ModelAdmin(BaseModelAdmin): 172 173 "Encapsulates all admin options and functionality for a given model." 173 174 __metaclass__ = forms.MediaDefiningClass 174 175 175 list_display = (' __str__',)176 list_display = ('action_checkbox', '__str__',) 176 177 list_display_links = () 177 178 list_filter = () 178 179 list_select_related = False … … class ModelAdmin(BaseModelAdmin): 190 191 delete_confirmation_template = None 191 192 object_history_template = None 192 193 194 # Actions 195 actions = ['delete_selected'] 196 action_form = helpers.ActionForm 197 actions_on_top = False 198 actions_on_bottom = True 199 193 200 def __init__(self, model, admin_site): 194 201 self.model = model 195 202 self.opts = model._meta … … class ModelAdmin(BaseModelAdmin): 198 205 for inline_class in self.inlines: 199 206 inline_instance = inline_class(self.model, self.admin_site) 200 207 self.inline_instances.append(inline_instance) 208 if 'action_checkbox' not in self.list_display: 209 self.list_display = list(self.list_display) 210 self.list_display.insert(0, 'action_checkbox') 211 if not self.list_display_links: 212 for name in self.list_display: 213 if name != 'action_checkbox': 214 self.list_display_links = [name] 215 break 201 216 super(ModelAdmin, self).__init__() 202 217 203 218 def get_urls(self): … … class ModelAdmin(BaseModelAdmin): 237 252 from django.conf import settings 238 253 239 254 js = ['js/core.js', 'js/admin/RelatedObjectLookups.js'] 255 if self.actions: 256 js.extend(['js/getElementsBySelector.js', 'js/actions.js']) 240 257 if self.prepopulated_fields: 241 258 js.append('js/urlify.js') 242 259 if self.opts.get_ordered_objects(): … … class ModelAdmin(BaseModelAdmin): 365 382 action_flag = DELETION 366 383 ) 367 384 385 def action_checkbox(self, obj): 386 """ 387 A list_display column containing a checkbox widget. 388 """ 389 return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, force_unicode(obj.pk)) 390 action_checkbox.short_description = mark_safe('<input type="checkbox" id="action-toggle" />') 391 action_checkbox.allow_tags = True 392 393 def _get_action_choices(self, blank_choice=BLANK_CHOICE_DASH): 394 if hasattr(self, '_action_choices'): 395 return self._action_choices 396 self._action_choices = blank_choice 397 for action in getattr(self, 'actions', []): 398 func, name, description = self.get_action(action) 399 choice = (name, description % model_format_dict(self.opts)) 400 self._action_choices.append(choice) 401 return self._action_choices 402 action_choices = property(_get_action_choices) 403 404 def get_action(self, name_or_callable): 405 if callable(name_or_callable): 406 func = name_or_callable 407 name = name_or_callable.__name__ 408 else: 409 try: 410 func = getattr(self, name_or_callable) 411 except AttributeError: 412 try: 413 func = getattr(self.model, name_or_callable) 414 except AttributeError: 415 raise AttributeError, \ 416 "'%s' model or '%s' objects have no action '%s'" % \ 417 (self.opts.object_name, self.__class__, name_or_callable) 418 name = name_or_callable 419 if hasattr(func, 'short_description'): 420 description = func.short_description 421 else: 422 description = capfirst(name.replace('_', ' ')) 423 return func, name, description 424 425 def delete_selected(self, request, changelist): 426 """ 427 Default action which deletes the selected objects. 428 """ 429 if self.has_delete_permission(request): 430 selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME) 431 objs = changelist.get_query_set().filter(pk__in=selected) 432 n = objs.count() 433 if n: 434 for obj in objs: 435 obj_display = force_unicode(obj) 436 self.log_deletion(request, obj, obj_display) 437 objs.delete() 438 self.message_user(request, _("Successfully deleted %d %s.") % ( 439 n, model_ngettext(self.opts, n) 440 )) 441 delete_selected.short_description = ugettext_lazy("Delete selected %(verbose_name_plural)s") 368 442 369 443 def construct_change_message(self, request, form, formsets): 370 444 """ … … class ModelAdmin(BaseModelAdmin): 503 577 else: 504 578 self.message_user(request, msg) 505 579 return HttpResponseRedirect("../") 580 581 def response_action(self, request, changelist): 582 if request.method == 'POST': 583 # There can be multiple action forms on the page (at the top 584 # and bottom of the change list, for example). Get the action 585 # whose button was pushed. 586 try: 587 action_index = int(request.POST.get('index', 0)) 588 except ValueError: 589 action_index = 0 590 data = {} 591 for key in request.POST: 592 if key not in (helpers.ACTION_CHECKBOX_NAME, 'index'): 593 data[key] = request.POST.getlist(key)[action_index] 594 action_form = self.action_form(data, auto_id=None) 595 action_form.fields['action'].choices = self.action_choices 596 597 if action_form.is_valid(): 598 action = action_form.cleaned_data['action'] 599 action_func, name, description = self.get_action(action) 600 response = None 601 if callable(action_func): 602 response = action_func(request, changelist) 603 if isinstance(response, HttpResponse): 604 return response 605 else: 606 redirect_to = request.META.get('HTTP_REFERER') or "." 607 return HttpResponseRedirect(redirect_to) 608 else: 609 action_form = self.action_form(auto_id=None) 610 action_form.fields['action'].choices = self.action_choices 611 return action_form 506 612 507 613 def add_view(self, request, form_url='', extra_context=None): 508 614 "The 'add' admin view for this model." … … class ModelAdmin(BaseModelAdmin): 696 802 return render_to_response('admin/invalid_setup.html', {'title': _('Database error')}) 697 803 return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1') 698 804 805 action_response = self.response_action(request, cl) 806 if isinstance(action_response, HttpResponse): 807 return action_response 808 699 809 context = { 700 810 'title': cl.title, 701 811 'is_popup': cl.is_popup, 702 812 'cl': cl, 813 'media': mark_safe(self.media), 703 814 'has_add_permission': self.has_add_permission(request), 704 815 'root_path': self.admin_site.root_path, 705 816 'app_label': app_label, 817 'action_form': action_response, 818 'actions_on_top': self.actions_on_top, 819 'actions_on_bottom': self.actions_on_bottom, 706 820 } 707 821 context.update(extra_context or {}) 708 822 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..21c0198
- + 1 {% load i18n %} 2 <div class="actions"> 3 {% for field in action_form %}<label>{% trans "Action:" %} {{ 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 5f8a430..1f00318 100644
a b 3 3 4 4 {% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/changelists.css" />{% endblock %} 5 5 6 {% block extrahead %}{{ block.super }}{{ media }}{% endblock %} 7 6 8 {% block bodyclass %}change-list{% endblock %} 7 9 8 10 {% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> › <a href="../">{{ app_label|capfirst }}</a> › {{ cl.opts.verbose_name_plural|capfirst }}</div>{% endblock %}{% endif %} … … 31 33 {% endif %} 32 34 {% endblock %} 33 35 34 {% block result_list %}{% result_list cl %}{% endblock %} 36 {% block result_list %} 37 <form id="action-form" method="post" action=""> 38 {% if actions_on_top and cl.full_result_count %}{% admin_actions %}{% endif %} 39 {% result_list cl %} 40 {% if actions_on_bottom and cl.full_result_count %}{% admin_actions %}{% endif %} 41 </form> 42 {% endblock %} 35 43 {% block pagination %}{% pagination cl %}{% endblock %} 36 44 </div> 37 45 </div> -
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 37cdb91..59fc4ff 100644
a b search_form = register.inclusion_tag('admin/search_form.html')(search_form) 311 311 def admin_list_filter(cl, spec): 312 312 return {'title': spec.title(), 'choices' : list(spec.choices(cl))} 313 313 admin_list_filter = register.inclusion_tag('admin/filter.html')(admin_list_filter) 314 315 def admin_actions(context): 316 """ 317 Track the number of times the action field has been rendered on the page, 318 so we know which value to use. 319 """ 320 context['action_index'] = context.get('action_index', -1) + 1 321 return context 322 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)