Django forms, inheritance and order of form fields

前端 未结 11 1627
终归单人心
终归单人心 2020-12-12 12:15

I\'m using Django forms in my website and would like to control the order of the fields.

Here\'s how I define my forms:

class edit_form(forms.Form):
         


        
相关标签:
11条回答
  • 2020-12-12 12:52

    Based on an answer by @akaihola and updated to work with latest Django 1.5 as self.fields.insert is being depreciated.

    from easycontactus.forms import *
    from django import forms
    class  CustomEasyContactUsForm(EasyContactUsForm):
        ### form settings and configuration
        CAPTHCA_PHRASE = 'igolf'
    
        ### native methods
        def __init__(self, *args, **kwargs):
            super(CustomEasyContactUsForm, self).__init__(*args, **kwargs)
            # re-order placement of added attachment field 
            self.fields.keyOrder.insert(self.fields.keyOrder.index('captcha'),
                                        self.fields.keyOrder.pop(self.fields.keyOrder.index('attachment'))
                                        )
    
        ### field defintitions
        attachment = forms.FileField()
    

    In the above we are extending an EasyContactUsForm base class as it is defined in django-easycontactus package.

    0 讨论(0)
  • 2020-12-12 12:53

    I used the solution posted by Selene but found that it removed all fields which weren't assigned to keyOrder. The form that I'm subclassing has a lot of fields so this didn't work very well for me. I coded up this function to solve the problem using akaihola's answer, but if you want it to work like Selene's all you need to do is set throw_away to True.

    def order_fields(form, field_list, throw_away=False):
        """
        Accepts a form and a list of dictionary keys which map to the
        form's fields. After running the form's fields list will begin
        with the fields in field_list. If throw_away is set to true only
        the fields in the field_list will remain in the form.
    
        example use:
        field_list = ['first_name', 'last_name']
        order_fields(self, field_list)
        """
        if throw_away:
            form.fields.keyOrder = field_list
        else:
            for field in field_list[::-1]:
                form.fields.insert(0, field, form.fields.pop(field))
    

    This is how I'm using it in my own code:

    class NestableCommentForm(ExtendedCommentSecurityForm):
        # TODO: Have min and max length be determined through settings.
        comment = forms.CharField(widget=forms.Textarea, max_length=100)
        parent_id = forms.IntegerField(widget=forms.HiddenInput, required=False)
    
        def __init__(self, *args, **kwargs):
            super(NestableCommentForm, self).__init__(*args, **kwargs)
            order_fields(self, ['comment', 'captcha'])
    
    0 讨论(0)
  • 2020-12-12 12:53

    The above answers are right but incomplete. They only work if all the fields are defined as class variables. What about dynamic form fields which have to be defined in the intitialiser (__init__)?

    from django import forms
    
    class MyForm(forms.Form):
        field1 = ...
        field2 = ...
    
        field_order = ['val', 'field1', 'field2']
    
        def __init__(self, val_list, *args, **kwargs):
            super(MyForm, self).__init__(*args, **kwargs)
            vals = zip(val_list, val_list)
            self.fields['val'] = forms.CharField(choices=vals)
    

    The above will never work for val but will work for field1 and field2 (if we reorder them). You might want to try defining field_order in the initialiser:

    class MyForm(forms.Form):
        # other fields
    
        def __init__(self, val_list, *args, **kwargs):
            super(MyForm, self).__init__(*args, **kwargs)
            vals = zip(val_list, val_list)
            self.fields['val'] = forms.CharField(choices=vals)
            self.field_order = ['val', 'field1', 'field2']
    

    but this will also fail because the field order is fixed before the call to super().

    Therefore the only solution is the constructor (__new__) and set field_order to a class variable.

    class MyForm(forms.Form):
        # other fields
    
        field_order = ['val', 'field1', 'field2']
    
        def __new__(cls, val_list, *args, **kwargs):
            form = super(MyForm, cls).__new__(cls)
            vals = zip(val_list, val_list)
            form.base_fields['val'] = forms.CharField(choices=vals)
            return form
    
    0 讨论(0)
  • 2020-12-12 12:59

    Alternate methods for changing the field order:

    Pop-and-insert:

    self.fields.insert(0, 'name', self.fields.pop('name'))
    

    Pop-and-append:

    self.fields['summary'] = self.fields.pop('summary')
    self.fields['description'] = self.fields.pop('description')
    

    Pop-and-append-all:

    for key in ('name', 'summary', 'description'):
        self.fields[key] = self.fields.pop(key)
    

    Ordered-copy:

    self.fields = SortedDict( [ (key, self.fields[key])
                                for key in ('name', 'summary' ,'description') ] )
    

    But Selene's approach from the Django CookBook still feels clearest of all.

    0 讨论(0)
  • 2020-12-12 13:01

    See the notes in this SO question on the way Django's internals keep track of field order; the answers include suggestions on how to "reorder" fields to your liking (in the end it boils down to messing with the .fields attribute).

    0 讨论(0)
  • 2020-12-12 13:02

    The accepted answer's approach makes use of an internal Django forms API that was changed in Django 1.7. The project team's opinion is that it should never have been used in the first place. I now use this function to reorder my forms. This code makes use of an OrderedDict:

    def reorder_fields(fields, order):
        """Reorder form fields by order, removing items not in order.
    
        >>> reorder_fields(
        ...     OrderedDict([('a', 1), ('b', 2), ('c', 3)]),
        ...     ['b', 'c', 'a'])
        OrderedDict([('b', 2), ('c', 3), ('a', 1)])
        """
        for key, v in fields.items():
            if key not in order:
                del fields[key]
    
        return OrderedDict(sorted(fields.items(), key=lambda k: order.index(k[0])))
    

    Which I use in classes like this:

    class ChangeOpenQuestionForm(ChangeMultipleChoiceForm):
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            key_order = ['title',
                         'question',
                         'answer',
                         'correct_answer',
                         'incorrect_answer']
    
            self.fields = reorder_fields(self.fields, key_order)
    
    0 讨论(0)
提交回复
热议问题