Есть ли способ создать уникальный идентификатор над 2 полями? - PullRequest
14 голосов
/ 23 октября 2019

Вот моя модель:

class GroupedModels(models.Model):
    other_model_one = models.ForeignKey('app.other_model')
    other_model_two = models.ForeignKey('app.other_model')

По сути, я хочу, чтобы other_model был уникальным в этой таблице. Это означает, что если есть запись, в которой other_model_one id равен 123, я не должен допускать создание другой записи с other_model_two id как 123. Я могу переопределить clean Я думаю, но мне было интересно, есть ли в django что-то встроенное.

Я использую версию 2.2.5 с PSQL.

Редактировать: Это не простая ситуация вместе,Если я добавлю запись с other_model_one_id=1 и другими other_model_two_id=2, я не смогу добавить еще одну запись с other_model_one_id=2 и другими other_model_two_id=1

Ответы [ 3 ]

10 голосов
/ 29 октября 2019

Я объясняю несколько вариантов здесь, может быть, вам может пригодиться один из них или их комбинация.

Переопределение save

Ваше ограничение - это бизнес-правило, вы можете переопределить save метод для обеспечения согласованности данных:


class GroupedModels(models.Model): 
    # ...
    def clean(self):
        if (self.other_model_one.pk == self.other_model_two.pk):
            raise ValidationError({'other_model_one':'Some message'}) 
        if (self.other_model_one.pk < self.other_model_two.pk):
            #switching models
            self.other_model_one, self.other_model_two = self.other_model_two, self.other_model_one
    # ...
    def save(self, *args, **kwargs):
        self.clean()
        super(GroupedModels, self).save(*args, **kwargs)

Изменение дизайна

Я положил образец, который легко понять. Давайте предположим, что такой сценарий:

class BasketballMatch(models.Model):
    local = models.ForeignKey('app.team')
    visitor = models.ForeignKey('app.team')

Теперь вы хотите избежать того, чтобы команда играла матч сама с собой, а команда A может играть с командой B только один раз (почти по вашим правилам). Вы можете изменить дизайн своих моделей следующим образом:

class BasketballMatch(models.Model):
    HOME = 'H'
    GUEST = 'G'
    ROLES = [
        (HOME, 'Home'),
        (GUEST, 'Guest'),
    ]
    match_id = models.IntegerField()
    role = models.CharField(max_length=1, choices=ROLES)
    player = models.ForeignKey('app.other_model')

    class Meta:
      unique_together = [ ( 'match_id', 'role', ) ,
                          ( 'match_id', 'player',) , ]

ManyToManyField.symmetrical

Это похоже на симметричный вопрос, с которым Django справится. Вместо создания GroupedModels модели, просто создайте поле ManyToManyField с самим собой на OtherModel:

from django.db import models
class OtherModel(models.Model):
    ...
    grouped_models = models.ManyToManyField("self")

Это то, что django имеет, как встроенное для этих сценариев.

1 голос
/ 29 октября 2019

Это не очень удовлетворительный ответ, но, к сожалению, правда в том, что нет способа сделать то, что вы описываете, с помощью простой встроенной функции.

То, что вы описали с помощью clean, будет работать, но вы должны быть осторожны, чтобы вызывать его вручную, так как я думаю, что он вызывается автоматически только при использовании ModelForm. Возможно, вы сможете создать сложное ограничение базы данных , но оно будет находиться за пределами Django, и вам придется обрабатывать исключения из базы данных (что может быть сложно в Django, когда выполняется транзакция).

Может быть, есть лучший способ структурировать данные?

0 голосов
/ 03 ноября 2019

Уже есть большой ответ от Дани Эррера , однако я хотел бы более подробно остановиться на нем.

Как объясняется вВо-вторых, решение, которое требует OP, состоит в том, чтобы изменить конструкцию и реализовать два уникальных ограничения попарно. Аналогия с баскетбольными матчами очень практично иллюстрирует проблему.

Вместо баскетбольного матча я использую пример с футбольными (или футбольными) играми. В футбольную игру (которую я называю Event) играют две команды (в моих моделях команда Competitor). Это отношение «многие ко многим» (m:n), с n, ограниченным двумя, в данном конкретном случае принцип подходит для неограниченного числа.

Вот как выглядят наши модели:

class Competitor(models.Model):
    name = models.CharField(max_length=100)
    city = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class Event(models.Model):
    title = models.CharField(max_length=200)
    venue = models.CharField(max_length=100)
    time = models.DateTimeField()
    participants = models.ManyToManyField(Competitor)

    def __str__(self):
        return self.title

Событие может быть:

  • Название: Кубок Карабао, 4-й раунд,
  • Место проведения: Anfield
  • Время: 30. Октябрь2019, 19:30 GMT
  • участники:
    • имя: Ливерпуль, город: Ливерпуль
    • имя: Арсенал, город: Лондон

Теперь мы должны решить вопрос из вопроса. Django автоматически создает промежуточную таблицу между моделями с отношением «многие ко многим», но мы можем использовать пользовательскую модель и добавлять дополнительные поля. Я называю эту модель Participant:

class Participant(models.Model):
    ROLES = (
        ('H', 'Home'),
        ('V', 'Visitor'),
    )
    event = models.ForeignKey(Event, on_delete=models.CASCADE)
    competitor = models.ForeignKey(Competitor, on_delete=models.CASCADE)
    role = models.CharField(max_length=1, choices=ROLES)

    class Meta:
        unique_together = (
            ('event', 'role'),
            ('event', 'competitor'),
        )

    def __str__(self):
        return '{} - {}'.format(self.event, self.get_role_display())

У ManyToManyField есть опция through, которая позволяет нам указать промежуточную модель. Давайте изменим это в модели Event:

class Event(models.Model):
    title = models.CharField(max_length=200)
    venue = models.CharField(max_length=100)
    time = models.DateTimeField()
    <strong>participants = models.ManyToManyField(
        Competitor,
        related_name='events', # if we want to retrieve events for a competitor
        through='Participant'
    )</strong>

    def __str__(self):
        return self.title

Уникальные ограничения теперь автоматически ограничат число участников на событие до двух (потому что есть только две роли: Home и Посетитель ).

В определенном событии (футбольной игре) может быть только одна домашняя команда и только одна команда посетителей. Клуб (Competitor) может выступать в роли домашней или гостевой команды.

Как мы теперь управляем всеми этими вещами в админе? Например:

from django.contrib import admin

from .models import Competitor, Event, Participant


class ParticipantInline(admin.StackedInline): # or admin.TabularInline
    model = Participant
    max_num = 2


class CompetitorAdmin(admin.ModelAdmin):
    fields = ('name', 'city',)


class EventAdmin(admin.ModelAdmin):
    fields = ('title', 'venue', 'time',)
    inlines = [ParticipantInline]


admin.site.register(Competitor, CompetitorAdmin)
admin.site.register(Event, EventAdmin)

Мы добавили Participant в качестве встроенного в EventAdmin. Когда мы создаем новый Event, мы можем выбрать команду хозяев и команду посетителей. Опция max_num ограничивает количество записей до 2, поэтому на событие может быть добавлено не более 2 команд.

Это может быть реорганизовано для разных случаев использования. Скажем, наши соревнования - соревнования по плаванию, и вместо дома и посетителя у нас есть дорожки с 1 по 8. Мы просто реорганизуем Participant:

class Participant(models.Model):
    ROLES = (
        ('L1', 'lane 1'),
        ('L2', 'lane 2'),
        # ... L3 to L8
    )
    event = models.ForeignKey(Event, on_delete=models.CASCADE)
    competitor = models.ForeignKey(Competitor, on_delete=models.CASCADE)
    role = models.CharField(max_length=1, choices=ROLES)

    class Meta:
        unique_together = (
            ('event', 'role'),
            ('event', 'competitor'),
        )

    def __str__(self):
        return '{} - {}'.format(self.event, self.get_role_display())

С помощью этой модификации мы можем иметь это событие:

  • название: FINA 2019, финал на 50 м на спине среди мужчин,

    • место: Муниципальный центр водных видов спорта Университета Намбу
    • время: 28. июль 2019, 20:02 UTC + 9
    • участники:

      • имя: Майкл Эндрю, город: Эдина, США, роль: полоса 1
      • имя: Зейн Уодделл,город: Блумфонтейн, ЮАР, роль: переулок 2
      • имя: Евгений Рылов, город: Новотроицк, Россия, роль: переулок 3
      • имя: Климент Колесников, город: Москва, Россия, роль: полоса 4

      // и т. д. с полосы 5 на полосу 8 (источник: Википедия

Пловец может появиться только один раз в жару, и полоса может быть занята только один раз в жару.

Я положил код в GitHub: https://github.com/cezar77/competition.

Опять все кредиты идут вДани Эррера. Я надеюсь, что этот ответ принес читателям дополнительную пользу.

...