【问题标题】:GenericForeignKey or ForeignKeyGenericForeignKey 或 ForeignKey
【发布时间】:2019-02-05 15:42:13
【问题描述】:

我目前必须为我的模型 Order 做出以下决定:要么我使用 GenericForeignKey,它指的是模型 DiscountModel 要么 AnotherDiscountModel。只能是其中之一,所以从GenericForeignKey 的想法来看可能是有道理的。

但是,我正在某个地方实现它,性能很重要。另一种方法是必须在我的模型中使用ForeignKey 字段:discount_modelanother_discount_model。其中一个永远是空的。

我现在想知道在我添加“其他折扣模式”之前你会走哪条路。你有什么见解可以和我分享吗?目前,GenericForeignKey 对我来说似乎有点复杂,我必须更改代码中的几个部分。

除了 Risadinha 的评论,我在这里分享我当前的模型结构:

class AbstractDiscount(TimeStampedModel):


    value = models.PositiveIntegerField(
        verbose_name=_("Value"),
        validators=[
            MinValueValidator(0),
        ],
        null=True,
        blank=True,
    )
    percentage = models.DecimalField(
        verbose_name=_("Percentage"),
        max_digits=5,
        decimal_places=4,
        validators=[
            MinValueValidator(0),
            MaxValueValidator(1),
        ],
        null=True,
        blank=True,
    )
    type = models.CharField(
        verbose_name=_("Type"),
        max_length=10,
        choices=TYPE_CHOICES,
    )
    redeemed_amount = models.PositiveIntegerField(
        verbose_name=_("Amount of redeems"),
        default=0,
    )

    class Meta:
        abstract = True


class Discount(AbstractDiscount):
    available_amount = models.PositiveIntegerField(
        verbose_name=_("Available amount"),
    )
    valid_from = models.DateTimeField(
        verbose_name=_("Valid from"),
        help_text=_("Choose local time of event location. Leave empty and discount will be valid right away."),
        null=True,
        blank=True,
    )
    valid_until = models.DateTimeField(
        verbose_name=_("Valid until"),
        help_text=_("Choose local time of event location. Leave empty to keep discount valid till the event."),
        null=True,
        blank=True,
    )
    comment = models.TextField(
        verbose_name=_("Comment"),
        help_text=_("Leave some notes for this discount code."),
        null=True,
        blank=True,
    )
    status = models.CharField(
        verbose_name=_("Status"),
        max_length=12,
        choices=STATUS_CHOICES,
        default=STATUS_ACTIVE,
    )
    code = models.CharField(
        verbose_name=_("Discount code"),
        max_length=20,
    )
    event = models.ForeignKey(
        Event,
        related_name='discounts',
        on_delete=models.CASCADE,
    )  # CASCADE = delete the discount if the event is deleted
    tickets = models.ManyToManyField(
        Ticket,
        related_name='discounts',
        blank=True,
        help_text=_("Leave empty to apply this discount to all tickets"),
        verbose_name=_("Tickets"),
    )

    class Meta:
        verbose_name = _("Discount")
        verbose_name_plural = _("Discounts")
        ordering = ['code']


class SocialDiscount(AbstractDiscount):
    event = models.OneToOneField(
        Event,
        related_name='social_ticketing_discount',
        on_delete=models.CASCADE,
    )  # CASCADE = delete the discount if the event is deleted
    tickets = models.ManyToManyField(
        Ticket,
        related_name='social_ticketing_discount',
        blank=True,
        help_text=_("Leave empty to apply this discount to all tickets"),
        verbose_name=_("Tickets"),
    )

    class Meta:
        verbose_name = _("SocialDiscount")
        verbose_name_plural = _("SocialDiscount")

【问题讨论】:

  • 还有另一种选择:如果DiscountModelAnotherDiscountModel 共享相同的非抽象父模型,您可以在该父模型上使用常规ForeignKey。见docs.djangoproject.com/en/2.1/topics/db/models/…
  • 您说性能很重要,但您确定选择会产生那么大的影响吗?如果是这样,您可以在基本实现中对其进行测试,如果任何一个选择都可以,请选择 GenericForeignKey 或 @Risadinha 的解决方案。
  • @Risadinha 我实际上有一个(当前)抽象类,其中两个模型都是建立的。但是,AbstractDiscount '不能' 单独存在,因为事件的 foreign_key 将丢失。您的建议包括将AbstractDiscount 设置为非抽象模型,我是否理解正确?
  • 其他需要考虑的事情:1)您不能在查询过滤器中使用GenericForeignKey; 2) GenericForeignKey 不会出现在 ModelForm 中。这些可能会被取消资格。见docs.djangoproject.com/en/2.1/ref/contrib/contenttypes/…

标签: python django


【解决方案1】:

对此没有通用的答案,只是考虑。该决定取决于您需要使用此解决方案实现的业务逻辑。

两列

order.discount = ForeignKey(Discount, null=True)
order.social_discount = ForeignKey(SocialDiscount, null=True)

签入后续代码时:

if order.discount:
    # do this based on Discount model
elif order.social_discount:
    # do that based on SocialDiscount model

这是有利于两种截然不同的折扣行为的解决方案。

使用这个:

  • 如果只有这两个,以后就没有了,
  • 如果您要在它们上调用非常不同的字段和方法(它们周围有不同的业务逻辑)。

非抽象父类

# renamed from AbstractDiscount to ParentDiscount for obvious reasons
order.discount = ForeignKey(ParentDiscount, null=True)

后续代码:

if order.discount:
    # do things that apply to either discount
    if isinstance(order.discount, 'Discount'):
        # do things that only apply to Discount
    elif isinstance(order.discount, 'SocialDiscount'):
        # do things that only apply to SocialDiscount

使用这个:

  • 如果将来可能有更多 ParentDiscount 的孩子,
  • 如果存在适用于所有子级共享的任何类型的 ParentDiscount 的通用业务逻辑。

GenericForeignKey

​​>

查询 GenericForeignKeys 需要一些工作。正如@Andy 所说,它不直接支持,但您当然可以同时查询content_typeobject_id__in 查找将不起作用,除非您只能依赖 object_id

它不会在表单中开箱即用。不过,对于 Django 管理员,可能有一些解决方案,请参阅 GenericForeignKey and Admin in Django

使用这个:

  • 如果未来可能会有更多各种类型的折扣(询问产品负责人并确保这不仅仅是遥远的未来),
  • 如果有适用于任何这些类型的通用业务逻辑,
  • 如果您不需要不工作的快速管理解决方案。

【讨论】:

  • 很好的解释,非常感谢。我还有一个问题要问Non-Abstract Parent。案例 1:我创建折扣。这将在 ParentDiscountDiscount 中创建一个字段,该字段在 OnetoOne-Relation 中引用 ParentDiscount。案例 2:我创建了一个 social_discount。与案例 1 相同(OnetoOne-Relation to PartentDiscount),只是它是 SocialDiscount 而不是 Discount
  • @JoeyCoder - 我不能完全关注你,但如果你想知道下面发生了什么:试试看并查看你的数据库。对于您创建的每个子表,将在子表中添加一行,在父表中添加一行(实际上,创建子 obj 会进行两次插入,每行插入两个表)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-30
  • 2012-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-29
相关资源
最近更新 更多