使用InlineForm for ForeignKey

时间:2016-12-07 09:14:54

标签: django django-forms

使用ForeignKey关系,是否可以显示指向内联的对象的形式? documentation显示了如何使用内联formset进行反向操作;但是我无法找到如何用内联表单替换默认选择框。

例如,考虑一下案例:

class Address(models.Model):
    line_1 = models.CharField(max_length=100)
    line_2 = models.CharField(max_length=100)
    town = models.CharField(max_length=50)
    state = models.ForeignKey(State,
                              on_delete=models.PROTECT)
    post_code = models.CharField(max_length=4)


class Person(models.Model):
    # ...
    residential_address = models.ForeignKey(Address, unique=True)
    postal_address = models.ForeignKey(Address, unique=True)

默认情况下,Person的表单会显示两个下拉选项:一个用于住宅地址,另一个用于邮政地址。我可以改为显示表格吗?也许通过小部件和/或自定义字段?

所以,既然还没有答案,我一直在努力建立自己的领域和小部件。显示方面(大多数)现在工作正常,但是当我尝试保存对象时出现错误,因为需要事先创建引用的对象;但是,我不确定我是否可以(或应该?)保存对象。

这就是我所拥有的:

model.py

class Address(models.Model):
    # as above

class AddressField(models.OneToOneField):
    description = "An address"

    def __init__(self, **kwargs):
        """The foreign key should always be to an Address class."""
        kwargs['to'] = 'address.Address'
        kwargs['related_name'] = '+'
        super().__init__(**kwargs)

    def formfield(self, **kwargs):
        """See https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.Field.formfield.

        Change the default form field.

        """
        from address.forms import AddressField as AddressFormField
        defaults = dict(form_class=AddressFormField)
        defaults.update(kwargs)
        return super(AddressField, self).formfield(**defaults)

forms.py

class AddressField(fields.MultiValueField):
    """Address (multi-value) field."""

    def __init__(self, *args, **kwargs):
        error_messages = {
            'incomplete': _("Please ensure all required fields are completed.")
        }

        sub_fields = (
            # Unique key
            models.ModelChoiceField(queryset=kwargs.pop('queryset'),
                                    limit_choices_to=kwargs.pop('limit_choices_to'),
                                    to_field_name=kwargs.pop('to_field_name'),
                                    required=False,
                                    error_messages={
                                        'invalid': _("Invalid address.")
                                    }),
            # Line 1
            fields.CharField(max_length=256,
                             error_messages={
                                 'incomplete': _("Enter a first address line.")
                             }),
            # Line 2
            fields.CharField(max_length=256,
                             required=False),
            # Town
            fields.CharField(max_length=100,
                             error_messages={
                                 'incomplete': _("Enter a town name.")
                             }),
            # State
            models.ModelChoiceField(queryset=State._default_manager.get_queryset(),
                                    error_messages={
                                        'incomplete': _("Select a state."),
                                        'invalid': _("Invalid state.")
                                    }),
            # Post code
            fields.CharField(max_length=4,
                             validators=[
                                 RegexValidator(
                                     regex=r"^[0-9]{4}$",
                                     message=_("Invalid post code."),
                                     code='invalid-postcode'
                                 )
                             ],
                             error_messages={
                                 'incomplete': _("Enter a post code.")
                             }),
        )

        super().__init__(
            error_messages=error_messages,
            fields=sub_fields,
            require_all_fields=False,
            widget=AddressWidget,
            *args, **kwargs
        )
        # TODO: Need to set the State choices

    def compress(self, values):
        """See https://docs.djangoproject.com/en/1.10/ref/forms/fields/#django.forms.MultiValueField.compress.

        Converts a list of values into an Address instance, unless no values
        were given in which case None is returned.

        """
        if not values:
            return None

        # If we have been given a PK, then fetch the object and updates its values
        if values[0]:
            try:
                address = Address.objects.get(pk=values[0])
                address.line_1 = values[1]
                address.line_2 = values[2]
                address.town = values[3]
                address.state = values[4]
                address.post_code = values[5]
            except Address.DoesNotExist:
                # TODO Handle this properly (if it every comes up)
                raise Exception("Tried to get the address with key '{}', but could not find it.")
        # Otherwise, instantiate a new Address
        else:
            address = Address(
                line_1=values[1],
                line_2=values[2],
                town=values[3],
                state=values[4],
                post_code=values[5]
            )
        return address


class AddressWidget(widgets.MultiWidget):
    """Address widget."""
    # TODO: Need ot handle the setting of choices (or not?)
    choices = None

    def __init__(self, *args, **kwargs):
        sub_widgets = (
            widgets.HiddenInput,
            widgets.TextInput(attrs={
                'title': _("Line 1"),
                'size': 50,
                'required': True,
                'class': "address-line address-line1",
            }),
            widgets.TextInput(attrs={
                'title': _("Line 2"),
                'size': 50,
                'required': False,
                'class': "address-line address-line2",
            }),
            widgets.TextInput(attrs={
                'title': _("Town"),
                'size': 30,
                'required': True,
                'style': "flex-grow: 4;",
                'class': "address-town",
            }),
            widgets.Select(attrs={
                'title': _("State"),
                'size': 3,
                'required': True,
                'style': "flex-grow: 1;",
                'class': "address-state",
            }),
            widgets.TextInput(attrs={
                'title': _("Post Code"),
                'size': 4,
                'required': True,
                'style': "flex-grow: 1;",
                'class': "address-postcode",
            }),
        )

        super().__init__(
            widgets=sub_widgets,
            *args, **kwargs
        )
        self.widgets[4].choices=[[1, 2]]
        print("widgets: {}".format(self.widgets))

    def decompress(self, value):
        """See https://docs.djangoproject.com/en/1.10/ref/forms/widgets/#django.forms.MultiWidget.decompress.

        Converts an Address object into a list of values; that is, performs the
        converse of `compress` above.

        """
        if not value:
            return [None] * 6
        if isinstance(value, Address):
            return [
                value.pk,
                value.line_1,
                value.line_2,
                value.town,
                value.state,
                value.post_code
            ]
        raise Exception("Unable to decompress the given value.")

    def format_output(self, rendered_widgets):
        """See https://docs.djangoproject.com/en/1.10/ref/forms/widgets/#django.forms.MultiWidget.format_output.

        Ensure that line 1 and 2 are on their own line, and place the town,
        state and post code on the third line.

        """

        print("choices: {}".format(self.choices))
        return """
<div class="address-widget" style="display: flex; flex-direction: column;">
  {pk}
  {line_1}
  {line_2}
  <div class="address-locality" style="display: flex; flex-direction: row; flex-wrap: wrap">
    {town}
    {state}
    {post_code}
  </div>
</div>
        """.format(
            pk=rendered_widgets[0],
            line_1=rendered_widgets[1],
            line_2=rendered_widgets[2],
            town=rendered_widgets[3],
            state=rendered_widgets[4],
            post_code=rendered_widgets[5],
        )

0 个答案:

没有答案