wtforms Подклассы класса Form и упорядочение полей - PullRequest
15 голосов
/ 01 мая 2011

У меня есть класс UserForm:

class UserForm(Form):
    first_name = TextField(u'First name', [validators.Required()])
    last_name = TextField(u'Last name', [validators.Required()])
    middle_name = TextField(u'Middle name', [validators.Required()])
    username = TextField(u'Username', [validators.Required()])
    password = TextField(u'Password', [validators.Required()], widget=PasswordInput())
    email = TextField(u'Email', [validators.Optional(), validators.Email()])

и хотите сделать поле пароля необязательным в UpdateUserForm:

class UpdateUserForm(UserForm):
    password = TextField(u'Password', [validators.Optional()], widget=PasswordInput())

Но поле пароля ставится после поля электронной почты, а не до.

Как сохранить порядок полей при создании подклассов?

Кроме того, когда я пытаюсь изменить валидаторы поля пароля, он не работает - пароль все еще требуется: / Почему?

class UpdateUserForm(UserForm):
    def __init__(self, **kwargs):
        self.password.validators = [validators.Optional()]
        super(UpdateUserForm, self).__init__(**kwargs)

или

class UpdateUserForm(UserForm):
    def __init__(self, **kwargs):
        self.password = TextField(u'Password', [validators.Optional()], widget=PasswordInput())
        super(UpdateUserForm, self).__init__(**kwargs)

Некоторые мысли ...

class UpdateUserForm(UserForm):
    def __init__(self, formdata=None, obj=None, prefix='', **kwargs):
        self._unbound_fields[4][1] = TextField(u'Password', [validators.Optional()], widget=PasswordInput())
        UserForm.__init__(self, formdata=None, obj=None, prefix='', **kwargs)

Наконец то, что мне нужно:

class UpdateUserForm(UserForm):
    def __init__(self, formdata=None, obj=None, prefix='', **kwargs):
        UserForm.__init__(self, formdata, obj, prefix, **kwargs)
        self['password'].validators = [validators.Optional()]
        self['password'].flags.required = False

Ответы [ 6 ]

11 голосов
/ 28 августа 2013

Что касается вашего первого вопроса о переписывании полей при итерации по объекту формы, это то, что я сделал:

class BaseForm(Form):
    def __iter__(self):
        field_order = getattr(self, 'field_order', None)
        if field_order:
            temp_fields = []
            for name in field_order:
                if name == '*':
                    temp_fields.extend([f for f in self._unbound_fields if f[0] not in field_order])
                else:
                    temp_fields.append([f for f in self._unbound_fields if f[0] == name][0])
            self._unbound_fields = temp_fields
        return super(BaseForm, self).__iter__()

class BaseUserForm(BaseForm):
    password = PasswordField('Password', [Required()])
    full_name = TextField('Full name', [Required()])

class NewUserForm(BaseUserForm):
    username = Textfield('Username', [Required()])
    field_order = ('username', '*')

Таким образом, при рендеринге NewUserForm (возможно, из шаблона, который перебирает поле рендеринга формы по полю), вы увидите username, password, full_name. Обычно вы видели бы username последний.

4 голосов
/ 15 августа 2014

Я решил эту проблему путем определения дополнительного атрибута __order в моем классе Form и переопределения метода __iter__, чтобы возвращаемые данные итератора сортировались первыми в соответствии с определением.Это может быть не совсем эффективно, но в форме не так много полей, чтобы это могло вызвать какие-либо проблемы.Он также работает с полями из подклассных форм.

class MyForm(Form):
    field3 = TextField()
    field1 = TextField()
    field2 = TextField()

    __order = ('field1', 'field2', 'field3')

    def __iter__(self):
        fields = list(super(MyForm, self).__iter__())
        get_field = lambda field_id: next((fld for fld in fields
                                           if fld.id == field_id))
        return (get_field(field_id) for field_id in self.__order)
3 голосов
/ 10 июля 2012

Вот как я выполняю то, что вы пытались сделать:

class UserForm(wtforms.Form):                                                   
    def __init__(self, *args, **kwargs):                                        
        super(UserForm,self).__init__(*args, **kwargs)                          

        if kwargs.get('update', None):                                          
            self['passwd'].validators.append(wtforms.validators.Optional())
            self['passwd'].flags.required = False     
        else:                                                                   
            self['passwd'].validators.append(wtforms.validators.Required()) 

    passwd = UnicodeField(                                                      
        u'Password',                                                            
        [                                                                       
            wtforms.validators.length(max=50),                                  
            wtforms.validators.EqualTo(                                         
                'confirm',                                                      
                message='Passwords must match'                                  
                )                                                               
            ],                                                                  
        widget = wtforms.widgets.PasswordInput()                                
        )                                                                       

    confirm = wtforms.PasswordField(u'Password Verify')

Затем, когда я создаю экземпляр пользовательской формы, я передаю update = True при редактировании. Похоже, это работает для меня.

2 голосов
/ 11 сентября 2014

Чтобы принудительно упорядочить поля формы, вы можете использовать следующий метод:

from collections import OrderedDict

def order_fields(fields, order):
    return OrderedDict((k,fields[k]) for k in order)

И вызовите его в конструкторе форм следующим образом:

class FancyForm(Form, ParentClass1, ParentClass2...):
    x = TextField()
    y = TextField()
    z = TextField()

    _order = 'x y z'.split()


    def __init__(self, *args, **kwargs):
        super(FancyForm, self).__init__(*args, **kwargs)
        self._fields = order_fields(self._fields, 
                                    self._order + ParentClass1._order + ParentClass2._order)
2 голосов
/ 06 января 2012

Это происходит потому, что порядок полей определяется классом UnboundField.creation_counter, который использует порядок, в котором класс Field появляется в коде.

>>> x1 = UserForm()
>>> x2 = UpdateUserForm()
>>> [(f[0], f[1].creation_counter) for f in x1._unbound_fields]
[('first_name', 22), ('last_name', 23), ('middle_name', 24), ('username', 25), ('password', 26), ('email', 27)]
>>> [(f[0], f[1].creation_counter) for f in x2._unbound_fields]
[('first_name', 22), ('last_name', 23), ('middle_name', 24), ('username', 25), ('email', 27), ('password', 28)]
>>> 

Поскольку это трудно решить (так как при использовании этого подхода весовые формы пытаются быть волшебными), лучший способ справиться с этим - определить поля в нужном порядке.

class BaseForm(Form):
    first_name = TextField(u'First name', [validators.Required()])
    last_name = TextField(u'Last name', [validators.Required()])
    middle_name = TextField(u'Middle name', [validators.Required()])
    username = TextField(u'Username', [validators.Required()])

class UserForm(BaseForm):
    password = TextField(u'Password', [validators.Required()], widget=PasswordInput())
    email = TextField(u'Email', [validators.Optional(), validators.Email()])

class UpdateUserForm(BaseForm):
    password = TextField(u'Password', [validators.Optional()], widget=PasswordInput())
    email = TextField(u'Email', [validators.Optional(), validators.Email()])

Но если вы перфекционист или вам необходимо придерживаться принципа DRY :

class BaseForm(Form):
    first_name = TextField(u'First name', [validators.Required()])
    last_name = TextField(u'Last name', [validators.Required()])
    middle_name = TextField(u'Middle name', [validators.Required()])
    username = TextField(u'Username', [validators.Required()])

class UserForm(BaseForm):
    password = TextField(u'Password', [validators.Required()], widget=PasswordInput())

class UpdateUserForm(BaseForm):
    password = TextField(u'Password', [validators.Optional()], widget=PasswordInput())

BaseForm.email = TextField(u'Email', [validators.Optional(), validators.Email()])
0 голосов
/ 12 февраля 2016

Я объединил два ответа в следующий фрагмент:

def __iter__(self):
    ordered_fields = collections.OrderedDict()

    for name in getattr(self, 'field_order', []):
        ordered_fields[name] = self._fields.pop(name)

    ordered_fields.update(self._fields)

    self._fields = ordered_fields

    return super(BaseForm, self).__iter__()

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

...