Django

Code

root/django/trunk/django/contrib/admin/helpers.py

Revision 9412, 6.9 kB (checked in by brosner, 1 week ago)

Fixed #9498 -- Handle a formset correctly when the foreign key is not available (for now).

This case pops up with generic foreign key inlines after [9297]. Added tests
to handle future regressions with generic foreign key inlines in the admin.

Thanks markus and danielr for patches.

Line 
1 from django import forms
2 from django.conf import settings
3 from django.utils.html import escape
4 from django.utils.safestring import mark_safe
5 from django.utils.encoding import force_unicode
6 from django.contrib.admin.util import flatten_fieldsets
7 from django.contrib.contenttypes.models import ContentType
8
9 class AdminForm(object):
10     def __init__(self, form, fieldsets, prepopulated_fields):
11         self.form, self.fieldsets = form, fieldsets
12         self.prepopulated_fields = [{
13             'field': form[field_name],
14             'dependencies': [form[f] for f in dependencies]
15         } for field_name, dependencies in prepopulated_fields.items()]
16
17     def __iter__(self):
18         for name, options in self.fieldsets:
19             yield Fieldset(self.form, name, **options)
20
21     def first_field(self):
22         try:
23             fieldset_name, fieldset_options = self.fieldsets[0]
24             field_name = fieldset_options['fields'][0]
25             if not isinstance(field_name, basestring):
26                 field_name = field_name[0]
27             return self.form[field_name]
28         except (KeyError, IndexError):
29             pass
30         try:
31             return iter(self.form).next()
32         except StopIteration:
33             return None
34
35     def _media(self):
36         media = self.form.media
37         for fs in self:
38             media = media + fs.media
39         return media
40     media = property(_media)
41
42 class Fieldset(object):
43     def __init__(self, form, name=None, fields=(), classes=(), description=None):
44         self.form = form
45         self.name, self.fields = name, fields
46         self.classes = u' '.join(classes)
47         self.description = description
48
49     def _media(self):
50         if 'collapse' in self.classes:
51             return forms.Media(js=['%sjs/admin/CollapsedFieldsets.js' % settings.ADMIN_MEDIA_PREFIX])
52         return forms.Media()
53     media = property(_media)
54
55     def __iter__(self):
56         for field in self.fields:
57             yield Fieldline(self.form, field)
58
59 class Fieldline(object):
60     def __init__(self, form, field):
61         self.form = form # A django.forms.Form instance
62         if isinstance(field, basestring):
63             self.fields = [field]
64         else:
65             self.fields = field
66
67     def __iter__(self):
68         for i, field in enumerate(self.fields):
69             yield AdminField(self.form, field, is_first=(i == 0))
70
71     def errors(self):
72         return mark_safe(u'\n'.join([self.form[f].errors.as_ul() for f in self.fields]).strip('\n'))
73
74 class AdminField(object):
75     def __init__(self, form, field, is_first):
76         self.field = form[field] # A django.forms.BoundField instance
77         self.is_first = is_first # Whether this field is first on the line
78         self.is_checkbox = isinstance(self.field.field.widget, forms.CheckboxInput)
79
80     def label_tag(self):
81         classes = []
82         if self.is_checkbox:
83             classes.append(u'vCheckboxLabel')
84             contents = force_unicode(escape(self.field.label))
85         else:
86             contents = force_unicode(escape(self.field.label)) + u':'
87         if self.field.field.required:
88             classes.append(u'required')
89         if not self.is_first:
90             classes.append(u'inline')
91         attrs = classes and {'class': u' '.join(classes)} or {}
92         return self.field.label_tag(contents=contents, attrs=attrs)
93
94 class InlineAdminFormSet(object):
95     """
96     A wrapper around an inline formset for use in the admin system.
97     """
98     def __init__(self, inline, formset, fieldsets):
99         self.opts = inline
100         self.formset = formset
101         self.fieldsets = fieldsets
102
103     def __iter__(self):
104         for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()):
105             yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, original)
106         for form in self.formset.extra_forms:
107             yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, None)
108
109     def fields(self):
110         fk = getattr(self.formset, "fk", None)
111         for field_name in flatten_fieldsets(self.fieldsets):
112             if fk and fk.name == field_name:
113                 continue
114             yield self.formset.form.base_fields[field_name]
115
116     def _media(self):
117         media = self.opts.media + self.formset.media
118         for fs in self:
119             media = media + fs.media
120         return media
121     media = property(_media)
122
123 class InlineAdminForm(AdminForm):
124     """
125     A wrapper around an inline form for use in the admin system.
126     """
127     def __init__(self, formset, form, fieldsets, prepopulated_fields, original):
128         self.formset = formset
129         self.original = original
130         if original is not None:
131             self.original.content_type_id = ContentType.objects.get_for_model(original).pk
132         self.show_url = original and hasattr(original, 'get_absolute_url')
133         super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields)
134    
135     def __iter__(self):
136         for name, options in self.fieldsets:
137             yield InlineFieldset(self.formset, self.form, name, **options)
138    
139     def field_count(self):
140         # tabular.html uses this function for colspan value.
141         num_of_fields = 1 # always has at least one field
142         num_of_fields += len(self.fieldsets[0][1]["fields"])
143         if self.formset.can_order:
144             num_of_fields += 1
145         if self.formset.can_delete:
146             num_of_fields += 1
147         return num_of_fields
148
149     def pk_field(self):
150         return AdminField(self.form, self.formset._pk_field.name, False)
151    
152     def fk_field(self):
153         fk = getattr(self.formset, "fk", None)
154         if fk:
155             return AdminField(self.form, fk.name, False)
156         else:
157             return ""
158
159     def deletion_field(self):
160         from django.forms.formsets import DELETION_FIELD_NAME
161         return AdminField(self.form, DELETION_FIELD_NAME, False)
162
163     def ordering_field(self):
164         from django.forms.formsets import ORDERING_FIELD_NAME
165         return AdminField(self.form, ORDERING_FIELD_NAME, False)
166
167 class InlineFieldset(Fieldset):
168     def __init__(self, formset, *args, **kwargs):
169         self.formset = formset
170         super(InlineFieldset, self).__init__(*args, **kwargs)
171        
172     def __iter__(self):
173         fk = getattr(self.formset, "fk", None)
174         for field in self.fields:
175             if fk and fk.name == field:
176                 continue
177             yield Fieldline(self.form, field)
178            
179 class AdminErrorList(forms.util.ErrorList):
180     """
181     Stores all errors for the form/formsets in an add/change stage view.
182     """
183     def __init__(self, form, inline_formsets):
184         if form.is_bound:
185             self.extend(form.errors.values())
186             for inline_formset in inline_formsets:
187                 self.extend(inline_formset.non_form_errors())
188                 for errors_in_inline_form in inline_formset.errors:
189                     self.extend(errors_in_inline_form.values())
Note: See TracBrowser for help on using the browser.