Dynamic fields in Django Admin

后端 未结 9 1461
失恋的感觉
失恋的感觉 2020-12-05 05:18

I want to have additional fields regarding value of one field. Therefor I build a custom admin form to add some new fields.

Related to the blogpost of jacobian 1 thi

相关标签:
9条回答
  • 2020-12-05 05:31

    This works for adding dynamic fields in Django 1.9.3, using just a ModelAdmin class (no ModelForm) and by overriding get_fields. I don't know yet how robust it is:

    class MyModelAdmin(admin.ModelAdmin):
    
        fields = [('title','status', ), 'description', 'contact_person',]
        exclude = ['material']
    
        def get_fields(self, request, obj=None):
            gf = super(MyModelAdmin, self).get_fields(request, obj)
    
            new_dynamic_fields = [
                ('test1', forms.CharField()),
                ('test2', forms.ModelMultipleChoiceField(MyModel.objects.all(), widget=forms.CheckboxSelectMultiple)),
            ]
    
            #without updating get_fields, the admin form will display w/o any new fields
            #without updating base_fields or declared_fields, django will throw an error: django.core.exceptions.FieldError: Unknown field(s) (test) specified for MyModel. Check fields/fieldsets/exclude attributes of class MyModelAdmin.
    
            for f in new_dynamic_fields:
                #`gf.append(f[0])` results in multiple instances of the new fields
                gf = gf + [f[0]]
                #updating base_fields seems to have the same effect
                self.form.declared_fields.update({f[0]:f[1]})
            return gf
    
    0 讨论(0)
  • 2020-12-05 05:32

    I for a long time could not solve a problem with dynamic addition of fields. The solution "little_birdie" really works. Thank you Birdie)) The only nuance is: "Self.declared_fieldsets" should be replaced with "self.fieldsets".

    #kwargs['fields'] =  flatten_fieldsets(self.declared_fieldsets)
    kwargs['fields'] =  flatten_fieldsets(self.fieldsets)
    

    I used version 1.10. Perhaps something has changed.

    If someone finds an even simpler and elegant solution, show here.

    Thanks to all )))

    0 讨论(0)
  • 2020-12-05 05:34

    The accepted answer above worked in older versions of django, and that's how I was doing it. This has now broken in later django versions (I am on 1.68 at the moment, but even that is old now).

    The reason it is now broken is because any fields within fieldsets you return from ModelAdmin.get_fieldsets() are ultimately passed as the fields=parameter to modelform_factory(), which will give you an error because the fields on your list do not exist (and will not exist until your form is instantiated and its __init__ is called).

    In order to fix this, we must override ModelAdmin.get_form() and supply a list of fields that does not include any extra fields that will be added later. The default behavior of get_form is to call get_fieldsets() for this information, and we must prevent that from happening:

    # CHOOSE ONE
    # newer versions of django use this
    from django.contrib.admin.utils import flatten_fieldsets
    # if above does not work, use this
    from django.contrib.admin.util import flatten_fieldsets
    
    class MyModelForm(ModelForm):
      def __init__(self, *args, **kwargs):
          super(MyModelForm, self).__init__(*args, **kwargs)
          # add your dynamic fields here..
          for fieldname in ('foo', 'bar', 'baz',):
              self.fields[fieldname] = form.CharField()
    
    class MyAdmin(ModelAdmin): 
       form = MyModelForm
    
        fieldsets = [
           # here you put the list of fieldsets you want displayed.. only
           # including the ones that are not dynamic
        ]
    
        def get_form(self, request, obj=None, **kwargs):
            # By passing 'fields', we prevent ModelAdmin.get_form from
            # looking up the fields itself by calling self.get_fieldsets()
            # If you do not do this you will get an error from 
            # modelform_factory complaining about non-existent fields.
    
            # use this line only for django before 1.9 (but after 1.5??)
            kwargs['fields'] =  flatten_fieldsets(self.declared_fieldsets)
            # use this line only for django 1.9 and later 
            kwargs['fields'] =  flatten_fieldsets(self.fieldsets)
    
            return super(MyAdmin, self).get_form(request, obj, **kwargs)
    
        def get_fieldsets(self, request, obj=None):
            fieldsets = super(MyAdmin, self).get_fieldsets(request, obj)
    
            newfieldsets = list(fieldsets)
            fields = ['foo', 'bar', 'baz']
            newfieldsets.append(['Dynamic Fields', { 'fields': fields }])
    
            return newfieldsets
    
    0 讨论(0)
  • 2020-12-05 05:35

    Stephan's answer is elegant, but when I used in in dj1.6 it required the field to be a tuple. The complete solution looked like this:

    class ProductForm(ModelForm):
        foo = CharField(label='foo')
    
    
    class ProductAdmin(admin.ModelAdmin):
        form = ProductForm
        def get_fieldsets(self, request, obj=None):
            fieldsets = super(ProductAdmin, self).get_fieldsets(request, obj)
            fieldsets[0][1]['fields'] += ('foo', ) 
            return fieldsets
    
    0 讨论(0)
  • 2020-12-05 05:42

    While Jacob's post might work all right for regular ModelForms (even though it's more than a year and a half old), the admin is a somewhat different matter.

    All the declarative way of defining models, forms ModelAdmins and whatnot makes heavy use of metaclasses and class introspection. Same with the admin – when you tell a ModelAdmin to use a specific form istead of creating a default one, it introspects the class. It gets the list of fields and other stuff from the class itself without instantiating it.

    Your custom class, however, does not define the extra form field at class level, instead it dynamically adds one after it has been instantiated – that's too late for the ModelAdmin to recognize this change.

    One way to go about your problem might be to subclass ModelAdmin and override its get_fieldsets method to actually instantiate the ModelForm class and get the list of fields from the instance instead of the class. You'll have to keep in mind, though, that this might be somewhat slower than the default implementation.

    0 讨论(0)
  • 2020-12-05 05:43

    Here is a solution to the problem. Thanks to koniiiik i tried to solve this by extending the *get_fieldsets* method

    class ProductAdmin(admin.ModelAdmin):
        def get_fieldsets(self, request, obj=None):
            fieldsets = super(ProductAdmin, self).get_fieldsets(request, obj)
            fieldsets[0][1]['fields'] += ['foo'] 
            return fieldsets
    

    If you use multiple fieldsets be sure to add the to the right fieldset by using the appropriate index.

    0 讨论(0)
提交回复
热议问题