如何在Django中使用通用ForeignKey定义模式

How to define Mode with generic ForeignKey in Django

我对姜戈不熟悉

我想用以下逻辑创建模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class ExerciseCardio(models.Model):
    pass


class ExerciseWeights(models.Model):
    pass


class Exercise(models.Model):
    name = models.CharField(max_length=100, default='')

    EXERCISE_TYPE_CHOICES = (
        (1, 'cardio'),
        (2, 'Weights'),
    )

    exercise_type = models.PositiveSmallIntegerField(
        choices=EXERCISE_TYPE_CHOICES, default=2)

    if exercise_type == 1:
        exercise_model_type = models.ForeignKey(ExerciseCardio, on_delete=models.CASCADE, default=0)
    elif exercise_type == 2:
        exercise_model_type = models.ForeignKey(ExerciseWeights, on_delete=models.CASCADE, default=0)

    def __str__(self):
        return self.name

我知道它看起来很难看但一定有办法


是的,有一种方法:可以使用Djangos通用关系。

其要点如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType

class Exercise(models.Model):
    EXERCISE_TYPE_CHOICES = (
        (1, 'cardio'),
        (2, 'Weights'),
    )

    name = models.CharField(
        max_length=100, default='')
    exercise_type = models.PositiveSmallIntegerField(
        choices=EXERCISE_TYPE_CHOICES, default=2)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

在您看来,在创建Exercise实例时,您必须选择正确模型的ContentType,可能如下所示:

1
2
3
4
5
6
obj = Exercise()
obj.exercise_type = ...
if obj.exercise_type == 1:
    obj.content_type = ContentType.objects.get_for_model(ExerciseCardio)
else:
    obj.content_type = ContentType.objects.get_for_model(ExerciseWeights)

正如你所指出的和拉尔夫所描述的,Django中的实际通用外键仍然笨重难看。

但是,您要讨论的是一些特定类型,它们需要以特定的方式进行操作,我认为这是一个很好的继承候选,可以借助于库中的自定义管理器:django-model-utils.managers.InheritanceManager

models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from django.db import models
from model_utils.managers import InheritanceManager


class Exercise(models.Model):
    name = models.CharField(max_length=32)

    objects = InheritanceManager()

    def __str__(self):
        return"{n} ({t})".format(n=self.name, t=type(self))


class ExerciseCardio(Exercise):
    pass


class ExerciseWeights(Exercise):
    pass

示例(在Django Shell中,使用我的高级测试应用程序eh):

1
2
3
4
5
6
7
8
9
10
11
12
13
from eh.models import ExerciseCardio, Exercise, ExerciseWeights


c = ExerciseCardio.objects.create(name="Cardio!")
w = ExerciseWeights.objects.create(name="Weights!")

print(Exercise.objects.filter(name="Cardio!").select_subclasses().get())
# Cardio! (<class 'eh.models.ExerciseCardio'>)

for e in Exercise.objects.all().select_subclasses():
    print(e)
# Cardio! (<class 'eh.models.ExerciseCardio'>)
# Weights! (<class 'eh.models.ExerciseWeights'>)