使用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],
)