2009-05-27 4 views
59

Я использую Django-формы на своем веб-сайте и хочу контролировать порядок полей.Django формы, наследование и порядок полей формы

Вот как я определяю мои формы:

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


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

Имя неизменен и должны быть перечислены только тогда, когда создается объект. Я использую наследование, чтобы добавить последовательность и СУХИЕ принципы. То, что происходит, что не является ошибочным, на самом деле полностью ожидаемым, состоит в том, что поле имени указано последним в представлении/html, но я бы хотел, чтобы поле имени находилось поверх резюме и описания. Я понимаю, что могу легко исправить это, скопировав резюме и описание в create_form и потеряв наследование, но я хотел бы знать, возможно ли это.

Почему? Представьте, что у вас есть 100 полей в edit_form и вам нужно добавить 10 полей вверху в create_form - копирование и сохранение двух форм не выглядят такими сексуальными. (Это не мой случай, я просто составляю пример)

Итак, как я могу отменить это поведение?

Edit:

Видимо нет правильного способа сделать это, не проходя через неприятное хаки (возился с .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()) 

Это меня заткнуть :)

+4

Это уже много лет спустя. Я уверен, что вы уже знаете это, но имена классов в python всегда должны быть «CamelCased». Только имена методов называются «named_with_underscore». –

+0

Вы имели в виду «camelCased»? :) – jball037

+1

TitleCase - правильный термин, я считаю. –

ответ

95

У меня была такая же проблема, и я нашел другую технику для изменения порядка полей в 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'] 
+0

Интересный ... мог бы работать – hannson

+6

Кажется, что это работает, и он ошибочно полагает, что SortedDict «не предоставляет никакого способа изменить порядок ключей: пары значений». – akaihola

+1

Это замечательно, но не забудьте использовать его только с формами, полученными из 'forms.Form'. В случае 'forms.ModelForm' вы должны установить это в вложенном« классе Meta »в атрибуте« fields ». См. Здесь: http://stackoverflow.com/questions/2893471/how-can-i-order-fields-in-django-modelform –

3

Смотрите примечания в this SO question на пути Внутренности Джанго отслеживать порядок полей; ответы включают предложения о том, как «переупорядочить» поля по своему вкусу (в конце концов это сводится к возиться с атрибутом .fields).

2

Альтернативные методы для изменения порядка полей:

Поп-и-вставка:

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

Поп-и-конкатенирующего все:

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

Но подход Селены из Джанго CookBook все еще чувствует себя яснее всего.

+0

Вы не можете вставить в 'OrderedDict' – chhantyal

11

Я использовал решение, отправленное Selene, но обнаружил, что он удалил все поля, которые не были назначены keyOrder. Форма, в которой я выполняю подклассы, имеет много полей, поэтому для меня это не очень хорошо. Я закодировал эту функцию, чтобы решить проблему, используя ответ akaihola, но если вы хотите, чтобы она работала, как и все, что вам нужно сделать, это установить 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']) 
+1

Мне это очень нравится! Благодарю. –

7

Вы также можете создать декоратор на заказ поля (вдохновленные решениями Иешуа):

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() 
1

на основе ответа по @akaihola и обновлены для работы с последними Django 1.5, как self.fields.insert в настоящее время 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() 

Выше мы расширяете EasyContactUsForm базовый класс, как это определено в django-easycontactus пакете.

10

Похоже, что в какой-то момент базовая структура порядка поля была изменена с Джанго конкретным 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 

Я уверен, что кто-то может играть в гольф на две или три линии, но для SO цели, я думаю, что ясное представление о том, как это работает, лучше, чем расклинивание.

+4

[Здесь] (https://github.com/pennersr/django-allauth/issues/356#issuecomment-24758824) - прекрасный пример с oneliners для новых и старых версий Django. Он использует обнаружение функций, поэтому поддерживаются как поля SortedDict, так и OrderedDict. – aruseni

+0

Спасибо за ответ Тед и комментарий @aruseni. Это лучший способ сделать это, на мой взгляд, поскольку он действительно не углубляется в ядро ​​Django. Я подтверждаю, что он по-прежнему работает на 1.9. – Yoone

6

Подход принятого ответа использует внутренний API форм Django, который был изменен в Django 1.7. The project team's opinion is that it should never have been used in the first place. Теперь я использую эту функцию для изменения порядка моих форм. Этот код использует в 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) 
15

От Джанго 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

+1

см. Https://docs.djangoproject.com/en/1.10/ref/forms/api/#notes-on-field-ordering для документации –

0

Я построил форму 'ExRegistrationForm' унаследовал от 'RegistrationForm' от Джанго-Registration-Redux. Я столкнулся с двумя проблемами, одна из которых заключалась в переупорядочивании полей на странице вывода html после создания новой формы.

Я решил их следующим образом:

1. ВОПРОС 1: Удалить пользователя из регистрационной формы: В 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. ВОПРОС 2: Сделайте ПгвЬЫате и LastName: наверху: В шаблонах/registration/registration_form.html

Вы можете индивидуально отображать поля в том порядке, в котором вы хотите. Это помогло бы в случае, если количество полей меньше, но не если у вас есть большое количество полей, где практически невозможно записать их в форме.

 {% 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 %} 
Смежные вопросы