假设我有一个用于在我的站点上存储客户订单的Order模型。将有两个地址字段:shipping_address和billing_address。我可以写成这样:
class Order(models.Model):
order_number = models.CharField()
time_created = models.DateTimeField()
...
shipping_name = models.CharField()
shipping_city = models.CharField()
shipping_street = models.CharField()
shipping_building = models.CharField()
billing_name = models.CharField()
billing_city = models.CharField()
billing_street = models.CharField()
billing_building = models.CharField()
...然后通过order.shipping_name和order.billing_name访问字段,但是当每个地址都需要包含15-20个字段时,这会变得非常麻烦。
我尝试了一对一的关系,但是应用程序的其他地方的代码变得非常复杂。
如果只有一个地址字段,我可以使用抽象模型来使其变得更清晰:
class Address(models.Model):
class Meta:
abstract = True
name = models.CharField()
city = models.CharField()
street = models.CharField()
building = models.CharField()
...
class Order(Address, models.Model):
order_number = models.CharField()
time_created = models.DateTimeField()但是这样,我只能拥有一组地址数据,Address类中的字段名将与来自Order的字段发生冲突。
是否有一种方法将具有给定前缀的字段注入或嵌入到给定模型中?我正在寻找类似于Spring模型中使用的@Embedded装饰器的东西。
我会想象它看起来是这样的:
class Address(models.Model):
class Meta:
abstract = True
name = models.CharField()
city = models.CharField()
street = models.CharField()
building = models.CharField()
...
class Order(models.Model):
order_number = models.CharField()
time_created = models.DateTimeField()
shipping_address = Embedded(Address, prefix='shipping')
billing_address = Embedded(Address, prefix='billing')然后通过order.shipping_address.city访问字段,或者不破坏其他地方的代码,也许order.shipping_address_city也能工作。
编辑:使用JSONField或HStoreField可以实现类似的功能吗?
发布于 2022-07-27 08:32:28
经过大量的尝试和错误之后,使用来自https://stackoverflow.com/a/55051990/345811的模型工厂的方法似乎对我来说是最好的。
下面是示例models.py实现:
from django.db import models
from django.utils.translation import gettext_lazy as _
from app.countries import COUNTRIES # A list of country code/name tuples
ADDRESS_FIELDS = (
('first_name', dict(verbose_name=_('First name'), max_length=256)),
('last_name', dict(verbose_name=_('Last name'), max_length=256)),
('company', dict(verbose_name=_('Company'), max_length=256, required=False)),
('street', dict(verbose_name=_('Street address'), max_length=256)),
('street2', dict(verbose_name=_('Street address line 2'), max_length=256, required=False)),
('street3', dict(verbose_name=_('Street address line 3'), max_length=256, required=False)),
('postal_code', dict(verbose_name=_('Postal code'), max_length=16)),
('city', dict(verbose_name=_('City'), max_length=64)),
('province', dict(verbose_name=_('Province'), max_length=64, required=False)),
('country', dict(verbose_name=_('Country'), choices=COUNTRIES, max_length=2)),
)
def address_model_factory(field_prefix, force_optional=False):
"""
Usage:
class SomeModel(address_model_factory(''), address_model_factory('billing', True))
"""
class AbstractAddressModel(models.Model):
class Meta:
abstract = True
for field_name, field_def in ADDRESS_FIELDS:
required = field_def.get('required', True)
field_kwargs = {k: v for k, v in field_def.items() if k != 'required'}
if force_optional or not required:
field_kwargs.update(blank=True, default="")
AbstractAddressModel.add_to_class(field_name if not field_prefix else f'{field_prefix}_{field_name}',
models.CharField(**field_kwargs)
)
return AbstractAddressModel
class Shipment(address_model_factory(''), address_model_factory('billing', True)):
status = models.Charfield(_('Shipment status'), max_length=20)会产生这样的迁移:
class Migration(migrations.Migration):
initial = True
operations = [
migrations.CreateModel(
name='Shipment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=256, verbose_name='First name')),
('last_name', models.CharField(max_length=256, verbose_name='Last name')),
('company', models.CharField(blank=True, default='', max_length=256, verbose_name='Company')),
('street', models.CharField(max_length=256, verbose_name='Street address')),
('street2', models.CharField(blank=True, default='', max_length=256, verbose_name='Street address line 2')),
('street3', models.CharField(blank=True, default='', max_length=256, verbose_name='Street address line 3')),
('postal_code', models.CharField(max_length=16, verbose_name='Postal code')),
('city', models.CharField(max_length=64, verbose_name='City')),
('province', models.CharField(blank=True, default='', max_length=64, verbose_name='Province')),
('country', models.CharField(choices=[('DE', 'Germany'), ('GB', 'United Kingdom')], max_length=2, verbose_name='Country')),
('billing_first_name', models.CharField(blank=True, default='', max_length=256, verbose_name='First name')),
('billing_last_name', models.CharField(blank=True, default='', max_length=256, verbose_name='Last name')),
('billing_company', models.CharField(blank=True, default='', max_length=256, verbose_name='Company')),
('billing_street', models.CharField(blank=True, default='', max_length=256, verbose_name='Street address')),
('billing_street2', models.CharField(blank=True, default='', max_length=256, verbose_name='Street address line 2')),
('billing_street3', models.CharField(blank=True, default='', max_length=256, verbose_name='Street address line 3')),
('billing_postal_code', models.CharField(blank=True, default='', max_length=16, verbose_name='Postal code')),
('billing_city', models.CharField(blank=True, default='', max_length=64, verbose_name='City')),
('billing_province', models.CharField(blank=True, default='', max_length=64, verbose_name='Province')),
('billing_country', models.CharField(blank=True, choices=choices=[('DE', 'Germany'), ('GB', 'United Kingdom')], default='', max_length=2, verbose_name='Country')),
('status', models.CharField(max_length=20, verbose_name='Shipment status')),
],
options={},
),
]发布于 2020-12-18 21:24:53
嵌入另一个模型的一部分作为引用可能是不可能的,而且,它肯定是非标准的方式。唯一推荐的解决方案是使用两种不同的模型:Order和Address。然后按顺序将地址指定为ForeignKey
class Order(models.Model):
...
shipping_address = models.ForeignKey(
Address, on_delete=models.SET_NULL, related_name="shipped_orders",
blank=True, null=True
)
invoice_address = models.ForeignKey(
Address, on_delete=models.SET_NULL, related_name="invoiced_orders",
blank=True, null=True
)它将显示为一个选择框在管理,您可以选择发货和发票地址。为了使它更简单,只需在管理中使用字段即可。
如果您需要在管理页面中进行一些验证,同时添加一个订单,您可以设置一个将在管理页面中使用的模型,文档的这一部分描述了如何做到这一点。在您的自定义订单表单中,您可以验证像def clean_shipping_address(self)这样的字段,更详细地描述这里。
https://stackoverflow.com/questions/65363610
复制相似问题