Home > Enterprise >  Django: UniqueConstraint validator does work neither for model nor for intermediate model
Django: UniqueConstraint validator does work neither for model nor for intermediate model

Time:10-05

My project is basically a platform, where one can post one's recipes with tags (for example, breakfast or dinner) and ingredients (carrot, milk, etc.).

I would want to implement unique constraint for recipe, so that user could not create one with, say, two identical tags or ingredients. So, I have decided to use django UniqueConstraint validator for that, but even migrations did not apply, django says: "No changed were detected".

from django.db import models
from django.contrib.auth import get_user_model
from django.core.validators import MinValueValidator, MaxValueValidator


User = get_user_model()


class Recipe(models.Model):
    author = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name='recipes',
        verbose_name='автор',
    )

    name = models.CharField(
        max_length=200,
        verbose_name='имя',
    )

    image = models.ImageField(
        max_length=4096,
        upload_to='images/%Y-%m-%d',
        verbose_name='фото',
    )

    text = models.TextField(verbose_name='описание')

    ingredients = models.ManyToManyField(
        'Ingredient',
        through='RecipeIngredient',
        verbose_name='ингредиет',
    )

    tags = models.ManyToManyField(
        'Tag',
        through='RecipeTag',
        verbose_name='тэг',
    )

    cooking_time = models.IntegerField(
        default=60,
        validators=(
            MinValueValidator(1),
            MaxValueValidator(44640)
        ),
        verbose_name='время приготовления (в минутах)',
    )

    creation_date = models.DateTimeField(
        auto_now=True,
        verbose_name='дата создания рецепта',
    )

    class Meta:
        verbose_name = 'рецепт'
        verbose_name_plural = 'рецепты'

    def __str__(self):
        return f'Рецепт - id: {self.id}, имя: {self.name}.'


class Tag(models.Model):
    name = models.CharField(
        max_length=256,
        verbose_name='имя',
    )
    color = models.CharField(
        max_length=64,
        verbose_name='цвет',
    )
    slug = models.SlugField(
        max_length=64,
        verbose_name='слаг',
    )

    class Meta:
        verbose_name = 'тэг'
        verbose_name_plural = 'тэги'

    def __str__(self):
        return f'Тэг - id: {self.id}, имя: {self.name}.'


class Ingredient(models.Model):
    name = models.CharField(
        max_length=256,
        verbose_name='имя',
    )

    measurement_unit = models.CharField(
        max_length=32,
        verbose_name='еденица измерения',
    )

    class Meta:
        verbose_name = 'ингредиент'
        verbose_name_plural = 'ингредиенты'

    def __str__(self):
        return f'Игредиент - id: {self.id}, имя: {self.name}.'


class RecipeIngredient(models.Model):
    """Intermediate "join" table for a relation between a recipe
    and an ingredient. Additionaly implements <amount> field.
    """
    recipe = models.ForeignKey(
        'Recipe',
        on_delete=models.CASCADE,
        verbose_name='рецепт',
    )

    ingredient = models.ForeignKey(
        'Ingredient',
        on_delete=models.CASCADE,
        verbose_name='игредиент',
    )

    amount = models.IntegerField(
        default=1,
        validators=(
            MinValueValidator(1),
            MaxValueValidator(44640)
        ),
        verbose_name='количество',
    )

    class Meta:
        verbose_name = 'отношение рецепта к ингредиенту'
        verbose_name_plural = 'отношение рецепта к ингредиентам'

    def __str__(self):
        return f'РецептИнгредиент - id: {self.id}.'


class RecipeTag(models.Model):
    """Intermediate "join" table for a relation between a recipe and tag.
    """
    recipe = models.ForeignKey(
        'Recipe',
        on_delete=models.CASCADE,
        verbose_name='рецепт',
    )

    tag = models.ForeignKey(
        'Tag',
        on_delete=models.CASCADE,
        verbose_name='тэг',
    )

    class Meta:
        verbose_name = 'отношение рецепта к тэгу'
        verbose_name_plural = 'отношение рецепта к тэгам'

        models.UniqueConstraint(
            fields=('recipe__id', 'tag__id'),
            name='recipe_tag_unique_constraint'
        )

    def __str__(self):
        return f'РецептТэг - id: {self.id}.'

CodePudding user response:

You should add these to a variable named constraints in the Meta options, so:

class RecipeTag(models.Model):
    # ⋮

    class Meta:
        verbose_name = 'отношение рецепта к тэгу'
        verbose_name_plural = 'отношение рецепта к тэгам'

        #   ↓ add this to a variable named constraints
        constraints = [
            models.UniqueConstraint(
                fields=('recipe_id', 'tag_id'),
                name='recipe_tag_unique_constraint'
            )
        ]

The constraint can only span over the table of the RecipeTag, so recipe__id and tag__id do not make much sense, you can work with recipe_id and tag_id.

  • Related