Django - 有条件的外键

时间:2018-05-05 07:55:11

标签: python django orm

我的程序中有以下4个模型 - 用户,品牌,代理商和创作者。

用户是品牌,代理商和创作者的超集。

用户可以是品牌,代理商或创作者。他们不能承担多个角色。他们的角色由accountType属性定义。如果它们未设置(即0),则不存在正式连接。

我如何在我的模型中表达这一点?

用户模型

class User(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
    email = models.EmailField(max_length=255, null=True, default=None)
    password = models.CharField(max_length=255, null=True, default=None)
    ACCOUNT_CHOICE_UNSET = 0
    ACCOUNT_CHOICE_BRAND = 1
    ACCOUNT_CHOICE_CREATOR = 2
    ACCOUNT_CHOICE_AGENCY = 3
    ACCOUNT_CHOICES = (
        (ACCOUNT_CHOICE_UNSET, 'Unset'),
        (ACCOUNT_CHOICE_BRAND, 'Brand'),
        (ACCOUNT_CHOICE_CREATOR, 'Creator'),
        (ACCOUNT_CHOICE_AGENCY, 'Agency'),
    )
    account_id = models.ForeignKey(Brand)
    account_type = models.IntegerField(choices=ACCOUNT_CHOICES, default=ACCOUNT_CHOICE_UNSET)

    class Meta:
        verbose_name_plural = "Users"

    def __str__(self):
        return "%s" % self.email

品牌模型

class Brand(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
    name = models.CharField(max_length=255, null=True, default=None)
    brand = models.CharField(max_length=255, null=True, default=None)
    email = models.EmailField(max_length=255, null=True, default=None)
    phone = models.CharField(max_length=255, null=True, default=None)
    website = models.CharField(max_length=255, null=True, default=None)

    class Meta:
        verbose_name_plural = "Brands"

    def __str__(self):
        return "%s" % self.brand

创作者模型

class Creator(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
    first_name = models.CharField(max_length=255, null=True, default=None)
    last_name = models.CharField(max_length=255, null=True, default=None)
    email = models.EmailField(max_length=255, null=True, default=None)
    youtube_channel_username = models.CharField(max_length=255, null=True, default=None)
    youtube_channel_url = models.CharField(max_length=255, null=True, default=None)
    youtube_channel_title = models.CharField(max_length=255, null=True, default=None)
    youtube_channel_description = models.CharField(max_length=255, null=True, default=None)
    photo = models.CharField(max_length=255, null=True, default=None)
    youtube_channel_start_date = models.CharField(max_length=255, null=True, default=None)
    keywords = models.CharField(max_length=255, null=True, default=None)
    no_of_subscribers = models.IntegerField(default=0)
    no_of_videos = models.IntegerField(default=0)
    no_of_views = models.IntegerField(default=0)
    no_of_likes = models.IntegerField(default=0)
    no_of_dislikes = models.IntegerField(default=0)
    location = models.CharField(max_length=255, null=True, default=None)
    avg_views = models.IntegerField(default=0)
    GENDER_CHOICE_UNSET = 0
    GENDER_CHOICE_MALE = 1
    GENDER_CHOICE_FEMALE = 2
    GENDER_CHOICES = (
        (GENDER_CHOICE_UNSET, 'Unset'),
        (GENDER_CHOICE_MALE, 'Male'),
        (GENDER_CHOICE_FEMALE, 'Female'),
    )
    gender = models.IntegerField(choices=GENDER_CHOICES, default=GENDER_CHOICE_UNSET)

    class Meta:
        verbose_name_plural = "Creators"

    def __str__(self):
        return "%s %s" % (self.first_name,self.last_name)

代理商模式

class Agency(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
    name = models.CharField(max_length=255, null=True, default=None)
    agency = models.CharField(max_length=255, null=True, default=None)
    email = models.EmailField(max_length=255, null=True, default=None)
    phone = models.CharField(max_length=255, null=True, default=None)
    website = models.CharField(max_length=255, null=True, default=None)

    class Meta:
        verbose_name_plural = "Agencies"

    def __str__(self):
        return "%s" % self.agency

更新

所以我在模型中将它缩减到这一点:

ACCOUNT_CHOICE_UNSET = 0
ACCOUNT_CHOICE_BRAND = 1
ACCOUNT_CHOICE_CREATOR = 2
ACCOUNT_CHOICE_AGENCY = 3
ACCOUNT_CHOICES = (
    (ACCOUNT_CHOICE_UNSET, 'Unset'),
    (ACCOUNT_CHOICE_BRAND, 'Brand'),
    (ACCOUNT_CHOICE_CREATOR, 'Creator'),
    (ACCOUNT_CHOICE_AGENCY, 'Agency'),
)
account_type = models.IntegerField(choices=ACCOUNT_CHOICES, default=ACCOUNT_CHOICE_UNSET)
limit = models.Q(app_label='api', model='Brand') | \
        models.Q(app_label='api', model='Creator') | \
        models.Q(app_label='api', model='Agency')
content_type = models.ForeignKey(ContentType, limit_choices_to=get_content_type_choices(), related_name='user_content_type')
content_object = GenericForeignKey('content_type', 'email')
  • 如果account_type = 1,则链接至品牌型号
  • 如果account_type = 2,则链接到创建者模型
  • 如果account_type = 3,则链接到代理商型号

我如何做到这一点?得到此错误:

  File "/Users/projects/adsoma-api/api/models.py", line 7, in <module>
    class User(models.Model):
  File "/Users/projects/adsoma-api/api/models.py", line 28, in User
    content_type = models.ForeignKey(ContentType, limit_choices_to=get_content_type_choices(), related_name='user_content_type')
NameError: name 'get_content_type_choices' is not defined

1 个答案:

答案 0 :(得分:5)

您是否尝试过探索Django的GenericForeignKey字段?

class User(models.Model):
    ...
    limit = models.Q(app_label='your_app_label', model='brand') | 
            models.Q(app_label='your_app_label', model='creator') | 
            models.Q(app_label='your_app_label', model='agency')
    content_type = models.ForeignKey(ContentType, limit_choices_to=limit, related_name='user_content_type')
    object_id = models.UUIDField()
    content_object = GenericForeignKey('content_type', 'object_id')

您可以使用以下表示法访问用户的品牌/创作者/代理商:

User.objects.get(pk=1).content_object

根据Brand,这将是Creator / Agency / content_type的一个实例。

https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/#django.contrib.contenttypes.fields.GenericForeignKey

根据您的评论进行更新

Re 1:使用电子邮件:

class User(models.Model):
    ...
    email = models.EmailField(max_length=255, unique=True)
    limit = models.Q(app_label='your_app_label', model='brand') | 
            models.Q(app_label='your_app_label', model='creator') | 
            models.Q(app_label='your_app_label', model='agency')
    content_type = models.ForeignKey(ContentType, limit_choices_to=get_content_type_choices, related_name='user_content_type')
    content_object = GenericForeignKey('content_type', 'email')

注意:如果您遵循此方法,电子邮件在任何地方都不能是可以为空的字段!此方法似乎也很麻烦/错误,因为email字段现在在多个地方声明;如果重新分配内容对象,则值可能会更改。在其他三个模型中使用离散GenericForeignKey关联UUIDField

更清晰

Re 2:使用account_type字段:

ContentType应该是对Django模型的引用;因此它需要选择模型而不是整数。 limit_choices_to的功能是执行过滤,以便所有可能的模型都不会显示为潜在的GenericForeignKey

class ContentType(models.Model):
    app_label = models.CharField(max_length=100)
    model = models.CharField(_('python model class name'), max_length=100)
    objects = ContentTypeManager()

然而,limit_choices_to确实接受了赎回权;因此,您可以编写一个帮助方法,将account_type转换为正确的Model

我不清楚这种转换应该如何运作;所以我把它留给你。