【问题标题】:Django Custom Model Field - formfield not workingDjango 自定义模型字段 - 表单域不起作用
【发布时间】:2017-05-05 04:15:36
【问题描述】:

我一直在为这个问题挠头。我正在尝试构建一个自定义模型字段来存储邮政地址。

总体思路是将序列化后的所有数据存储在一个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)

如果你有任何线索......

谢谢!

【问题讨论】:

    标签: django django-custom-field


    【解决方案1】:

    我终于通过this piece of code成功了。

    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)
    

    随时发表评论或分享您对此的看法。 我选择使用双“@”作为分隔符。这值得商榷,但它适用于我的情况。

    【讨论】:

      猜你喜欢
      • 2012-12-11
      • 1970-01-01
      • 2015-06-13
      • 2012-08-21
      • 1970-01-01
      • 2014-01-12
      • 2017-12-17
      • 1970-01-01
      • 2019-01-28
      相关资源
      最近更新 更多