简体   繁体   English

Django Unique Together(带外键)

[英]Django Unique Together (with foreign keys)

I have a situation where I want to use the Meta options of unique_together to enforce a certain rule, here's the intermediary model:我有一种情况,我想使用unique_together的 Meta 选项来执行某个规则,这是中间模型:

class UserProfileExtension(models.Model):
    extension = models.ForeignKey(Extension, unique=False)
    userprofile = models.ForeignKey(UserProfile, unique=False)
    user = models.ForeignKey(User, unique=False)  

    class Meta:
        unique_together = (("userprofile", "extension"),
                           ("user", "extension"),
                           # How can I enforce UserProfile's Client 
                           # and Extension to be unique? This obviously
                           # doesn't work, but is this idea possible without
                           # creating another FK in my intermediary model 
                           ("userprofile__client", "extension"))

and here's UserProfile:这是用户配置文件:

class UserProfile(models.Model):
    user = models.ForeignKey(User, unique=True)
    client = models.ForeignKey(Client)

Thanks.谢谢。

You can't.你不能。

The unique_together clause is directly translated to the SQL unique index. unique_together子句直接转换为SQL唯一索引。 And you can only set those on columns of a single table, not a combination of several tables.而且您只能在单个表的列上设置这些,而不是多个表的组合。

You can add validation for it yourself though, simply overwrite the validate_unique method and add this validation to it.不过,您可以自己为其添加验证,只需覆盖validate_unique方法并将此验证添加到其中即可。

Docs: http://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.validate_unique文档: http ://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.validate_unique

My 2 cents, complementing the accepted response from @Wolph我的 2 美分,补充了@Wolph 接受的回复

You can add validation for it yourself though, simply overwrite the validate_unique method and add this validation to it.不过,您可以自己为其添加验证,只需覆盖 validate_unique 方法并将此验证添加到其中即可。

This is a working example code someone could find usefull.这是一个有人会发现有用的工作示例代码。

from django.core.exceptions import ValidationError


class MyModel(models.Model):

    fk = models.ForeignKey(AnotherModel, on_delete=models.CASCADE)

    my_field = models.CharField(...)  # whatever

    def validate_unique(self, *args, **kwargs):
        super().validate_unique(*args, **kwargs)
        if self.__class__.objects.\
                filter(fk=self.fk, my_field=self.my_field).\
                exists():
            raise ValidationError(
                message='MyModel with this (fk, my_field) already exists.',
                code='unique_together',
            )

My solution was to use Django's get_or_create .我的解决方案是使用 Django 的get_or_create By using get_or_create, a useless get will occur if the row already exists in the database, and the row will be created if it does not exist.通过使用get_or_create,如果该行已经存在于数据库中,则发生无用的get,如果该行不存在,则创建该行。

Example:例子:

 
extension = Extension.objects.get(pk=someExtensionPK)
userProfile = UserProfile.objects.get(pk=someUserProfilePK)
UserProfileExtension.objects.get_or_create(extension=extension, userprofile=userProfile)

From django 2.2+ versions, it is suggested to use constraint & Index as model class meta option:从 django 2.2+ 版本开始,建议使用约束和索引作为模型类元选项:

https://docs.djangoproject.com/en/3.2/ref/models/options/#django.db.models.Options.unique_together https://docs.djangoproject.com/en/3.2/ref/models/options/#django.db.models.Options.unique_together

https://docs.djangoproject.com/en/3.2/ref/models/options/#django.db.models.Options.constraints https://docs.djangoproject.com/en/3.2/ref/models/options/#django.db.models.Options.constraints

class UniqueConstraintModel(models.Model):
    race_name = models.CharField(max_length=100)
    position = models.IntegerField()
    global_id = models.IntegerField()
    fancy_conditions = models.IntegerField(null=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                name="unique_constraint_model_global_id_uniq",
                fields=('global_id',),
            ),
            models.UniqueConstraint(
                name="unique_constraint_model_fancy_1_uniq",
                fields=('fancy_conditions',),
                condition=models.Q(global_id__lte=1)
            ),
            models.UniqueConstraint(
                name="unique_constraint_model_fancy_3_uniq",
                fields=('fancy_conditions',),
                condition=models.Q(global_id__gte=3)
            ),
            models.UniqueConstraint(
                name="unique_constraint_model_together_uniq",
                fields=('race_name', 'position'),
                condition=models.Q(race_name='example'),
            )
        ]

You need to call Models.full_clean() method to call validate_unique for foreignKey.您需要调用Models.full_clean()方法来调用 foreignKey 的 validate_unique。 You can override save() to call this您可以覆盖 save() 来调用它

class UserProfileExtension(models.Model):
    extension = models.ForeignKey(Extension, unique=False)
    userprofile = models.ForeignKey(UserProfile, unique=False)
    user = models.ForeignKey(User, unique=False)  


    def save(self, *args, **kwargs):
        self.full_clean()
        super().save(*args, **kwargs)

    class Meta:
        unique_together = (("userprofile", "extension"),
                       ("user", "extension"),
                       # How can I enforce UserProfile's Client 
                       # and Extension to be unique? This obviously
                       # doesn't work, but is this idea possible without
                       # creating another FK in my intermediary model 
                       ("userprofile__client", "extension"))
from django.core.exceptions import ValidationError

.....

class UserProfileExtension(models.Model):
    extension = models.ForeignKey(Extension, unique=False)
    userprofile = models.ForeignKey(UserProfile, unique=False)
    user = models.ForeignKey(User, unique=False)  

    def validate_unique(self, *args, **kwargs):
        super(UserProfileExtension, self).validate_unique(*args, **kwargs)
        query = UserProfileExtension.objects.filter(extension=self.extension)
        if query.filter(userprofile__client=self.userprofile.client).exists():
            raise ValidationError({'extension':['Extension already exits for userprofile__client',]})

The first query is to filter all records in UserProfileExtension model which has the same extension we are putting in the current record.第一个查询是过滤 UserProfileExtension 模型中的所有记录,这些记录与我们放入当前记录的扩展名相同。

Then we filter the query returned to find if it already contains userprofile__client which we are passing in the current record.然后我们过滤返回的查询以查找它是否已经包含我们在当前记录中传递的 userprofile__client。

Another possible solution is to add this on your save method from your Model:另一种可能的解决方案是从您的模型中将其添加到您的保存方法中:

def save(self, *args, **kwargs):
    unique = self.__class__.objects.filter( extension =self.extension, userprofile=self.userprofile )
    if unique.exists():
        self.id = unique[0].id
    super(self.__class__, self).save(*args, **kwargs)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM