diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index c87d903..82e0b70 100644
a
|
b
|
class BaseModelAdmin(object):
|
108 | 108 | |
109 | 109 | # If we've got overrides for the formfield defined, use 'em. **kwargs |
110 | 110 | # passed to formfield_for_dbfield override the defaults. |
111 | | for klass in db_field.__class__.mro(): |
112 | | if klass in self.formfield_overrides: |
113 | | kwargs = dict(self.formfield_overrides[klass], **kwargs) |
114 | | return db_field.formfield(**kwargs) |
| 111 | for klass in db_field.__class__.mro(): |
| 112 | if klass in self.formfield_overrides: |
| 113 | kwargs = dict(self.formfield_overrides[klass], **kwargs) |
| 114 | return db_field.formfield(**kwargs) |
115 | 115 | |
116 | 116 | # For any other type of field, just call its formfield() method. |
117 | 117 | return db_field.formfield(**kwargs) |
… |
… |
class ModelAdmin(BaseModelAdmin):
|
336 | 336 | exclude = [] |
337 | 337 | else: |
338 | 338 | exclude = list(self.exclude) |
| 339 | # if exclude is an empty list we pass None to be consistant with the |
| 340 | # default on modelform_factory |
339 | 341 | defaults = { |
340 | 342 | "form": self.form, |
341 | 343 | "fields": fields, |
342 | | "exclude": exclude + kwargs.get("exclude", []), |
| 344 | "exclude": (exclude + kwargs.get("exclude", [])) or None, |
343 | 345 | "formfield_callback": curry(self.formfield_for_dbfield, request=request), |
344 | 346 | } |
345 | 347 | defaults.update(kwargs) |
… |
… |
class ModelAdmin(BaseModelAdmin):
|
437 | 439 | # want *any* actions enabled on this page. |
438 | 440 | if self.actions is None: |
439 | 441 | return [] |
440 | | |
| 442 | |
441 | 443 | actions = [] |
442 | | |
| 444 | |
443 | 445 | # Gather actions from the admin site first |
444 | 446 | for (name, func) in self.admin_site.actions: |
445 | 447 | description = getattr(func, 'short_description', name.replace('_', ' ')) |
446 | 448 | actions.append((func, name, description)) |
447 | | |
448 | | # Then gather them from the model admin and all parent classes, |
| 449 | |
| 450 | # Then gather them from the model admin and all parent classes, |
449 | 451 | # starting with self and working back up. |
450 | 452 | for klass in self.__class__.mro()[::-1]: |
451 | 453 | class_actions = getattr(klass, 'actions', []) |
452 | 454 | # Avoid trying to iterate over None |
453 | 455 | if not class_actions: |
454 | | continue |
| 456 | continue |
455 | 457 | actions.extend([self.get_action(action) for action in class_actions]) |
456 | | |
| 458 | |
457 | 459 | # get_action might have returned None, so filter any of those out. |
458 | 460 | actions = filter(None, actions) |
459 | | |
| 461 | |
460 | 462 | # Convert the actions into a SortedDict keyed by name |
461 | 463 | # and sorted by description. |
462 | 464 | actions.sort(lambda a,b: cmp(a[2].lower(), b[2].lower())) |
… |
… |
class ModelAdmin(BaseModelAdmin):
|
464 | 466 | (name, (func, name, desc)) |
465 | 467 | for func, name, desc in actions |
466 | 468 | ]) |
467 | | |
| 469 | |
468 | 470 | return actions |
469 | 471 | |
470 | 472 | def get_action_choices(self, request, default_choices=BLANK_CHOICE_DASH): |
… |
… |
class ModelAdmin(BaseModelAdmin):
|
488 | 490 | if callable(action): |
489 | 491 | func = action |
490 | 492 | action = action.__name__ |
491 | | |
| 493 | |
492 | 494 | # Next, look for a method. Grab it off self.__class__ to get an unbound |
493 | 495 | # method instead of a bound one; this ensures that the calling |
494 | 496 | # conventions are the same for functions and methods. |
495 | 497 | elif hasattr(self.__class__, action): |
496 | 498 | func = getattr(self.__class__, action) |
497 | | |
| 499 | |
498 | 500 | # Finally, look for a named method on the admin site |
499 | 501 | else: |
500 | 502 | try: |
501 | 503 | func = self.admin_site.get_action(action) |
502 | 504 | except KeyError: |
503 | 505 | return None |
504 | | |
| 506 | |
505 | 507 | if hasattr(func, 'short_description'): |
506 | 508 | description = func.short_description |
507 | 509 | else: |
… |
… |
class ModelAdmin(BaseModelAdmin):
|
664 | 666 | data = request.POST.copy() |
665 | 667 | data.pop(helpers.ACTION_CHECKBOX_NAME, None) |
666 | 668 | data.pop("index", None) |
667 | | |
| 669 | |
668 | 670 | # Use the action whose button was pushed |
669 | 671 | try: |
670 | 672 | data.update({'action': data.getlist('action')[action_index]}) |
… |
… |
class ModelAdmin(BaseModelAdmin):
|
673 | 675 | # POST data, so by deleting action it'll fail the validation check |
674 | 676 | # below. So no need to do anything here |
675 | 677 | pass |
676 | | |
| 678 | |
677 | 679 | action_form = self.action_form(data, auto_id=None) |
678 | 680 | action_form.fields['action'].choices = self.get_action_choices(request) |
679 | 681 | |
… |
… |
class ModelAdmin(BaseModelAdmin):
|
877 | 879 | app_label = opts.app_label |
878 | 880 | if not self.has_change_permission(request, None): |
879 | 881 | raise PermissionDenied |
880 | | |
| 882 | |
881 | 883 | # Check actions to see if any are available on this changelist |
882 | 884 | actions = self.get_actions(request) |
883 | | |
| 885 | |
884 | 886 | # Remove action checkboxes if there aren't any actions available. |
885 | 887 | list_display = list(self.list_display) |
886 | 888 | if not actions: |
… |
… |
class ModelAdmin(BaseModelAdmin):
|
888 | 890 | list_display.remove('action_checkbox') |
889 | 891 | except ValueError: |
890 | 892 | pass |
891 | | |
| 893 | |
892 | 894 | try: |
893 | 895 | cl = ChangeList(request, self.model, list_display, self.list_display_links, self.list_filter, |
894 | 896 | self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self.list_editable, self) |
… |
… |
class ModelAdmin(BaseModelAdmin):
|
901 | 903 | if ERROR_FLAG in request.GET.keys(): |
902 | 904 | return render_to_response('admin/invalid_setup.html', {'title': _('Database error')}) |
903 | 905 | return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1') |
904 | | |
| 906 | |
905 | 907 | # If the request was POSTed, this might be a bulk action or a bulk edit. |
906 | 908 | # Try to look up an action first, but if this isn't an action the POST |
907 | 909 | # will fall through to the bulk edit check, below. |
diff --git a/django/forms/models.py b/django/forms/models.py
index 010d3bf..8d59dfd 100644
a
|
b
|
def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
|
345 | 345 | formfield_callback=lambda f: f.formfield()): |
346 | 346 | # HACK: we should be able to construct a ModelForm without creating |
347 | 347 | # and passing in a temporary inner class |
348 | | class Meta: |
349 | | pass |
350 | | setattr(Meta, 'model', model) |
351 | | setattr(Meta, 'fields', fields) |
352 | | setattr(Meta, 'exclude', exclude) |
| 348 | attrs = {} |
| 349 | if fields is not None: |
| 350 | attrs['fields'] = fields |
| 351 | if exclude is not None: |
| 352 | attrs['exclude'] = exclude |
| 353 | attrs['model'] = model |
| 354 | parent = (object,) |
| 355 | if hasattr(form, 'Meta'): |
| 356 | parent = (form.Meta, object) |
| 357 | meta = type('Meta', parent, attrs) |
353 | 358 | class_name = model.__name__ + 'Form' |
354 | | return ModelFormMetaclass(class_name, (form,), {'Meta': Meta, |
| 359 | return ModelFormMetaclass(class_name, (form,), {'Meta': meta, |
355 | 360 | 'formfield_callback': formfield_callback}) |
356 | 361 | |
357 | 362 | |
diff --git a/tests/regressiontests/modeladmin/models.py b/tests/regressiontests/modeladmin/models.py
index 3a7d3f0..e11736a 100644
a
|
b
|
class Band(models.Model):
|
8 | 8 | name = models.CharField(max_length=100) |
9 | 9 | bio = models.TextField() |
10 | 10 | sign_date = models.DateField() |
11 | | |
| 11 | |
12 | 12 | def __unicode__(self): |
13 | 13 | return self.name |
14 | 14 | |
… |
… |
displayed because you forgot to add it to fields/fielsets
|
118 | 118 | |
119 | 119 | # Using `exclude`. |
120 | 120 | |
121 | | >>> class BandAdmin(ModelAdmin): |
122 | | ... exclude = ['bio'] |
123 | | >>> ma = BandAdmin(Band, site) |
124 | | >>> ma.get_form(request).base_fields.keys() |
| 121 | >>> class BandAdmin(ModelAdmin): |
| 122 | ... exclude = ['bio'] |
| 123 | >>> ma = BandAdmin(Band, site) |
| 124 | >>> ma.get_form(request).base_fields.keys() |
125 | 125 | ['name', 'sign_date'] |
126 | 126 | |
127 | 127 | # You can also pass a tuple to `exclude`. |
128 | | |
129 | | >>> class BandAdmin(ModelAdmin): |
130 | | ... exclude = ('bio',) |
131 | | >>> ma = BandAdmin(Band, site) |
132 | | >>> ma.get_form(request).base_fields.keys() |
| 128 | |
| 129 | >>> class BandAdmin(ModelAdmin): |
| 130 | ... exclude = ('bio',) |
| 131 | >>> ma = BandAdmin(Band, site) |
| 132 | >>> ma.get_form(request).base_fields.keys() |
133 | 133 | ['name', 'sign_date'] |
134 | | |
| 134 | |
135 | 135 | # Using `fields` and `exclude`. |
136 | 136 | |
137 | | >>> class BandAdmin(ModelAdmin): |
138 | | ... fields = ['name', 'bio'] |
139 | | ... exclude = ['bio'] |
140 | | >>> ma = BandAdmin(Band, site) |
141 | | >>> ma.get_form(request).base_fields.keys() |
| 137 | >>> class BandAdmin(ModelAdmin): |
| 138 | ... fields = ['name', 'bio'] |
| 139 | ... exclude = ['bio'] |
| 140 | >>> ma = BandAdmin(Band, site) |
| 141 | >>> ma.get_form(request).base_fields.keys() |
142 | 142 | ['name'] |
143 | 143 | |
144 | 144 | If we specify a form, it should use it allowing custom validation to work |
… |
… |
properly. This won't, however, break any of the admin widgets or media.
|
147 | 147 | >>> from django import forms |
148 | 148 | >>> class AdminBandForm(forms.ModelForm): |
149 | 149 | ... delete = forms.BooleanField() |
150 | | ... |
| 150 | ... |
151 | 151 | ... class Meta: |
152 | 152 | ... model = Band |
153 | 153 | |
… |
… |
blank=True for the model field. Finally, the widget should have the
|
262 | 262 | >>> list(cmafa.base_fields['transport'].widget.choices) |
263 | 263 | [('', u'None'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')] |
264 | 264 | |
| 265 | >>> class AdminConcertForm(forms.ModelForm): |
| 266 | ... class Meta: |
| 267 | ... model = Concert |
| 268 | ... exclude = ('transport',) |
| 269 | |
| 270 | >>> class ConcertAdmin(ModelAdmin): |
| 271 | ... form = AdminConcertForm |
| 272 | |
| 273 | >>> ma = ConcertAdmin(Concert, site) |
| 274 | >>> ma.get_form(request).base_fields.keys() |
| 275 | ['main_band', 'opening_band', 'day'] |
| 276 | |
| 277 | >>> class AdminConcertForm(forms.ModelForm): |
| 278 | ... extra = forms.CharField() |
| 279 | ... class Meta: |
| 280 | ... model = Concert |
| 281 | ... fields = ['extra', 'transport'] |
| 282 | |
| 283 | >>> class ConcertAdmin(ModelAdmin): |
| 284 | ... form = AdminConcertForm |
| 285 | |
| 286 | >>> ma = ConcertAdmin(Concert, site) |
| 287 | >>> ma.get_form(request).base_fields.keys() |
| 288 | ['extra', 'transport'] |
| 289 | |
265 | 290 | >>> band.delete() |
266 | 291 | |
267 | 292 | # ModelAdmin Option Validation ################################################ |
… |
… |
Traceback (most recent call last):
|
371 | 396 | ... |
372 | 397 | ImproperlyConfigured: Both fieldsets and fields are specified in ValidationTestModelAdmin. |
373 | 398 | |
374 | | >>> class ValidationTestModelAdmin(ModelAdmin): |
375 | | ... fieldsets = [(None, {'fields': ['name', 'name']})] |
376 | | >>> validate(ValidationTestModelAdmin, ValidationTestModel) |
377 | | Traceback (most recent call last): |
378 | | ... |
| 399 | >>> class ValidationTestModelAdmin(ModelAdmin): |
| 400 | ... fieldsets = [(None, {'fields': ['name', 'name']})] |
| 401 | >>> validate(ValidationTestModelAdmin, ValidationTestModel) |
| 402 | Traceback (most recent call last): |
| 403 | ... |
379 | 404 | ImproperlyConfigured: There are duplicate field(s) in ValidationTestModelAdmin.fieldsets |
380 | 405 | |
381 | 406 | >>> class ValidationTestModelAdmin(ModelAdmin): |