Wtforms Подкласс класса класса и упорядочение полей
У меня есть класс 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())
Но поле пароля помещается после поля электронной почты, а не раньше.
Как сохранить порядок полей при подклассовке?
Кроме того, когда я пытаюсь изменить валидаторы полей пароля, это не сработает - пароль все еще необходим:/Why?
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
Ответы
Ответ 1
Что касается вашего первого вопроса о записи полей при итерации по объекту формы, это то, что я сделал:
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
последний.
Ответ 2
Вот как я делаю то, что вы пытались сделать:
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')
Затем, когда я создаю экземпляр UserForm, я передаю update = True при редактировании. Кажется, это работает для меня.
Ответ 3
Я решил это, указав дополнительный атрибут __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)
Ответ 4
Это происходит потому, что упорядочение полей определяется классом 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)]
>>>
Поскольку это трудно решить (потому что wtforms пытаются использовать магию с помощью этого подхода), лучший способ справиться с этим - определить поля в нужном порядке.
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()])
Ответ 5
Чтобы принудительно упорядочить поля формы, вы можете использовать следующий метод:
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)
Ответ 6
Я объединил два ответа в следующий фрагмент:
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, идет в этом порядке, остальные поля отображаются как-есть.