4 from django.forms.models import BaseInlineFormSet |
4 from django.forms.models import BaseInlineFormSet |
5 from django.contrib.contenttypes.models import ContentType |
5 from django.contrib.contenttypes.models import ContentType |
6 from django.contrib.admin import widgets |
6 from django.contrib.admin import widgets |
7 from django.contrib.admin import helpers |
7 from django.contrib.admin import helpers |
8 from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict |
8 from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict |
9 from django.core.exceptions import PermissionDenied |
9 from django.contrib import messages |
|
10 from django.views.decorators.csrf import csrf_protect |
|
11 from django.core.exceptions import PermissionDenied, ValidationError |
10 from django.db import models, transaction |
12 from django.db import models, transaction |
11 from django.db.models.fields import BLANK_CHOICE_DASH |
13 from django.db.models.fields import BLANK_CHOICE_DASH |
12 from django.http import Http404, HttpResponse, HttpResponseRedirect |
14 from django.http import Http404, HttpResponse, HttpResponseRedirect |
13 from django.shortcuts import get_object_or_404, render_to_response |
15 from django.shortcuts import get_object_or_404, render_to_response |
|
16 from django.utils.decorators import method_decorator |
14 from django.utils.datastructures import SortedDict |
17 from django.utils.datastructures import SortedDict |
15 from django.utils.functional import update_wrapper |
18 from django.utils.functional import update_wrapper |
16 from django.utils.html import escape |
19 from django.utils.html import escape |
17 from django.utils.safestring import mark_safe |
20 from django.utils.safestring import mark_safe |
18 from django.utils.functional import curry |
21 from django.utils.functional import curry |
19 from django.utils.text import capfirst, get_text_list |
22 from django.utils.text import capfirst, get_text_list |
20 from django.utils.translation import ugettext as _ |
23 from django.utils.translation import ugettext as _ |
21 from django.utils.translation import ungettext, ugettext_lazy |
24 from django.utils.translation import ungettext, ugettext_lazy |
22 from django.utils.encoding import force_unicode |
25 from django.utils.encoding import force_unicode |
23 try: |
|
24 set |
|
25 except NameError: |
|
26 from sets import Set as set # Python 2.3 fallback |
|
27 |
26 |
28 HORIZONTAL, VERTICAL = 1, 2 |
27 HORIZONTAL, VERTICAL = 1, 2 |
29 # returns the <ul> class for a given radio_admin field |
28 # returns the <ul> class for a given radio_admin field |
30 get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '') |
29 get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '') |
31 |
30 |
38 FORMFIELD_FOR_DBFIELD_DEFAULTS = { |
37 FORMFIELD_FOR_DBFIELD_DEFAULTS = { |
39 models.DateTimeField: { |
38 models.DateTimeField: { |
40 'form_class': forms.SplitDateTimeField, |
39 'form_class': forms.SplitDateTimeField, |
41 'widget': widgets.AdminSplitDateTime |
40 'widget': widgets.AdminSplitDateTime |
42 }, |
41 }, |
43 models.DateField: {'widget': widgets.AdminDateWidget}, |
42 models.DateField: {'widget': widgets.AdminDateWidget}, |
44 models.TimeField: {'widget': widgets.AdminTimeWidget}, |
43 models.TimeField: {'widget': widgets.AdminTimeWidget}, |
45 models.TextField: {'widget': widgets.AdminTextareaWidget}, |
44 models.TextField: {'widget': widgets.AdminTextareaWidget}, |
46 models.URLField: {'widget': widgets.AdminURLFieldWidget}, |
45 models.URLField: {'widget': widgets.AdminURLFieldWidget}, |
47 models.IntegerField: {'widget': widgets.AdminIntegerFieldWidget}, |
46 models.IntegerField: {'widget': widgets.AdminIntegerFieldWidget}, |
48 models.CharField: {'widget': widgets.AdminTextInputWidget}, |
47 models.BigIntegerField: {'widget': widgets.AdminIntegerFieldWidget}, |
49 models.ImageField: {'widget': widgets.AdminFileWidget}, |
48 models.CharField: {'widget': widgets.AdminTextInputWidget}, |
50 models.FileField: {'widget': widgets.AdminFileWidget}, |
49 models.ImageField: {'widget': widgets.AdminFileWidget}, |
|
50 models.FileField: {'widget': widgets.AdminFileWidget}, |
51 } |
51 } |
52 |
52 |
|
53 csrf_protect_m = method_decorator(csrf_protect) |
53 |
54 |
54 class BaseModelAdmin(object): |
55 class BaseModelAdmin(object): |
55 """Functionality common to both ModelAdmin and InlineAdmin.""" |
56 """Functionality common to both ModelAdmin and InlineAdmin.""" |
|
57 __metaclass__ = forms.MediaDefiningClass |
56 |
58 |
57 raw_id_fields = () |
59 raw_id_fields = () |
58 fields = None |
60 fields = None |
59 exclude = None |
61 exclude = None |
60 fieldsets = None |
62 fieldsets = None |
136 |
141 |
137 def formfield_for_foreignkey(self, db_field, request=None, **kwargs): |
142 def formfield_for_foreignkey(self, db_field, request=None, **kwargs): |
138 """ |
143 """ |
139 Get a form Field for a ForeignKey. |
144 Get a form Field for a ForeignKey. |
140 """ |
145 """ |
|
146 db = kwargs.get('using') |
141 if db_field.name in self.raw_id_fields: |
147 if db_field.name in self.raw_id_fields: |
142 kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel) |
148 kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel, using=db) |
143 elif db_field.name in self.radio_fields: |
149 elif db_field.name in self.radio_fields: |
144 kwargs['widget'] = widgets.AdminRadioSelect(attrs={ |
150 kwargs['widget'] = widgets.AdminRadioSelect(attrs={ |
145 'class': get_ul_class(self.radio_fields[db_field.name]), |
151 'class': get_ul_class(self.radio_fields[db_field.name]), |
146 }) |
152 }) |
147 kwargs['empty_label'] = db_field.blank and _('None') or None |
153 kwargs['empty_label'] = db_field.blank and _('None') or None |
150 |
156 |
151 def formfield_for_manytomany(self, db_field, request=None, **kwargs): |
157 def formfield_for_manytomany(self, db_field, request=None, **kwargs): |
152 """ |
158 """ |
153 Get a form Field for a ManyToManyField. |
159 Get a form Field for a ManyToManyField. |
154 """ |
160 """ |
155 # If it uses an intermediary model, don't show field in admin. |
161 # If it uses an intermediary model that isn't auto created, don't show |
156 if db_field.rel.through is not None: |
162 # a field in admin. |
|
163 if not db_field.rel.through._meta.auto_created: |
157 return None |
164 return None |
|
165 db = kwargs.get('using') |
158 |
166 |
159 if db_field.name in self.raw_id_fields: |
167 if db_field.name in self.raw_id_fields: |
160 kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel) |
168 kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, using=db) |
161 kwargs['help_text'] = '' |
169 kwargs['help_text'] = '' |
162 elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)): |
170 elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)): |
163 kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical)) |
171 kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical)) |
164 |
172 |
165 return db_field.formfield(**kwargs) |
173 return db_field.formfield(**kwargs) |
188 save_on_top = False |
198 save_on_top = False |
189 ordering = None |
199 ordering = None |
190 inlines = [] |
200 inlines = [] |
191 |
201 |
192 # Custom templates (designed to be over-ridden in subclasses) |
202 # Custom templates (designed to be over-ridden in subclasses) |
|
203 add_form_template = None |
193 change_form_template = None |
204 change_form_template = None |
194 change_list_template = None |
205 change_list_template = None |
195 delete_confirmation_template = None |
206 delete_confirmation_template = None |
|
207 delete_selected_confirmation_template = None |
196 object_history_template = None |
208 object_history_template = None |
197 |
209 |
198 # Actions |
210 # Actions |
199 actions = [] |
211 actions = [] |
200 action_form = helpers.ActionForm |
212 action_form = helpers.ActionForm |
201 actions_on_top = True |
213 actions_on_top = True |
202 actions_on_bottom = False |
214 actions_on_bottom = False |
|
215 actions_selection_counter = True |
203 |
216 |
204 def __init__(self, model, admin_site): |
217 def __init__(self, model, admin_site): |
205 self.model = model |
218 self.model = model |
206 self.opts = model._meta |
219 self.opts = model._meta |
207 self.admin_site = admin_site |
220 self.admin_site = admin_site |
252 urls = property(urls) |
265 urls = property(urls) |
253 |
266 |
254 def _media(self): |
267 def _media(self): |
255 from django.conf import settings |
268 from django.conf import settings |
256 |
269 |
257 js = ['js/core.js', 'js/admin/RelatedObjectLookups.js'] |
270 js = ['js/core.js', 'js/admin/RelatedObjectLookups.js', |
|
271 'js/jquery.min.js', 'js/jquery.init.js'] |
258 if self.actions is not None: |
272 if self.actions is not None: |
259 js.extend(['js/getElementsBySelector.js', 'js/actions.js']) |
273 js.extend(['js/actions.min.js']) |
260 if self.prepopulated_fields: |
274 if self.prepopulated_fields: |
261 js.append('js/urlify.js') |
275 js.append('js/urlify.js') |
|
276 js.append('js/prepopulate.min.js') |
262 if self.opts.get_ordered_objects(): |
277 if self.opts.get_ordered_objects(): |
263 js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js']) |
278 js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js']) |
264 |
279 |
265 return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js]) |
280 return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js]) |
266 media = property(_media) |
281 media = property(_media) |
319 def get_fieldsets(self, request, obj=None): |
334 def get_fieldsets(self, request, obj=None): |
320 "Hook for specifying fieldsets for the add form." |
335 "Hook for specifying fieldsets for the add form." |
321 if self.declared_fieldsets: |
336 if self.declared_fieldsets: |
322 return self.declared_fieldsets |
337 return self.declared_fieldsets |
323 form = self.get_form(request, obj) |
338 form = self.get_form(request, obj) |
324 return [(None, {'fields': form.base_fields.keys()})] |
339 fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj)) |
|
340 return [(None, {'fields': fields})] |
325 |
341 |
326 def get_form(self, request, obj=None, **kwargs): |
342 def get_form(self, request, obj=None, **kwargs): |
327 """ |
343 """ |
328 Returns a Form class for use in the admin add view. This is used by |
344 Returns a Form class for use in the admin add view. This is used by |
329 add_view and change_view. |
345 add_view and change_view. |
334 fields = None |
350 fields = None |
335 if self.exclude is None: |
351 if self.exclude is None: |
336 exclude = [] |
352 exclude = [] |
337 else: |
353 else: |
338 exclude = list(self.exclude) |
354 exclude = list(self.exclude) |
|
355 exclude.extend(kwargs.get("exclude", [])) |
|
356 exclude.extend(self.get_readonly_fields(request, obj)) |
339 # if exclude is an empty list we pass None to be consistant with the |
357 # if exclude is an empty list we pass None to be consistant with the |
340 # default on modelform_factory |
358 # default on modelform_factory |
|
359 exclude = exclude or None |
341 defaults = { |
360 defaults = { |
342 "form": self.form, |
361 "form": self.form, |
343 "fields": fields, |
362 "fields": fields, |
344 "exclude": (exclude + kwargs.get("exclude", [])) or None, |
363 "exclude": exclude, |
345 "formfield_callback": curry(self.formfield_for_dbfield, request=request), |
364 "formfield_callback": curry(self.formfield_for_dbfield, request=request), |
346 } |
365 } |
347 defaults.update(kwargs) |
366 defaults.update(kwargs) |
348 return modelform_factory(self.model, **defaults) |
367 return modelform_factory(self.model, **defaults) |
|
368 |
|
369 def get_changelist(self, request, **kwargs): |
|
370 """ |
|
371 Returns the ChangeList class for use on the changelist page. |
|
372 """ |
|
373 from django.contrib.admin.views.main import ChangeList |
|
374 return ChangeList |
|
375 |
|
376 def get_object(self, request, object_id): |
|
377 """ |
|
378 Returns an instance matching the primary key provided. ``None`` is |
|
379 returned if no match is found (or the object_id failed validation |
|
380 against the primary key field). |
|
381 """ |
|
382 queryset = self.queryset(request) |
|
383 model = queryset.model |
|
384 try: |
|
385 object_id = model._meta.pk.to_python(object_id) |
|
386 return queryset.get(pk=object_id) |
|
387 except (model.DoesNotExist, ValidationError): |
|
388 return None |
349 |
389 |
350 def get_changelist_form(self, request, **kwargs): |
390 def get_changelist_form(self, request, **kwargs): |
351 """ |
391 """ |
352 Returns a Form class for use in the Formset on the changelist page. |
392 Returns a Form class for use in the Formset on the changelist page. |
353 """ |
393 """ |
520 |
560 |
521 if formsets: |
561 if formsets: |
522 for formset in formsets: |
562 for formset in formsets: |
523 for added_object in formset.new_objects: |
563 for added_object in formset.new_objects: |
524 change_message.append(_('Added %(name)s "%(object)s".') |
564 change_message.append(_('Added %(name)s "%(object)s".') |
525 % {'name': added_object._meta.verbose_name, |
565 % {'name': force_unicode(added_object._meta.verbose_name), |
526 'object': force_unicode(added_object)}) |
566 'object': force_unicode(added_object)}) |
527 for changed_object, changed_fields in formset.changed_objects: |
567 for changed_object, changed_fields in formset.changed_objects: |
528 change_message.append(_('Changed %(list)s for %(name)s "%(object)s".') |
568 change_message.append(_('Changed %(list)s for %(name)s "%(object)s".') |
529 % {'list': get_text_list(changed_fields, _('and')), |
569 % {'list': get_text_list(changed_fields, _('and')), |
530 'name': changed_object._meta.verbose_name, |
570 'name': force_unicode(changed_object._meta.verbose_name), |
531 'object': force_unicode(changed_object)}) |
571 'object': force_unicode(changed_object)}) |
532 for deleted_object in formset.deleted_objects: |
572 for deleted_object in formset.deleted_objects: |
533 change_message.append(_('Deleted %(name)s "%(object)s".') |
573 change_message.append(_('Deleted %(name)s "%(object)s".') |
534 % {'name': deleted_object._meta.verbose_name, |
574 % {'name': force_unicode(deleted_object._meta.verbose_name), |
535 'object': force_unicode(deleted_object)}) |
575 'object': force_unicode(deleted_object)}) |
536 change_message = ' '.join(change_message) |
576 change_message = ' '.join(change_message) |
537 return change_message or _('No fields changed.') |
577 return change_message or _('No fields changed.') |
538 |
578 |
539 def message_user(self, request, message): |
579 def message_user(self, request, message): |
540 """ |
580 """ |
541 Send a message to the user. The default implementation |
581 Send a message to the user. The default implementation |
542 posts a message using the auth Message object. |
582 posts a message using the django.contrib.messages backend. |
543 """ |
583 """ |
544 request.user.message_set.create(message=message) |
584 messages.info(request, message) |
545 |
585 |
546 def save_form(self, request, form, change): |
586 def save_form(self, request, form, change): |
547 """ |
587 """ |
548 Given a ModelForm return an unsaved instance. ``change`` is True if |
588 Given a ModelForm return an unsaved instance. ``change`` is True if |
549 the object is being changed, and False if it's being added. |
589 the object is being changed, and False if it's being added. |
580 'content_type_id': ContentType.objects.get_for_model(self.model).id, |
620 'content_type_id': ContentType.objects.get_for_model(self.model).id, |
581 'save_as': self.save_as, |
621 'save_as': self.save_as, |
582 'save_on_top': self.save_on_top, |
622 'save_on_top': self.save_on_top, |
583 'root_path': self.admin_site.root_path, |
623 'root_path': self.admin_site.root_path, |
584 }) |
624 }) |
|
625 if add and self.add_form_template is not None: |
|
626 form_template = self.add_form_template |
|
627 else: |
|
628 form_template = self.change_form_template |
585 context_instance = template.RequestContext(request, current_app=self.admin_site.name) |
629 context_instance = template.RequestContext(request, current_app=self.admin_site.name) |
586 return render_to_response(self.change_form_template or [ |
630 return render_to_response(form_template or [ |
587 "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()), |
631 "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()), |
588 "admin/%s/change_form.html" % app_label, |
632 "admin/%s/change_form.html" % app_label, |
589 "admin/change_form.html" |
633 "admin/change_form.html" |
590 ], context, context_instance=context_instance) |
634 ], context, context_instance=context_instance) |
591 |
635 |
681 action_form.fields['action'].choices = self.get_action_choices(request) |
726 action_form.fields['action'].choices = self.get_action_choices(request) |
682 |
727 |
683 # If the form's valid we can handle the action. |
728 # If the form's valid we can handle the action. |
684 if action_form.is_valid(): |
729 if action_form.is_valid(): |
685 action = action_form.cleaned_data['action'] |
730 action = action_form.cleaned_data['action'] |
|
731 select_across = action_form.cleaned_data['select_across'] |
686 func, name, description = self.get_actions(request)[action] |
732 func, name, description = self.get_actions(request)[action] |
687 |
733 |
688 # Get the list of selected PKs. If nothing's selected, we can't |
734 # Get the list of selected PKs. If nothing's selected, we can't |
689 # perform an action on it, so bail. |
735 # perform an action on it, so bail. Except we want to perform |
|
736 # the action explicitly on all objects. |
690 selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME) |
737 selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME) |
691 if not selected: |
738 if not selected and not select_across: |
|
739 # Reminder that something needs to be selected or nothing will happen |
|
740 msg = _("Items must be selected in order to perform " |
|
741 "actions on them. No items have been changed.") |
|
742 self.message_user(request, msg) |
692 return None |
743 return None |
693 |
744 |
694 response = func(self, request, queryset.filter(pk__in=selected)) |
745 if not select_across: |
|
746 # Perform the action only on the selected objects |
|
747 queryset = queryset.filter(pk__in=selected) |
|
748 |
|
749 response = func(self, request, queryset) |
695 |
750 |
696 # Actions may return an HttpResponse, which will be used as the |
751 # Actions may return an HttpResponse, which will be used as the |
697 # response from the POST. If not, we'll be a good little HTTP |
752 # response from the POST. If not, we'll be a good little HTTP |
698 # citizen and redirect back to the changelist page. |
753 # citizen and redirect back to the changelist page. |
699 if isinstance(response, HttpResponse): |
754 if isinstance(response, HttpResponse): |
700 return response |
755 return response |
701 else: |
756 else: |
702 return HttpResponseRedirect(".") |
757 return HttpResponseRedirect(".") |
703 |
758 else: |
|
759 msg = _("No action selected.") |
|
760 self.message_user(request, msg) |
|
761 return None |
|
762 |
|
763 @csrf_protect_m |
|
764 @transaction.commit_on_success |
704 def add_view(self, request, form_url='', extra_context=None): |
765 def add_view(self, request, form_url='', extra_context=None): |
705 "The 'add' admin view for this model." |
766 "The 'add' admin view for this model." |
706 model = self.model |
767 model = self.model |
707 opts = model._meta |
768 opts = model._meta |
708 |
769 |
712 ModelForm = self.get_form(request) |
773 ModelForm = self.get_form(request) |
713 formsets = [] |
774 formsets = [] |
714 if request.method == 'POST': |
775 if request.method == 'POST': |
715 form = ModelForm(request.POST, request.FILES) |
776 form = ModelForm(request.POST, request.FILES) |
716 if form.is_valid(): |
777 if form.is_valid(): |
|
778 new_object = self.save_form(request, form, change=False) |
717 form_validated = True |
779 form_validated = True |
718 new_object = self.save_form(request, form, change=False) |
|
719 else: |
780 else: |
720 form_validated = False |
781 form_validated = False |
721 new_object = self.model() |
782 new_object = self.model() |
722 prefixes = {} |
783 prefixes = {} |
723 for FormSet in self.get_formsets(request): |
784 for FormSet, inline in zip(self.get_formsets(request), self.inline_instances): |
724 prefix = FormSet.get_default_prefix() |
785 prefix = FormSet.get_default_prefix() |
725 prefixes[prefix] = prefixes.get(prefix, 0) + 1 |
786 prefixes[prefix] = prefixes.get(prefix, 0) + 1 |
726 if prefixes[prefix] != 1: |
787 if prefixes[prefix] != 1: |
727 prefix = "%s-%s" % (prefix, prefixes[prefix]) |
788 prefix = "%s-%s" % (prefix, prefixes[prefix]) |
728 formset = FormSet(data=request.POST, files=request.FILES, |
789 formset = FormSet(data=request.POST, files=request.FILES, |
729 instance=new_object, |
790 instance=new_object, |
730 save_as_new=request.POST.has_key("_saveasnew"), |
791 save_as_new=request.POST.has_key("_saveasnew"), |
731 prefix=prefix) |
792 prefix=prefix, queryset=inline.queryset(request)) |
732 formsets.append(formset) |
793 formsets.append(formset) |
733 if all_valid(formsets) and form_validated: |
794 if all_valid(formsets) and form_validated: |
734 self.save_model(request, new_object, form, change=False) |
795 self.save_model(request, new_object, form, change=False) |
735 form.save_m2m() |
796 form.save_m2m() |
736 for formset in formsets: |
797 for formset in formsets: |
749 continue |
810 continue |
750 if isinstance(f, models.ManyToManyField): |
811 if isinstance(f, models.ManyToManyField): |
751 initial[k] = initial[k].split(",") |
812 initial[k] = initial[k].split(",") |
752 form = ModelForm(initial=initial) |
813 form = ModelForm(initial=initial) |
753 prefixes = {} |
814 prefixes = {} |
754 for FormSet in self.get_formsets(request): |
815 for FormSet, inline in zip(self.get_formsets(request), |
|
816 self.inline_instances): |
755 prefix = FormSet.get_default_prefix() |
817 prefix = FormSet.get_default_prefix() |
756 prefixes[prefix] = prefixes.get(prefix, 0) + 1 |
818 prefixes[prefix] = prefixes.get(prefix, 0) + 1 |
757 if prefixes[prefix] != 1: |
819 if prefixes[prefix] != 1: |
758 prefix = "%s-%s" % (prefix, prefixes[prefix]) |
820 prefix = "%s-%s" % (prefix, prefixes[prefix]) |
759 formset = FormSet(instance=self.model(), prefix=prefix) |
821 formset = FormSet(instance=self.model(), prefix=prefix, |
|
822 queryset=inline.queryset(request)) |
760 formsets.append(formset) |
823 formsets.append(formset) |
761 |
824 |
762 adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields) |
825 adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), |
|
826 self.prepopulated_fields, self.get_readonly_fields(request), |
|
827 model_admin=self) |
763 media = self.media + adminForm.media |
828 media = self.media + adminForm.media |
764 |
829 |
765 inline_admin_formsets = [] |
830 inline_admin_formsets = [] |
766 for inline, formset in zip(self.inline_instances, formsets): |
831 for inline, formset in zip(self.inline_instances, formsets): |
767 fieldsets = list(inline.get_fieldsets(request)) |
832 fieldsets = list(inline.get_fieldsets(request)) |
768 inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets) |
833 readonly = list(inline.get_readonly_fields(request)) |
|
834 inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, |
|
835 fieldsets, readonly, model_admin=self) |
769 inline_admin_formsets.append(inline_admin_formset) |
836 inline_admin_formsets.append(inline_admin_formset) |
770 media = media + inline_admin_formset.media |
837 media = media + inline_admin_formset.media |
771 |
838 |
772 context = { |
839 context = { |
773 'title': _('Add %s') % force_unicode(opts.verbose_name), |
840 'title': _('Add %s') % force_unicode(opts.verbose_name), |
780 'root_path': self.admin_site.root_path, |
847 'root_path': self.admin_site.root_path, |
781 'app_label': opts.app_label, |
848 'app_label': opts.app_label, |
782 } |
849 } |
783 context.update(extra_context or {}) |
850 context.update(extra_context or {}) |
784 return self.render_change_form(request, context, form_url=form_url, add=True) |
851 return self.render_change_form(request, context, form_url=form_url, add=True) |
785 add_view = transaction.commit_on_success(add_view) |
852 |
786 |
853 @csrf_protect_m |
|
854 @transaction.commit_on_success |
787 def change_view(self, request, object_id, extra_context=None): |
855 def change_view(self, request, object_id, extra_context=None): |
788 "The 'change' admin view for this model." |
856 "The 'change' admin view for this model." |
789 model = self.model |
857 model = self.model |
790 opts = model._meta |
858 opts = model._meta |
791 |
859 |
792 try: |
860 obj = self.get_object(request, unquote(object_id)) |
793 obj = self.queryset(request).get(pk=unquote(object_id)) |
|
794 except model.DoesNotExist: |
|
795 # Don't raise Http404 just yet, because we haven't checked |
|
796 # permissions yet. We don't want an unauthenticated user to be able |
|
797 # to determine whether a given object exists. |
|
798 obj = None |
|
799 |
861 |
800 if not self.has_change_permission(request, obj): |
862 if not self.has_change_permission(request, obj): |
801 raise PermissionDenied |
863 raise PermissionDenied |
802 |
864 |
803 if obj is None: |
865 if obj is None: |
815 new_object = self.save_form(request, form, change=True) |
877 new_object = self.save_form(request, form, change=True) |
816 else: |
878 else: |
817 form_validated = False |
879 form_validated = False |
818 new_object = obj |
880 new_object = obj |
819 prefixes = {} |
881 prefixes = {} |
820 for FormSet in self.get_formsets(request, new_object): |
882 for FormSet, inline in zip(self.get_formsets(request, new_object), |
|
883 self.inline_instances): |
821 prefix = FormSet.get_default_prefix() |
884 prefix = FormSet.get_default_prefix() |
822 prefixes[prefix] = prefixes.get(prefix, 0) + 1 |
885 prefixes[prefix] = prefixes.get(prefix, 0) + 1 |
823 if prefixes[prefix] != 1: |
886 if prefixes[prefix] != 1: |
824 prefix = "%s-%s" % (prefix, prefixes[prefix]) |
887 prefix = "%s-%s" % (prefix, prefixes[prefix]) |
825 formset = FormSet(request.POST, request.FILES, |
888 formset = FormSet(request.POST, request.FILES, |
826 instance=new_object, prefix=prefix) |
889 instance=new_object, prefix=prefix, |
|
890 queryset=inline.queryset(request)) |
|
891 |
827 formsets.append(formset) |
892 formsets.append(formset) |
828 |
893 |
829 if all_valid(formsets) and form_validated: |
894 if all_valid(formsets) and form_validated: |
830 self.save_model(request, new_object, form, change=True) |
895 self.save_model(request, new_object, form, change=True) |
831 form.save_m2m() |
896 form.save_m2m() |
837 return self.response_change(request, new_object) |
902 return self.response_change(request, new_object) |
838 |
903 |
839 else: |
904 else: |
840 form = ModelForm(instance=obj) |
905 form = ModelForm(instance=obj) |
841 prefixes = {} |
906 prefixes = {} |
842 for FormSet in self.get_formsets(request, obj): |
907 for FormSet, inline in zip(self.get_formsets(request, obj), self.inline_instances): |
843 prefix = FormSet.get_default_prefix() |
908 prefix = FormSet.get_default_prefix() |
844 prefixes[prefix] = prefixes.get(prefix, 0) + 1 |
909 prefixes[prefix] = prefixes.get(prefix, 0) + 1 |
845 if prefixes[prefix] != 1: |
910 if prefixes[prefix] != 1: |
846 prefix = "%s-%s" % (prefix, prefixes[prefix]) |
911 prefix = "%s-%s" % (prefix, prefixes[prefix]) |
847 formset = FormSet(instance=obj, prefix=prefix) |
912 formset = FormSet(instance=obj, prefix=prefix, |
|
913 queryset=inline.queryset(request)) |
848 formsets.append(formset) |
914 formsets.append(formset) |
849 |
915 |
850 adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields) |
916 adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), |
|
917 self.prepopulated_fields, self.get_readonly_fields(request, obj), |
|
918 model_admin=self) |
851 media = self.media + adminForm.media |
919 media = self.media + adminForm.media |
852 |
920 |
853 inline_admin_formsets = [] |
921 inline_admin_formsets = [] |
854 for inline, formset in zip(self.inline_instances, formsets): |
922 for inline, formset in zip(self.inline_instances, formsets): |
855 fieldsets = list(inline.get_fieldsets(request, obj)) |
923 fieldsets = list(inline.get_fieldsets(request, obj)) |
856 inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets) |
924 readonly = list(inline.get_readonly_fields(request, obj)) |
|
925 inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, |
|
926 fieldsets, readonly, model_admin=self) |
857 inline_admin_formsets.append(inline_admin_formset) |
927 inline_admin_formsets.append(inline_admin_formset) |
858 media = media + inline_admin_formset.media |
928 media = media + inline_admin_formset.media |
859 |
929 |
860 context = { |
930 context = { |
861 'title': _('Change %s') % force_unicode(opts.verbose_name), |
931 'title': _('Change %s') % force_unicode(opts.verbose_name), |
869 'root_path': self.admin_site.root_path, |
939 'root_path': self.admin_site.root_path, |
870 'app_label': opts.app_label, |
940 'app_label': opts.app_label, |
871 } |
941 } |
872 context.update(extra_context or {}) |
942 context.update(extra_context or {}) |
873 return self.render_change_form(request, context, change=True, obj=obj) |
943 return self.render_change_form(request, context, change=True, obj=obj) |
874 change_view = transaction.commit_on_success(change_view) |
944 |
875 |
945 @csrf_protect_m |
876 def changelist_view(self, request, extra_context=None): |
946 def changelist_view(self, request, extra_context=None): |
877 "The 'change list' admin view for this model." |
947 "The 'change list' admin view for this model." |
878 from django.contrib.admin.views.main import ChangeList, ERROR_FLAG |
948 from django.contrib.admin.views.main import ERROR_FLAG |
879 opts = self.model._meta |
949 opts = self.model._meta |
880 app_label = opts.app_label |
950 app_label = opts.app_label |
881 if not self.has_change_permission(request, None): |
951 if not self.has_change_permission(request, None): |
882 raise PermissionDenied |
952 raise PermissionDenied |
883 |
953 |
890 try: |
960 try: |
891 list_display.remove('action_checkbox') |
961 list_display.remove('action_checkbox') |
892 except ValueError: |
962 except ValueError: |
893 pass |
963 pass |
894 |
964 |
|
965 ChangeList = self.get_changelist(request) |
895 try: |
966 try: |
896 cl = ChangeList(request, self.model, list_display, self.list_display_links, self.list_filter, |
967 cl = ChangeList(request, self.model, list_display, self.list_display_links, self.list_filter, |
897 self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self.list_editable, self) |
968 self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self.list_editable, self) |
898 except IncorrectLookupParameters: |
969 except IncorrectLookupParameters: |
899 # Wacky lookup parameters were given, so redirect to the main |
970 # Wacky lookup parameters were given, so redirect to the main |
900 # changelist page, without parameters, and pass an 'invalid=1' |
971 # changelist page, without parameters, and pass an 'invalid=1' |
901 # parameter via the query string. If wacky parameters were given and |
972 # parameter via the query string. If wacky parameters were given |
902 # the 'invalid=1' parameter was already in the query string, something |
973 # and the 'invalid=1' parameter was already in the query string, |
903 # is screwed up with the database, so display an error page. |
974 # something is screwed up with the database, so display an error |
|
975 # page. |
904 if ERROR_FLAG in request.GET.keys(): |
976 if ERROR_FLAG in request.GET.keys(): |
905 return render_to_response('admin/invalid_setup.html', {'title': _('Database error')}) |
977 return render_to_response('admin/invalid_setup.html', {'title': _('Database error')}) |
906 return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1') |
978 return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1') |
907 |
979 |
908 # If the request was POSTed, this might be a bulk action or a bulk edit. |
980 # If the request was POSTed, this might be a bulk action or a bulk |
909 # Try to look up an action first, but if this isn't an action the POST |
981 # edit. Try to look up an action or confirmation first, but if this |
910 # will fall through to the bulk edit check, below. |
982 # isn't an action the POST will fall through to the bulk edit check, |
911 if actions and request.method == 'POST': |
983 # below. |
912 response = self.response_action(request, queryset=cl.get_query_set()) |
984 action_failed = False |
913 if response: |
985 selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME) |
914 return response |
986 |
|
987 # Actions with no confirmation |
|
988 if (actions and request.method == 'POST' and |
|
989 'index' in request.POST and '_save' not in request.POST): |
|
990 if selected: |
|
991 response = self.response_action(request, queryset=cl.get_query_set()) |
|
992 if response: |
|
993 return response |
|
994 else: |
|
995 action_failed = True |
|
996 else: |
|
997 msg = _("Items must be selected in order to perform " |
|
998 "actions on them. No items have been changed.") |
|
999 self.message_user(request, msg) |
|
1000 action_failed = True |
|
1001 |
|
1002 # Actions with confirmation |
|
1003 if (actions and request.method == 'POST' and |
|
1004 helpers.ACTION_CHECKBOX_NAME in request.POST and |
|
1005 'index' not in request.POST and '_save' not in request.POST): |
|
1006 if selected: |
|
1007 response = self.response_action(request, queryset=cl.get_query_set()) |
|
1008 if response: |
|
1009 return response |
|
1010 else: |
|
1011 action_failed = True |
915 |
1012 |
916 # If we're allowing changelist editing, we need to construct a formset |
1013 # If we're allowing changelist editing, we need to construct a formset |
917 # for the changelist given all the fields to be edited. Then we'll |
1014 # for the changelist given all the fields to be edited. Then we'll |
918 # use the formset to validate/process POSTed data. |
1015 # use the formset to validate/process POSTed data. |
919 formset = cl.formset = None |
1016 formset = cl.formset = None |
920 |
1017 |
921 # Handle POSTed bulk-edit data. |
1018 # Handle POSTed bulk-edit data. |
922 if request.method == "POST" and self.list_editable: |
1019 if (request.method == "POST" and self.list_editable and |
|
1020 '_save' in request.POST and not action_failed): |
923 FormSet = self.get_changelist_formset(request) |
1021 FormSet = self.get_changelist_formset(request) |
924 formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list) |
1022 formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list) |
925 if formset.is_valid(): |
1023 if formset.is_valid(): |
926 changecount = 0 |
1024 changecount = 0 |
927 for form in formset.forms: |
1025 for form in formset.forms: |
963 action_form = self.action_form(auto_id=None) |
1061 action_form = self.action_form(auto_id=None) |
964 action_form.fields['action'].choices = self.get_action_choices(request) |
1062 action_form.fields['action'].choices = self.get_action_choices(request) |
965 else: |
1063 else: |
966 action_form = None |
1064 action_form = None |
967 |
1065 |
|
1066 selection_note_all = ungettext('%(total_count)s selected', |
|
1067 'All %(total_count)s selected', cl.result_count) |
|
1068 |
968 context = { |
1069 context = { |
|
1070 'module_name': force_unicode(opts.verbose_name_plural), |
|
1071 'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)}, |
|
1072 'selection_note_all': selection_note_all % {'total_count': cl.result_count}, |
969 'title': cl.title, |
1073 'title': cl.title, |
970 'is_popup': cl.is_popup, |
1074 'is_popup': cl.is_popup, |
971 'cl': cl, |
1075 'cl': cl, |
972 'media': media, |
1076 'media': media, |
973 'has_add_permission': self.has_add_permission(request), |
1077 'has_add_permission': self.has_add_permission(request), |
974 'root_path': self.admin_site.root_path, |
1078 'root_path': self.admin_site.root_path, |
975 'app_label': app_label, |
1079 'app_label': app_label, |
976 'action_form': action_form, |
1080 'action_form': action_form, |
977 'actions_on_top': self.actions_on_top, |
1081 'actions_on_top': self.actions_on_top, |
978 'actions_on_bottom': self.actions_on_bottom, |
1082 'actions_on_bottom': self.actions_on_bottom, |
|
1083 'actions_selection_counter': self.actions_selection_counter, |
979 } |
1084 } |
980 context.update(extra_context or {}) |
1085 context.update(extra_context or {}) |
981 context_instance = template.RequestContext(request, current_app=self.admin_site.name) |
1086 context_instance = template.RequestContext(request, current_app=self.admin_site.name) |
982 return render_to_response(self.change_list_template or [ |
1087 return render_to_response(self.change_list_template or [ |
983 'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()), |
1088 'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()), |
984 'admin/%s/change_list.html' % app_label, |
1089 'admin/%s/change_list.html' % app_label, |
985 'admin/change_list.html' |
1090 'admin/change_list.html' |
986 ], context, context_instance=context_instance) |
1091 ], context, context_instance=context_instance) |
987 |
1092 |
|
1093 @csrf_protect_m |
988 def delete_view(self, request, object_id, extra_context=None): |
1094 def delete_view(self, request, object_id, extra_context=None): |
989 "The 'delete' admin view for this model." |
1095 "The 'delete' admin view for this model." |
990 opts = self.model._meta |
1096 opts = self.model._meta |
991 app_label = opts.app_label |
1097 app_label = opts.app_label |
992 |
1098 |
993 try: |
1099 obj = self.get_object(request, unquote(object_id)) |
994 obj = self.queryset(request).get(pk=unquote(object_id)) |
|
995 except self.model.DoesNotExist: |
|
996 # Don't raise Http404 just yet, because we haven't checked |
|
997 # permissions yet. We don't want an unauthenticated user to be able |
|
998 # to determine whether a given object exists. |
|
999 obj = None |
|
1000 |
1100 |
1001 if not self.has_delete_permission(request, obj): |
1101 if not self.has_delete_permission(request, obj): |
1002 raise PermissionDenied |
1102 raise PermissionDenied |
1003 |
1103 |
1004 if obj is None: |
1104 if obj is None: |
1005 raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)}) |
1105 raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)}) |
1006 |
1106 |
1007 # Populate deleted_objects, a data structure of all related objects that |
1107 # Populate deleted_objects, a data structure of all related objects that |
1008 # will also be deleted. |
1108 # will also be deleted. |
1009 deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), object_id, escape(obj))), []] |
1109 (deleted_objects, perms_needed) = get_deleted_objects((obj,), opts, request.user, self.admin_site) |
1010 perms_needed = set() |
|
1011 get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site) |
|
1012 |
1110 |
1013 if request.POST: # The user has already confirmed the deletion. |
1111 if request.POST: # The user has already confirmed the deletion. |
1014 if perms_needed: |
1112 if perms_needed: |
1015 raise PermissionDenied |
1113 raise PermissionDenied |
1016 obj_display = force_unicode(obj) |
1114 obj_display = force_unicode(obj) |
1050 action_list = LogEntry.objects.filter( |
1148 action_list = LogEntry.objects.filter( |
1051 object_id = object_id, |
1149 object_id = object_id, |
1052 content_type__id__exact = ContentType.objects.get_for_model(model).id |
1150 content_type__id__exact = ContentType.objects.get_for_model(model).id |
1053 ).select_related().order_by('action_time') |
1151 ).select_related().order_by('action_time') |
1054 # If no history was found, see whether this object even exists. |
1152 # If no history was found, see whether this object even exists. |
1055 obj = get_object_or_404(model, pk=object_id) |
1153 obj = get_object_or_404(model, pk=unquote(object_id)) |
1056 context = { |
1154 context = { |
1057 'title': _('Change history: %s') % force_unicode(obj), |
1155 'title': _('Change history: %s') % force_unicode(obj), |
1058 'action_list': action_list, |
1156 'action_list': action_list, |
1059 'module_name': capfirst(force_unicode(opts.verbose_name_plural)), |
1157 'module_name': capfirst(force_unicode(opts.verbose_name_plural)), |
1060 'object': obj, |
1158 'object': obj, |
1124 if self.verbose_name_plural is None: |
1223 if self.verbose_name_plural is None: |
1125 self.verbose_name_plural = self.model._meta.verbose_name_plural |
1224 self.verbose_name_plural = self.model._meta.verbose_name_plural |
1126 |
1225 |
1127 def _media(self): |
1226 def _media(self): |
1128 from django.conf import settings |
1227 from django.conf import settings |
1129 js = [] |
1228 js = ['js/jquery.min.js', 'js/jquery.init.js', 'js/inlines.min.js'] |
1130 if self.prepopulated_fields: |
1229 if self.prepopulated_fields: |
1131 js.append('js/urlify.js') |
1230 js.append('js/urlify.js') |
|
1231 js.append('js/prepopulate.min.js') |
1132 if self.filter_vertical or self.filter_horizontal: |
1232 if self.filter_vertical or self.filter_horizontal: |
1133 js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js']) |
1233 js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js']) |
1134 return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js]) |
1234 return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js]) |
1135 media = property(_media) |
1235 media = property(_media) |
1136 |
1236 |
1142 fields = None |
1242 fields = None |
1143 if self.exclude is None: |
1243 if self.exclude is None: |
1144 exclude = [] |
1244 exclude = [] |
1145 else: |
1245 else: |
1146 exclude = list(self.exclude) |
1246 exclude = list(self.exclude) |
|
1247 exclude.extend(kwargs.get("exclude", [])) |
|
1248 exclude.extend(self.get_readonly_fields(request, obj)) |
1147 # if exclude is an empty list we use None, since that's the actual |
1249 # if exclude is an empty list we use None, since that's the actual |
1148 # default |
1250 # default |
|
1251 exclude = exclude or None |
1149 defaults = { |
1252 defaults = { |
1150 "form": self.form, |
1253 "form": self.form, |
1151 "formset": self.formset, |
1254 "formset": self.formset, |
1152 "fk_name": self.fk_name, |
1255 "fk_name": self.fk_name, |
1153 "fields": fields, |
1256 "fields": fields, |
1154 "exclude": (exclude + kwargs.get("exclude", [])) or None, |
1257 "exclude": exclude, |
1155 "formfield_callback": curry(self.formfield_for_dbfield, request=request), |
1258 "formfield_callback": curry(self.formfield_for_dbfield, request=request), |
1156 "extra": self.extra, |
1259 "extra": self.extra, |
1157 "max_num": self.max_num, |
1260 "max_num": self.max_num, |
|
1261 "can_delete": self.can_delete, |
1158 } |
1262 } |
1159 defaults.update(kwargs) |
1263 defaults.update(kwargs) |
1160 return inlineformset_factory(self.parent_model, self.model, **defaults) |
1264 return inlineformset_factory(self.parent_model, self.model, **defaults) |
1161 |
1265 |
1162 def get_fieldsets(self, request, obj=None): |
1266 def get_fieldsets(self, request, obj=None): |
1163 if self.declared_fieldsets: |
1267 if self.declared_fieldsets: |
1164 return self.declared_fieldsets |
1268 return self.declared_fieldsets |
1165 form = self.get_formset(request).form |
1269 form = self.get_formset(request).form |
1166 return [(None, {'fields': form.base_fields.keys()})] |
1270 fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj)) |
|
1271 return [(None, {'fields': fields})] |
|
1272 |
|
1273 def queryset(self, request): |
|
1274 return self.model._default_manager.all() |
1167 |
1275 |
1168 class StackedInline(InlineModelAdmin): |
1276 class StackedInline(InlineModelAdmin): |
1169 template = 'admin/edit_inline/stacked.html' |
1277 template = 'admin/edit_inline/stacked.html' |
1170 |
1278 |
1171 class TabularInline(InlineModelAdmin): |
1279 class TabularInline(InlineModelAdmin): |