code

Django 양식, 상속 및 양식 필드 순서

codestyles 2020. 11. 23. 08:11
반응형

Django 양식, 상속 및 양식 필드 순서


내 웹 사이트에서 Django 양식을 사용하고 있으며 필드 순서를 제어하고 싶습니다.

내 양식을 정의하는 방법은 다음과 같습니다.

class edit_form(forms.Form):
    summary = forms.CharField()
    description = forms.CharField(widget=forms.TextArea)


class create_form(edit_form):
    name = forms.CharField()

이름은 변경할 수 없으며 엔티티가 생성 될 때만 나열되어야합니다. 일관성과 DRY 원칙을 추가하기 위해 상속을 사용합니다. 실제로 완전히 예상되는 오류가 아닌 것은 이름 필드가보기 / html에서 마지막에 나열되지만 이름 필드가 요약 및 설명 위에 있기를 원한다는 것입니다. 요약과 설명을 create_form에 복사하고 상속을 잃어 버림으로써 쉽게 고칠 수 있다는 것을 알고 있지만 이것이 가능한지 알고 싶습니다.

왜? edit_form에 100 개의 필드가 있고 create_form의 맨 위에 10 개의 필드를 추가해야한다고 가정 해보십시오. 두 양식을 복사하고 유지하는 것은 그렇게 섹시 해 보이지 않을 것입니다. (이것은 내 경우 아니라 단지 예제를 만드는 중입니다)

그렇다면이 동작을 어떻게 재정의 할 수 있습니까?

편집하다:

명백하게 끔찍한 해킹 (.field 속성 조작)을 거치지 않고이를 수행하는 적절한 방법은 없습니다. .field 속성은 SortedDict (Django의 내부 데이터 구조 중 하나)로 키 : 값 쌍을 재정렬하는 방법을 제공하지 않습니다. 주어진 인덱스에 항목을 삽입하는 방법을 제공하지만 클래스 멤버에서 생성자로 항목을 이동합니다. 이 방법은 작동하지만 코드를 읽기 어렵게 만듭니다. 내가 적합하다고 생각하는 유일한 다른 방법은 대부분의 상황에서 최적이 아닌 프레임 워크 자체를 수정하는 것입니다.

간단히 말해서 코드는 다음과 같이됩니다.

class edit_form(forms.Form):
    summary = forms.CharField()
    description = forms.CharField(widget=forms.TextArea)


class create_form(edit_form):
    def __init__(self,*args,**kwargs):
        forms.Form.__init__(self,*args,**kwargs)

        self.fields.insert(0,'name',forms.CharField())

그게 나를 닥쳐 :)


Django 1.9 이상에서

Django 1.9는 클래스에서 선언 순서에 관계없이 필드를 정렬 할 수 있는 새로운 Form속성을 추가합니다 field_order.

class MyForm(forms.Form):
    summary = forms.CharField()
    description = forms.CharField(widget=forms.TextArea)
    author = forms.CharField()
    notes = form.CharField()

    field_order = ['author', 'summary']

누락 된 필드 field_order는 클래스에서 순서 유지하고 목록에 지정된 필드 뒤에 추가됩니다. 위의 예는 다음 순서로 필드를 생성합니다.['author', 'summary', 'description', 'notes']

문서 참조 : https://docs.djangoproject.com/en/stable/ref/forms/api/#notes-on-field-ordering

최대 Django 1.6

나는 이와 같은 문제가 있었고 Django CookBook 에서 필드를 재정렬하는 또 다른 기술을 발견했습니다 .

class EditForm(forms.Form):
    summary = forms.CharField()
    description = forms.CharField(widget=forms.TextArea)


class CreateForm(EditForm):
    name = forms.CharField()

    def __init__(self, *args, **kwargs):
        super(CreateForm, self).__init__(*args, **kwargs)
        self.fields.keyOrder = ['name', 'summary', 'description']

Django 1.9에서 : https://docs.djangoproject.com/en/1.10/ref/forms/api/#notes-on-field-ordering


원래 답변 : Django 1.9는 기본적으로 다음을 사용하여 양식에서 이것을 지원합니다 field_order.

class MyForm(forms.Form):
    ...
    field_order = ['field_1', 'field_2']
    ...

https://github.com/django/django/commit/28986da4ca167ae257abcaf7caea230eca2bcd80


Selene이 게시 한 솔루션을 사용했지만 keyOrder에 할당되지 않은 모든 필드가 제거되었음을 발견했습니다. 내가 서브 클래 싱하는 폼은 많은 필드를 가지고있어서 이것은 나에게 잘 맞지 않았습니다. akaihola의 답변을 사용하여 문제를 해결하기 위해이 함수를 코딩했지만 Selene처럼 작동 throw_away하려면 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))

이것이 내 코드에서 사용하는 방법입니다.

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'])

어느 시점에서 필드 순서의 기본 구조가 SordedDict파이썬 표준에 특화된 장고에서 변경된 것으로 보입니다.OrderedDict

따라서 1.7에서는 다음을 수행해야했습니다.

from collections import OrderedDict

class MyForm(forms.Form):

    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        original_fields = self.fields
        new_order = OrderedDict()
        for key in ['first', 'second', ... 'last']:
            new_order[key] = original_fields[key]
        self.fields = new_order

나는 누군가가 그것을 두세 줄로 골프를 칠 수 있다고 확신하지만, 그래서 나는 그것이 어떻게 작동하는지 명확하게 보여주는 것이 클리버보다 낫다고 생각합니다.


필드를 주문하는 데코레이터를 만들 수도 있습니다 (Joshua의 솔루션에서 영감을 얻음).

def order_fields(*field_list):
    def decorator(form):
        original_init = form.__init__
        def init(self, *args, **kwargs):
            original_init(self, *args, **kwargs)        
            for field in field_list[::-1]:
                self.fields.insert(0, field, self.fields.pop(field))
        form.__init__ = init
        return form            
    return decorator

이렇게하면 데코레이터에 전달 된 모든 필드가 먼저 표시됩니다. 다음과 같이 사용할 수 있습니다.

@order_fields('name')
class CreateForm(EditForm):
    name = forms.CharField()

허용되는 답변의 접근 방식은 Django 1.7에서 변경된 내부 Django 양식 API를 사용합니다. 프로젝트 팀의 의견은 애초에 사용해서는 안된다는 것입니다. 이제이 기능을 사용하여 양식을 재정렬합니다. 이 코드는 다음을 사용합니다 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])))

다음과 같은 수업에서 사용합니다.

class ChangeOpenQuestion(ChangeMultipleChoice):

    def __init__(self, *args, **kwargs):
        super(ChangeOpenQuestion, self).__init__(*args, **kwargs)
        key_order = ['title',
                     'question',
                     'answer',
                     'correct_answer',
                     'incorrect_answer']

        self.fields = reorder_fields(self.fields, key_order)

장고의 내부가 필드 순서를 추적하는 방법에 대한 이 SO 질문 의 메모를 참조하십시오 . 답변에는 원하는대로 필드를 "재정렬"하는 방법에 대한 제안이 포함됩니다 (결국 .fields속성 을 엉망으로 만드는 것으로 요약됩니다 ).


필드 순서를 변경하는 다른 방법 :

팝 앤 삽입 :

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

팝 앤 추가 :

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

모두 팝업 및 추가 :

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

주문 사본 :

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.


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.


I built a form 'ExRegistrationForm' inherited from the 'RegistrationForm' from Django-Registration-Redux. I faced two issues, one of which was reordering the fields on the html output page once the new form had been created.

I solved them as follows:

1. ISSUE 1: Remove Username from the Registration Form: In my_app.forms.py

    class ExRegistrationForm(RegistrationForm):
          #Below 2 lines extend the RegistrationForm with 2 new fields firstname & lastname
          first_name = forms.CharField(label=(u'First Name'))
          last_name = forms.CharField(label=(u'Last Name'))

          #Below 3 lines removes the username from the fields shown in the output of the this form
          def __init__(self, *args, **kwargs):
          super(ExRegistrationForm, self).__init__(*args, **kwargs)
          self.fields.pop('username')

2. ISSUE 2: Make FirstName and LastName appear on top: In templates/registration/registration_form.html

You can individually display the fields in the order that you want. This would help in case the number of fields are less, but not if you have a large number of fields where it becomes practically impossible to actually write them in the form.

     {% extends "base.html" %}
     {% load i18n %}

     {% block content %}
     <form method="post" action=".">
          {% csrf_token %}

          #The Default html is: {{ form.as_p }} , which can be broken down into individual elements as below for re-ordering.
          <p>First Name: {{ form.first_name }}</p>
          <p>Last Name:  {{ form.last_name }}</p>
          <p>Email: {{ form.email }}</p>
          <p>Password: {{ form.password1 }}</p>
          <p>Confirm Password: {{ form.password2 }}</p>

          <input type="submit" value="{% trans 'Submit' %}" />
      </form>
      {% endblock %}

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

참고URL : https://stackoverflow.com/questions/913589/django-forms-inheritance-and-order-of-form-fields

반응형