Django

Code

root/django/branches/newforms-admin/django/contrib/admin/options.py

Revision 7935, 34.9 kB (checked in by brosner, 1 year ago)

newforms-admin: Fixed #5490 -- Properly quote special characters in primary keys in the admin. Added tests to ensure functionality. This also moves quote and unquote to django/contrib/admin/util.py. Thanks jdetaeye and shanx for all your help.

Line 
1 from django import oldforms, template
2 from django import newforms as forms
3 from django.newforms.formsets import all_valid
4 from django.newforms.models import modelform_factory, inlineformset_factory
5 from django.newforms.models import BaseInlineFormset
6 from django.contrib.contenttypes.models import ContentType
7 from django.contrib.admin import widgets
8 from django.contrib.admin.util import quote, unquote, get_deleted_objects
9 from django.core.exceptions import ImproperlyConfigured, PermissionDenied
10 from django.db import models, transaction
11 from django.http import Http404, HttpResponse, HttpResponseRedirect
12 from django.shortcuts import get_object_or_404, render_to_response
13 from django.utils.html import escape
14 from django.utils.safestring import mark_safe
15 from django.utils.text import capfirst, get_text_list
16 from django.utils.translation import ugettext as _
17 from django.utils.encoding import force_unicode
18 import sets
19
20 HORIZONTAL, VERTICAL = 1, 2
21 # returns the <ul> class for a given radio_admin field
22 get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
23
24 class IncorrectLookupParameters(Exception):
25     pass
26
27 def flatten_fieldsets(fieldsets):
28     """Returns a list of field names from an admin fieldsets structure."""
29     field_names = []
30     for name, opts in fieldsets:
31         for field in opts['fields']:
32             # type checking feels dirty, but it seems like the best way here
33             if type(field) == tuple:
34                 field_names.extend(field)
35             else:
36                 field_names.append(field)
37     return field_names
38
39 class AdminForm(object):
40     def __init__(self, form, fieldsets, prepopulated_fields):
41         self.form, self.fieldsets = form, fieldsets
42         self.prepopulated_fields = [{
43             'field': form[field_name],
44             'dependencies': [form[f] for f in dependencies]
45         } for field_name, dependencies in prepopulated_fields.items()]
46
47     def __iter__(self):
48         for name, options in self.fieldsets:
49             yield Fieldset(self.form, name, **options)
50
51     def first_field(self):
52         for bf in self.form:
53             return bf
54
55     def _media(self):
56         media = self.form.media
57         for fs in self:
58             media = media + fs.media
59         return media
60     media = property(_media)
61
62 class Fieldset(object):
63     def __init__(self, form, name=None, fields=(), classes=(), description=None):
64         self.form = form
65         self.name, self.fields = name, fields
66         self.classes = u' '.join(classes)
67         self.description = description
68
69     def _media(self):
70         from django.conf import settings
71         if 'collapse' in self.classes:
72             return forms.Media(js=['%sjs/admin/CollapsedFieldsets.js' % settings.ADMIN_MEDIA_PREFIX])
73         return forms.Media()
74     media = property(_media)
75
76     def __iter__(self):
77         for field in self.fields:
78             yield Fieldline(self.form, field)
79
80 class Fieldline(object):
81     def __init__(self, form, field):
82         self.form = form # A django.forms.Form instance
83         if isinstance(field, basestring):
84             self.fields = [field]
85         else:
86             self.fields = field
87
88     def __iter__(self):
89         for i, field in enumerate(self.fields):
90             yield AdminField(self.form, field, is_first=(i == 0))
91
92     def errors(self):
93         return mark_safe(u'\n'.join([self.form[f].errors.as_ul() for f in self.fields]))
94
95 class AdminField(object):
96     def __init__(self, form, field, is_first):
97         self.field = form[field] # A django.forms.BoundField instance
98         self.is_first = is_first # Whether this field is first on the line
99         self.is_checkbox = isinstance(self.field.field.widget, forms.CheckboxInput)
100
101     def label_tag(self):
102         classes = []
103         if self.is_checkbox:
104             classes.append(u'vCheckboxLabel')
105             contents = escape(self.field.label)
106         else:
107             contents = force_unicode(escape(self.field.label)) + u':'
108         if self.field.field.required:
109             classes.append(u'required')
110         if not self.is_first:
111             classes.append(u'inline')
112         attrs = classes and {'class': u' '.join(classes)} or {}
113         return self.field.label_tag(contents=contents, attrs=attrs)
114
115 class BaseModelAdmin(object):
116     """Functionality common to both ModelAdmin and InlineAdmin."""
117     raw_id_fields = ()
118     fields = None
119     fieldsets = None
120     form = forms.ModelForm
121     filter_vertical = ()
122     filter_horizontal = ()
123     radio_fields = {}
124     prepopulated_fields = {}
125
126     def formfield_for_dbfield(self, db_field, **kwargs):
127         """
128         Hook for specifying the form Field instance for a given database Field
129         instance.
130
131         If kwargs are given, they're passed to the form Field's constructor.
132         """
133         # For DateTimeFields, use a special field and widget.
134         if isinstance(db_field, models.DateTimeField):
135             kwargs['form_class'] = forms.SplitDateTimeField
136             kwargs['widget'] = widgets.AdminSplitDateTime()
137             return db_field.formfield(**kwargs)
138
139         # For DateFields, add a custom CSS class.
140         if isinstance(db_field, models.DateField):
141             kwargs['widget'] = widgets.AdminDateWidget
142             return db_field.formfield(**kwargs)
143
144         # For TimeFields, add a custom CSS class.
145         if isinstance(db_field, models.TimeField):
146             kwargs['widget'] = widgets.AdminTimeWidget
147             return db_field.formfield(**kwargs)
148
149         # For FileFields and ImageFields add a link to the current file.
150         if isinstance(db_field, models.ImageField) or isinstance(db_field, models.FileField):
151             kwargs['widget'] = widgets.AdminFileWidget
152             return db_field.formfield(**kwargs)
153
154         # For ForeignKey or ManyToManyFields, use a special widget.
155         if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
156             if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields:
157                 kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel)
158             elif isinstance(db_field, models.ForeignKey) and db_field.name in self.radio_fields:
159                 kwargs['widget'] = widgets.AdminRadioSelect(attrs={
160                     'class': get_ul_class(self.radio_fields[db_field.name]),
161                 })
162                 kwargs['empty_label'] = db_field.blank and _('None') or None
163             else:
164                 if isinstance(db_field, models.ManyToManyField):
165                     if db_field.name in self.raw_id_fields:
166                         kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel)
167                         kwargs['help_text'] = ''
168                     elif db_field.name in (self.filter_vertical + self.filter_horizontal):
169                         kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
170             # Wrap the widget's render() method with a method that adds
171             # extra HTML to the end of the rendered output.
172             formfield = db_field.formfield(**kwargs)
173             # Don't wrap raw_id fields. Their add function is in the popup window.
174             if not db_field.name in self.raw_id_fields:
175                 formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
176             return formfield
177        
178         if db_field.choices and db_field.name in self.radio_fields:
179             kwargs['widget'] = widgets.AdminRadioSelect(
180                 choices=db_field.get_choices(include_blank=db_field.blank,
181                     blank_choice=[('', _('None'))]),
182                 attrs={
183                     'class': get_ul_class(self.radio_fields[db_field.name]),
184                 }
185             )
186
187         # For any other type of field, just call its formfield() method.
188         return db_field.formfield(**kwargs)
189
190     def _declared_fieldsets(self):
191         if self.fieldsets:
192             return self.fieldsets
193         elif self.fields:
194             return [(None, {'fields': self.fields})]
195         return None
196     declared_fieldsets = property(_declared_fieldsets)
197
198 class ModelAdmin(BaseModelAdmin):
199     "Encapsulates all admin options and functionality for a given model."
200     __metaclass__ = forms.MediaDefiningClass
201
202     list_display = ('__str__',)
203     list_display_links = ()
204     list_filter = ()
205     list_select_related = False
206     list_per_page = 100
207     search_fields = ()
208     date_hierarchy = None
209     save_as = False
210     save_on_top = False
211     ordering = None
212     inlines = []
213    
214     # Custom templates (designed to be over-ridden in subclasses)
215     change_form_template = None
216     change_list_template = None
217     delete_confirmation_template = None
218     object_history_template = None
219
220     def __init__(self, model, admin_site):
221         self.model = model
222         self.opts = model._meta
223         self.admin_site = admin_site
224         self.inline_instances = []
225         for inline_class in self.inlines:
226             inline_instance = inline_class(self.model, self.admin_site)
227             self.inline_instances.append(inline_instance)
228         super(ModelAdmin, self).__init__()
229
230     def __call__(self, request, url):
231         # Check that LogEntry, ContentType and the auth context processor are installed.
232         from django.conf import settings
233         if settings.DEBUG:
234             from django.contrib.admin.models import LogEntry
235             if not LogEntry._meta.installed:
236                 raise ImproperlyConfigured("Put 'django.contrib.admin' in your INSTALLED_APPS setting in order to use the admin application.")
237             if not ContentType._meta.installed:
238                 raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in your INSTALLED_APPS setting in order to use the admin application.")
239             if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS:
240                 raise ImproperlyConfigured("Put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.")
241
242         # Delegate to the appropriate method, based on the URL.
243         if url is None:
244             return self.changelist_view(request)
245         elif url.endswith('add'):
246             return self.add_view(request)
247         elif url.endswith('history'):
248             return self.history_view(request, unquote(url[:-8]))
249         elif url.endswith('delete'):
250             return self.delete_view(request, unquote(url[:-7]))
251         else:
252             return self.change_view(request, unquote(url))
253
254     def _media(self):
255         from django.conf import settings
256
257         js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
258         if self.prepopulated_fields:
259             js.append('js/urlify.js')
260         if self.opts.get_ordered_objects():
261             js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
262         if self.filter_vertical or self.filter_horizontal:
263             js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
264        
265         return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
266     media = property(_media)
267
268     def has_add_permission(self, request):
269         "Returns True if the given request has permission to add an object."
270         opts = self.opts
271         return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission())
272
273     def has_change_permission(self, request, obj=None):
274         """
275         Returns True if the given request has permission to change the given
276         Django model instance.
277
278         If `obj` is None, this should return True if the given request has
279         permission to change *any* object of the given type.
280         """
281         opts = self.opts
282         return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission())
283
284     def has_delete_permission(self, request, obj=None):
285         """
286         Returns True if the given request has permission to change the given
287         Django model instance.
288
289         If `obj` is None, this should return True if the given request has
290         permission to delete *any* object of the given type.
291         """
292         opts = self.opts
293         return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission())
294
295     def queryset(self, request):
296         """
297         Returns a QuerySet of all model instances that can be edited by the
298         admin site. This is used by changelist_view.
299         """
300         qs = self.model._default_manager.get_query_set()
301         # TODO: this should be handled by some parameter to the ChangeList.
302         ordering = self.ordering or () # otherwise we might try to *None, which is bad ;)
303         if ordering:
304             qs = qs.order_by(*ordering)
305         return qs
306
307     def get_fieldsets(self, request, obj=None):
308         "Hook for specifying fieldsets for the add form."
309         if self.declared_fieldsets:
310             return self.declared_fieldsets
311         form = self.get_form(request)
312         return [(None, {'fields': form.base_fields.keys()})]
313
314     def get_form(self, request, obj=None):
315         """
316         Returns a Form class for use in the admin add view. This is used by
317         add_view and change_view.
318         """
319         if self.declared_fieldsets:
320             fields = flatten_fieldsets(self.declared_fieldsets)
321         else:
322             fields = None
323         return modelform_factory(self.model, form=self.form, fields=fields, formfield_callback=self.formfield_for_dbfield)
324
325     def get_formsets(self, request, obj=None):
326         for inline in self.inline_instances:
327             yield inline.get_formset(request, obj)
328
329     def save_add(self, request, form, formsets, post_url_continue):
330         """
331         Saves the object in the "add" stage and returns an HttpResponseRedirect.
332
333         `form` is a bound Form instance that's verified to be valid.
334         """
335         from django.contrib.admin.models import LogEntry, ADDITION
336         opts = self.model._meta
337         new_object = form.save(commit=True)
338
339         if formsets:
340             for formset in formsets:
341                 # HACK: it seems like the parent obejct should be passed into
342                 # a method of something, not just set as an attribute
343                 formset.instance = new_object
344                 formset.save()
345
346         pk_value = new_object._get_pk_val()
347         LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(self.model).id, pk_value, force_unicode(new_object), ADDITION)
348         msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': opts.verbose_name, 'obj': new_object}
349         # Here, we distinguish between different save types by checking for
350         # the presence of keys in request.POST.
351         if request.POST.has_key("_continue"):
352             request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
353             if request.POST.has_key("_popup"):
354                 post_url_continue += "?_popup=1"
355             return HttpResponseRedirect(post_url_continue % pk_value)
356
357         if request.POST.has_key("_popup"):
358             return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
359                 # escape() calls force_unicode.
360                 (escape(pk_value), escape(new_object)))
361         elif request.POST.has_key("_addanother"):
362             request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
363             return HttpResponseRedirect(request.path)
364         else:
365             request.user.message_set.create(message=msg)
366             # Figure out where to redirect. If the user has change permission,
367             # redirect to the change-list page for this object. Otherwise,
368             # redirect to the admin index.
369             if self.has_change_permission(request, None):
370                 post_url = '../'
371             else:
372                 post_url = '../../../'
373             return HttpResponseRedirect(post_url)
374     save_add = transaction.commit_on_success(save_add)
375
376     def save_change(self, request, form, formsets=None):
377         """
378         Saves the object in the "change" stage and returns an HttpResponseRedirect.
379
380         `form` is a bound Form instance that's verified to be valid.
381         
382         `formsets` is a sequence of InlineFormSet instances that are verified to be valid.
383         """
384         from django.contrib.admin.models import LogEntry, CHANGE
385         opts = self.model._meta
386         new_object = form.save(commit=True)
387         pk_value = new_object._get_pk_val()
388
389         if formsets:
390             for formset in formsets:
391                 formset.save()
392
393         # Construct the change message.
394         change_message = []
395         if form.changed_data:
396             change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and')))
397            
398         if formsets:
399             for formset in formsets:
400                 for added_object in formset.new_objects:
401                     change_message.append(_('Added %(name)s "%(object)s".')
402                                           % {'name': added_object._meta.verbose_name,
403                                              'object': added_object})
404                 for changed_object, changed_fields in formset.changed_objects:
405                     change_message.append(_('Changed %(list)s for %(name)s "%(object)s".')
406                                           % {'list': get_text_list(changed_fields, _('and')),
407                                              'name': changed_object._meta.verbose_name,
408                                              'object': changed_object})
409                 for deleted_object in formset.deleted_objects:
410                     change_message.append(_('Deleted %(name)s "%(object)s".')
411                                           % {'name': deleted_object._meta.verbose_name,
412                                              'object': deleted_object})
413         change_message = ' '.join(change_message)
414         if not change_message:
415             change_message = _('No fields changed.')
416         LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(self.model).id, pk_value, force_unicode(new_object), CHANGE, change_message)
417
418         msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj': new_object}
419         if request.POST.has_key("_continue"):
420             request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
421             if request.REQUEST.has_key('_popup'):
422                 return HttpResponseRedirect(request.path + "?_popup=1")
423             else:
424                 return HttpResponseRedirect(request.path)
425         elif request.POST.has_key("_saveasnew"):
426             request.user.message_set.create(message=_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object})
427             return HttpResponseRedirect("../%s/" % pk_value)
428         elif request.POST.has_key("_addanother"):
429             request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
430             return HttpResponseRedirect("../add/")
431         else:
432             request.user.message_set.create(message=msg)
433             return HttpResponseRedirect("../")
434     save_change = transaction.commit_on_success(save_change)
435
436     def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
437         opts = self.model._meta
438         app_label = opts.app_label
439         ordered_objects = opts.get_ordered_objects()
440         context.update({
441             'add': add,
442             'change': change,
443             'has_add_permission': self.has_add_permission(request),
444             'has_change_permission': self.has_change_permission(request, obj),
445             'has_delete_permission': self.has_delete_permission(request, obj),
446             'has_file_field': True, # FIXME - this should check if form or formsets have a FileField,
447             'has_absolute_url': hasattr(self.model, 'get_absolute_url'),
448             'ordered_objects': ordered_objects,
449             'form_url': mark_safe(form_url),
450             'opts': opts,
451             'content_type_id': ContentType.objects.get_for_model(self.model).id,
452             'save_as': self.save_as,
453             'save_on_top': self.save_on_top,
454             'root_path': self.admin_site.root_path,
455         })
456         return render_to_response(self.change_form_template or [
457             "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
458             "admin/%s/change_form.html" % app_label,
459             "admin/change_form.html"
460         ], context, context_instance=template.RequestContext(request))
461
462     def add_view(self, request, form_url='', extra_context=None):
463         "The 'add' admin view for this model."
464         model = self.model
465         opts = model._meta
466         app_label = opts.app_label
467
468         if not self.has_add_permission(request):
469             raise PermissionDenied
470
471         if self.has_change_permission(request, None):
472             # redirect to list view
473             post_url = '../'
474         else:
475             # Object list will give 'Permission Denied', so go back to admin home
476             post_url = '../../../'
477
478         ModelForm = self.get_form(request)
479         inline_formsets = []
480         obj = self.model()
481         if request.method == 'POST':
482             form = ModelForm(request.POST, request.FILES)
483             for FormSet in self.get_formsets(request):
484                 inline_formset = FormSet(data=request.POST, files=request.FILES,
485                     instance=obj, save_as_new=request.POST.has_key("_saveasnew"))
486                 inline_formsets.append(inline_formset)
487             if all_valid(inline_formsets) and form.is_valid():
488                 return self.save_add(request, form, inline_formsets, '../%s/')
489         else:
490             form = ModelForm(initial=dict(request.GET.items()))
491             for FormSet in self.get_formsets(request):
492                 inline_formset = FormSet(instance=obj)
493                 inline_formsets.append(inline_formset)
494
495         adminForm = AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields)
496         media = self.media + adminForm.media
497         for fs in inline_formsets:
498             media = media + fs.media
499
500         inline_admin_formsets = []
501         for inline, formset in zip(self.inline_instances, inline_formsets):
502             fieldsets = list(inline.get_fieldsets(request))
503             inline_admin_formset = InlineAdminFormSet(inline, formset, fieldsets)
504             inline_admin_formsets.append(inline_admin_formset)
505
506         context = {
507             'title': _('Add %s') % opts.verbose_name,
508             'adminform': adminForm,
509             'is_popup': request.REQUEST.has_key('_popup'),
510             'show_delete': False,
511             'media': mark_safe(media),
512             'inline_admin_formsets': inline_admin_formsets,
513             'errors': AdminErrorList(form, inline_formsets),
514             'root_path': self.admin_site.root_path,
515         }
516         context.update(extra_context or {})
517         return self.render_change_form(request, context, add=True)
518
519     def change_view(self, request, object_id, extra_context=None):
520         "The 'change' admin view for this model."
521         model = self.model
522         opts = model._meta
523         app_label = opts.app_label
524
525         try:
526             obj = model._default_manager.get(pk=object_id)
527         except model.DoesNotExist:
528             # Don't raise Http404 just yet, because we haven't checked
529             # permissions yet. We don't want an unauthenticated user to be able
530             # to determine whether a given object exists.
531             obj = None
532
533         if not self.has_change_permission(request, obj):
534             raise PermissionDenied
535
536         if obj is None:
537             raise Http404('%s object with primary key %r does not exist.' % (opts.verbose_name, escape(object_id)))
538
539         if request.POST and request.POST.has_key("_saveasnew"):
540             return self.add_view(request, form_url='../../add/')
541
542         ModelForm = self.get_form(request, obj)
543         inline_formsets = []
544         if request.method == 'POST':
545             form = ModelForm(request.POST, request.FILES, instance=obj)
546             for FormSet in self.get_formsets(request, obj):
547                 inline_formset = FormSet(request.POST, request.FILES, instance=obj)
548                 inline_formsets.append(inline_formset)
549
550             if all_valid(inline_formsets) and form.is_valid():
551                 return self.save_change(request, form, inline_formsets)
552         else:
553             form = ModelForm(instance=obj)
554             for FormSet in self.get_formsets(request, obj):
555                 inline_formset = FormSet(instance=obj)
556                 inline_formsets.append(inline_formset)
557
558         adminForm = AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
559         media = self.media + adminForm.media
560         for fs in inline_formsets:
561             media = media + fs.media
562
563         inline_admin_formsets = []
564         for inline, formset in zip(self.inline_instances, inline_formsets):
565             fieldsets = list(inline.get_fieldsets(request, obj))
566             inline_admin_formset = InlineAdminFormSet(inline, formset, fieldsets)
567             inline_admin_formsets.append(inline_admin_formset)
568
569         context = {
570             'title': _('Change %s') % opts.verbose_name,
571             'adminform': adminForm,
572             'object_id': object_id,
573             'original': obj,
574             'is_popup': request.REQUEST.has_key('_popup'),
575             'media': mark_safe(media),
576             'inline_admin_formsets': inline_admin_formsets,
577             'errors': AdminErrorList(form, inline_formsets),
578             'root_path': self.admin_site.root_path,
579         }
580         context.update(extra_context or {})
581         return self.render_change_form(request, context, change=True, obj=obj)
582
583     def changelist_view(self, request, extra_context=None):
584         "The 'change list' admin view for this model."
585         from django.contrib.admin.views.main import ChangeList, ERROR_FLAG
586         opts = self.model._meta
587         app_label = opts.app_label
588         if not self.has_change_permission(request, None):
589             raise PermissionDenied
590         try:
591             cl = ChangeList(request, self.model, self.list_display, self.list_display_links, self.list_filter,
592                 self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self)
593         except IncorrectLookupParameters:
594             # Wacky lookup parameters were given, so redirect to the main
595             # changelist page, without parameters, and pass an 'invalid=1'
596             # parameter via the query string. If wacky parameters were given and
597             # the 'invalid=1' parameter was already in the query string, something
598             # is screwed up with the database, so display an error page.
599             if ERROR_FLAG in request.GET.keys():
600                 return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
601             return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
602        
603         context = {
604             'title': cl.title,
605             'is_popup': cl.is_popup,
606             'cl': cl,
607             'has_add_permission': self.has_add_permission(request),
608             'root_path': self.admin_site.root_path,
609         }
610         context.update(extra_context or {})
611         return render_to_response(self.change_list_template or [
612             'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
613             'admin/%s/change_list.html' % app_label,
614             'admin/change_list.html'
615         ], context, context_instance=template.RequestContext(request))
616
617     def delete_view(self, request, object_id, extra_context=None):
618         "The 'delete' admin view for this model."
619         from django.contrib.admin.models import LogEntry, DELETION
620         opts = self.model._meta
621         app_label = opts.app_label
622
623         try:
624             obj = self.model._default_manager.get(pk=object_id)
625         except self.model.DoesNotExist:
626             # Don't raise Http404 just yet, because we haven't checked
627             # permissions yet. We don't want an unauthenticated user to be able
628             # to determine whether a given object exists.
629             obj = None
630
631         if not self.has_delete_permission(request, obj):
632             raise PermissionDenied
633
634         if obj is None:
635             raise Http404('%s object with primary key %r does not exist.' % (opts.verbose_name, escape(object_id)))
636
637         # Populate deleted_objects, a data structure of all related objects that
638         # will also be deleted.
639         deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), quote(object_id), escape(obj))), []]
640         perms_needed = sets.Set()
641         get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site)
642
643         if request.POST: # The user has already confirmed the deletion.
644             if perms_needed:
645                 raise PermissionDenied
646             obj_display = str(obj)
647             obj.delete()
648             LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(self.model).id, object_id, obj_display, DELETION)
649             request.user.message_set.create(message=_('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)})
650             if not self.has_change_permission(request, None):
651                 return HttpResponseRedirect("../../../../")
652             return HttpResponseRedirect("../../")
653        
654         context = {
655             "title": _("Are you sure?"),
656             "object_name": opts.verbose_name,
657             "object": obj,
658             "deleted_objects": deleted_objects,
659             "perms_lacking": perms_needed,
660             "opts": opts,
661             "root_path": self.admin_site.root_path,
662         }
663         context.update(extra_context or {})
664         return render_to_response(self.delete_confirmation_template or [
665             "admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
666             "admin/%s/delete_confirmation.html" % app_label,
667             "admin/delete_confirmation.html"
668         ], context, context_instance=template.RequestContext(request))
669
670     def history_view(self, request, object_id, extra_context=None):
671         "The 'history' admin view for this model."
672         from django.contrib.admin.models import LogEntry
673         model = self.model
674         opts = model._meta
675         action_list = LogEntry.objects.filter(
676             object_id = object_id,
677             content_type__id__exact = ContentType.objects.get_for_model(model).id
678         ).select_related().order_by('action_time')
679         # If no history was found, see whether this object even exists.
680         obj = get_object_or_404(model, pk=object_id)
681         context = {
682             'title': _('Change history: %s') % force_unicode(obj),
683             'action_list': action_list,
684             'module_name': capfirst(opts.verbose_name_plural),
685             'object': obj,
686             'root_path': self.admin_site.root_path,
687         }
688         context.update(extra_context or {})
689         return render_to_response(self.object_history_template or [
690             "admin/%s/%s/object_history.html" % (opts.app_label, opts.object_name.lower()),
691             "admin/%s/object_history.html" % opts.app_label,
692             "admin/object_history.html"
693         ], context, context_instance=template.RequestContext(request))
694
695 class InlineModelAdmin(BaseModelAdmin):
696     """
697     Options for inline editing of ``model`` instances.
698
699     Provide ``name`` to specify the attribute name of the ``ForeignKey`` from
700     ``model`` to its parent. This is required if ``model`` has more than one
701     ``ForeignKey`` to its parent.
702     """
703     model = None
704     fk_name = None
705     formset = BaseInlineFormset
706     extra = 3
707     max_num = 0
708     template = None
709     verbose_name = None
710     verbose_name_plural = None
711
712     def __init__(self, parent_model, admin_site):
713         self.admin_site = admin_site
714         self.parent_model = parent_model
715         self.opts = self.model._meta
716         super(InlineModelAdmin, self).__init__()
717         if self.verbose_name is None:
718             self.verbose_name = self.model._meta.verbose_name
719         if self.verbose_name_plural is None:
720             self.verbose_name_plural = self.model._meta.verbose_name_plural
721
722     def get_formset(self, request, obj=None):
723         """Returns a BaseInlineFormSet class for use in admin add/change views."""
724         if self.declared_fieldsets:
725             fields = flatten_fieldsets(self.declared_fieldsets)
726         else:
727             fields = None
728         return inlineformset_factory(self.parent_model, self.model,
729             form=self.form, formset=self.formset, fk_name=self.fk_name,
730             fields=fields, formfield_callback=self.formfield_for_dbfield,
731             extra=self.extra, max_num=self.max_num)
732
733     def get_fieldsets(self, request, obj=None):
734         if self.declared_fieldsets:
735             return self.declared_fieldsets
736         form = self.get_formset(request).form
737         return [(None, {'fields': form.base_fields.keys()})]
738
739 class StackedInline(InlineModelAdmin):
740     template = 'admin/edit_inline/stacked.html'
741
742 class TabularInline(InlineModelAdmin):
743     template = 'admin/edit_inline/tabular.html'
744
745 class InlineAdminFormSet(object):
746     """
747     A wrapper around an inline formset for use in the admin system.
748     """
749     def __init__(self, inline, formset, fieldsets):
750         self.opts = inline
751         self.formset = formset
752         self.fieldsets = fieldsets
753
754     def __iter__(self):
755         for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()):
756             yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, original)
757         for form in self.formset.extra_forms:
758             yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, None)
759
760     def fields(self):
761         for field_name in flatten_fieldsets(self.fieldsets):
762             yield self.formset.form.base_fields[field_name]
763
764 class InlineAdminForm(AdminForm):
765     """
766     A wrapper around an inline form for use in the admin system.
767     """
768     def __init__(self, formset, form, fieldsets, prepopulated_fields, original):
769         self.formset = formset
770         self.original = original
771         self.show_url = original and hasattr(original, 'get_absolute_url')
772         super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields)
773
774     def pk_field(self):
775         return AdminField(self.form, self.formset._pk_field_name, False)
776
777     def deletion_field(self):
778         from django.newforms.formsets import DELETION_FIELD_NAME
779         return AdminField(self.form, DELETION_FIELD_NAME, False)
780
781     def ordering_field(self):
782         from django.newforms.formsets import ORDERING_FIELD_NAME
783         return AdminField(self.form, ORDERING_FIELD_NAME, False)
784
785 class AdminErrorList(forms.util.ErrorList):
786     """
787     Stores all errors for the form/formsets in an add/change stage view.
788     """
789     def __init__(self, form, inline_formsets):
790         if form.is_bound:
791             self.extend(form.errors.values())
792             for inline_formset in inline_formsets:
793                 self.extend(inline_formset.non_form_errors())
794                 for errors_in_inline_form in inline_formset.errors:
795                     self.extend(errors_in_inline_form.values())
Note: See TracBrowser for help on using the browser.