Ticket #10208: custom-modeladmin-form.3.diff
File custom-modeladmin-form.3.diff, 12.7 KB (added by , 16 years ago) |
---|
-
django/contrib/admin/options.py
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index c87d903..9599d90 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. … … class InlineModelAdmin(BaseModelAdmin): 1138 1140 exclude = [] 1139 1141 else: 1140 1142 exclude = list(self.exclude) 1143 # if exclude is an empty list we use None, since that's the actual 1144 # default 1141 1145 defaults = { 1142 1146 "form": self.form, 1143 1147 "formset": self.formset, 1144 1148 "fk_name": self.fk_name, 1145 1149 "fields": fields, 1146 "exclude": exclude + kwargs.get("exclude", []),1150 "exclude": (exclude + kwargs.get("exclude", [])) or None, 1147 1151 "formfield_callback": curry(self.formfield_for_dbfield, request=request), 1148 1152 "extra": self.extra, 1149 1153 "max_num": self.max_num, -
django/forms/models.py
diff --git a/django/forms/models.py b/django/forms/models.py index 010d3bf..d67149a 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 … … def inlineformset_factory(parent_model, model, form=ModelForm, 613 618 # enforce a max_num=1 when the foreign key to the parent model is unique. 614 619 if fk.unique: 615 620 max_num = 1 616 if fields is not None:617 fields = list(fields)618 fields.append(fk.name)619 else:620 # get all the fields for this model that will be generated.621 fields = fields_for_model(model, fields, exclude, formfield_callback).keys()622 fields.append(fk.name)623 621 kwargs = { 624 622 'form': form, 625 623 'formfield_callback': formfield_callback, -
tests/regressiontests/modeladmin/models.py
diff --git a/tests/regressiontests/modeladmin/models.py b/tests/regressiontests/modeladmin/models.py index 3a7d3f0..0dc347d 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 … … class ValidationTestInlineModel(models.Model): 37 37 38 38 __test__ = {'API_TESTS': """ 39 39 40 >>> from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL40 >>> from django.contrib.admin.options import ModelAdmin, TabularInline, HORIZONTAL, VERTICAL 41 41 >>> from django.contrib.admin.sites import AdminSite 42 42 43 43 None of the following tests really depend on the content of the request, so … … 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 290 >>> class ConcertInline(TabularInline): 291 ... form = AdminConcertForm 292 ... model = Concert 293 ... fk_name = 'main_band' 294 295 >>> class BandAdmin(ModelAdmin): 296 ... inlines = [ 297 ... ConcertInline 298 ... ] 299 300 >>> ma = BandAdmin(Band, site) 301 >>> list(ma.get_formsets(request))[0]().forms[0].fields.keys() 302 ['extra', 'transport', 'id', 'DELETE', 'main_band'] 303 304 265 305 >>> band.delete() 266 306 267 307 # ModelAdmin Option Validation ################################################ … … Traceback (most recent call last): 371 411 ... 372 412 ImproperlyConfigured: Both fieldsets and fields are specified in ValidationTestModelAdmin. 373 413 374 >>> class ValidationTestModelAdmin(ModelAdmin): 375 ... fieldsets = [(None, {'fields': ['name', 'name']})] 376 >>> validate(ValidationTestModelAdmin, ValidationTestModel) 377 Traceback (most recent call last): 378 ... 414 >>> class ValidationTestModelAdmin(ModelAdmin): 415 ... fieldsets = [(None, {'fields': ['name', 'name']})] 416 >>> validate(ValidationTestModelAdmin, ValidationTestModel) 417 Traceback (most recent call last): 418 ... 379 419 ImproperlyConfigured: There are duplicate field(s) in ValidationTestModelAdmin.fieldsets 380 420 381 421 >>> class ValidationTestModelAdmin(ModelAdmin):