Django自定义模型字段 - formfield无法正常工作

时间:2016-12-20 02:30:46

标签: django django-custom-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 个答案:

答案 0 :(得分:0)

我终于完成了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)

随时发表评论或分享您的想法。 我选择使用双'@'作为分隔符。这是有争议的,但它适用于我的情况。