Django Custom Model Field - поле формы не работает
Я почесал голову над этим. Я пытаюсь создать собственное поле модели для хранения почтового адреса.
Общая идея состоит в том, чтобы хранить все данные после сериализации в models.TextField
и использовать form.MultiValueField
поймать каждое значение.
По какой-то причине, даже если миграция работает нормально, в админке поле выглядит как простое TextField
, Как будто MultiValueField
часть была полностью проигнорирована...
Вот мой код:
from django.db import models
from django.forms import MultiValueField, CharField
from django.utils.translation import ugettext_lazy as _
class Address(object):
"""A postal address."""
def __init__(self, street, postal_code, city,
country, complement=None, region=None):
self.street = street
self.complement = complement
self.postal_code = postal_code
self.city = city
self.region = region
self.country = country
def print_address_inline(self):
complement = ''
region = ''
if self.complement:
complement = ', %s' % self.complement
if self.region:
region = ', %s' % self.region
data = {
'street': self.street,
'complement': complement,
'code': self.postal_code,
'city': self.city,
'region': region,
'country': self.country
}
return '%(street)s%(complement)s, %(code)s %(city)s%(region)s' \
', %(country)s' % data
def __str__(self):
return self.print_address_inline()
class AddressFormField(MultiValueField):
def __init__(self, *args, **kwargs):
del kwargs['max_length']
error_messages = {
'incomplete': _('Enter a complete address: ' \
'street, postal code, city and country.'),
}
fields = (
CharField(
label='street', max_length=1024,
error_messages={
'incomplete': _('Enter the number and street.')
}
),
CharField(
label='complement', max_length=1024, required=False
),
CharField(
label='code', max_length=10,
error_messages={'incomplete': _('Enter the postal code.')}
),
CharField(
label='city', max_length=255,
error_messages={'incomplete': _('Enter the city.')}
),
CharField(
label='region', max_length=255, required=False
),
CharField(
label='country', max_length=255,
error_messages={'incomplete': _('Enter the country.')}
)
)
super(AddressFormField, self).__init__(
error_messages=error_messages, fields=fields,
require_all_fields=False, *args, **kwargs
)
def compress(self, data_list):
if data_list:
if data_list[0] in self.empty_values:
raise ValidationError(
_('Enter the number and street.'),
code='incomplete'
)
if data_list[2] in self.empty_values:
raise ValidationError(
_('Enter the postal code.'),
code='incomplete'
)
if data_list[3] in self.empty_values:
raise ValidationError(
_('Enter the city.'),
code='incomplete'
)
if data_list[5] in self.empty_values:
raise ValidationError(
_('Enter the country.'),
code='incomplete'
)
address = Address(
street=data_list[0],
complement=data_list[1],
code=data_list[2],
city=data_list[3].title(),
region=data_list[4],
country=data_list[5].upper()
)
return address
return None
class AddressField(models.TextField):
description = "A postal address."
def __init__(self, *args, **kwargs):
super(AddressField, self).__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super(AddressField, self).deconstruct()
return name, path, args, kwargs
def from_db_value(self, value, expression, connection, context):
if value is None:
return value
data = value.split('@@')
return Address(*data)
def to_python(self, value):
if isinstance(value, Address):
return value
if value is None:
return value
data = value.split('@@')
return Address(*data)
def get_prep_value(self, value):
if isinstance(value, Address):
data = []
for v in [
value.street,
value.complement,
value.postal_code,
value.city,
value.region,
value.country,
]:
if v is not None:
data.append(v)
else:
data.append('')
return '@@'.join(data)
else:
return value
def formfield(self, **kwargs):
defaults = {'form_class': AddressFormField}
defaults.update(kwargs)
return super(AddressField, self).formfield(**defaults)
Если у вас есть какие-либо подсказки...
Спасибо!
1 ответ
Решение
Я наконец закончил с этим куском кода.
from django.db import models
from django.forms import MultiValueField, CharField, TextInput
from django.forms.widgets import MultiWidget
from django.utils.translation import ugettext_lazy as _
class Address(object):
"""A postal address."""
def __init__(self, street, complement, postal_code, city, region, country):
self.street = street
self.complement = complement
self.postal_code = postal_code
self.city = city
self.region = region
self.country = country
def print_address_inline(self):
complement = ''
region = ''
if self.complement != '':
complement = ', %s' % self.complement
if self.region != '':
region = ', %s' % self.region
data = {
'street': self.street,
'complement': complement,
'code': self.postal_code,
'city': self.city,
'region': region,
'country': self.country
}
return '%(street)s%(complement)s, %(code)s %(city)s%(region)s' \
', %(country)s' % data
def get_values_list(self):
data = [
self.street,
self.complement,
self.postal_code,
self.city,
self.region,
self.country,
]
value_list = []
for value in data:
if value is not None:
value_list.append(value)
else:
value_list.append('')
return value_list
def __str__(self):
return self.print_address_inline()
class AddressWidget(MultiWidget):
"""A special widget to render Address form field."""
def __init__(self, attrs=None):
# TODO Look for django-localflavor to improve this custom field.
# https://django-localflavor.readthedocs.io/en/latest/localflavor/fr/
widgets = [
TextInput(attrs={'placeholder':'street'}),
TextInput(attrs={'placeholder':'complement'}),
TextInput(attrs={'placeholder':'postal code'}),
TextInput(attrs={'placeholder':'city'}),
TextInput(attrs={'placeholder':'region'}),
TextInput(attrs={'placeholder':'country'}),
]
super(AddressWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return [
value.street,
value.complement,
value.postal_code,
value.city,
value.region,
value.country,
]
return [None, None, None, None, None, None]
class AddressFormField(MultiValueField):
"""A special form field to handle Address model field."""
widget = AddressWidget
def __init__(self, *args, **kwargs):
error_messages = {
'incomplete': _('Enter a complete address: ' \
'street, postal code, city and country.'),
}
fields = (
CharField(
label='street', max_length=1024,
error_messages={
'incomplete': _('Enter the number and street.')
}
),
CharField(
label='complement', max_length=1024, required=False
),
CharField(
label='code', max_length=10,
error_messages={'incomplete': _('Enter the postal code.')}
),
CharField(
label='city', max_length=255,
error_messages={'incomplete': _('Enter the city.')}
),
CharField(
label='region', max_length=255, required=False
),
CharField(
label='country', max_length=255,
error_messages={'incomplete': _('Enter the country.')}
)
)
super(AddressFormField, self).__init__(
error_messages=error_messages, fields=fields,
require_all_fields=False, *args, **kwargs
)
def compress(self, data_list):
if data_list:
if data_list[0] in self.empty_values:
raise ValidationError(
_('Enter the number and street.'),
code='incomplete'
)
if data_list[2] in self.empty_values:
raise ValidationError(
_('Enter the postal code.'),
code='incomplete'
)
if data_list[3] in self.empty_values:
raise ValidationError(
_('Enter the city.'),
code='incomplete'
)
if data_list[5] in self.empty_values:
raise ValidationError(
_('Enter the country.'),
code='incomplete'
)
address = Address(
street=data_list[0],
complement=data_list[1],
postal_code=data_list[2],
city=data_list[3].title(),
region=data_list[4],
country=data_list[5].upper()
)
return address
return None
class AddressField(models.Field):
description = "A postal address."
def __init__(self, *args, **kwargs):
super(AddressField, self).__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super(AddressField, self).deconstruct()
return name, path, args, kwargs
def from_db_value(self, value, expression, connection, context):
if value is None or value == '':
return value
data = value.split('@@')
address = Address(*data)
return address
def to_python(self, value):
if isinstance(value, Address):
return value
if value is None:
return value
data = value.split('@@')
return Address(*data)
def get_prep_value(self, value):
if value:
return '@@'.join(value.get_values_list())
return ''
def get_internal_type(self):
return 'TextField'
def formfield(self, **kwargs):
defaults = {'form_class': AddressFormField}
defaults.update(kwargs)
return super(AddressField, self).formfield(**defaults)
Не стесняйтесь комментировать или делиться своими мыслями об этом. Я сделал выбор использования двойного '@' в качестве разделителя. Это спорно, но он работает в моем случае.