Оптимизировать решение для многопользовательского типа, два из них имеют общее поле manytomany

Система является многопользовательской (на основе сигналов) :

  • Компания

  • Водитель

  • Клиент

    • Компания и водитель имеют команду и могут приглашать водителей в свои команды
    • .
    • Компания и водитель делят командное поле, которое является полем "многие ко многим"
    • .
  • Я нашел два решения :

    • Первое:

      • Поле команды будет в режиме BaseUser.
      • .
      • Про:
        • Простая реализация
      • Против:
        • У пользователя-клиента будет поле команды на его записи в базе данных, что означает избыточные данные.
    • Второе:

      • Создать абстрактную модель, содержащую поле команды, затем обе модели компании, водителя будут наследовать эту абстрактную модель.
      • Плюсы:
        • Избежать создания поля команды в пользователе клиента
      • Против:
        • Больше кода
        • Код немного грязноват
        • Увеличить сложность логики приглашения и присоединения (должно быть оптимизировано для получения информации о том, является ли создатель приглашения компанией или водителем, тогда сделка зависит от этого), (в первом решении создателем всегда будет BaseUser)
        • .
        • Добавить дополнительные поля в объект приглашения, ссылающиеся на CompanyMembership и DriverMembership, (в первом решении было просто поле, ссылающееся на общее членство), но этот объект приглашения будет удаляться после присоединения, будет храниться в базе данных на случай, если приглашенный не присоединился, и может быть решен с помощью celery для удаления просроченных приглашений.
        • .

есть ли другое лучшее решение, и если нет, то какое из них лучше?

Реализация второго решения:

Абстрактные модели, которые содержат общие поля:

class CommonFields(models.Model):
    team = models.ManyToManyField(
        "driver.DriverProfile", symmetrical=False, blank=True, through="Membership")
    user = models.OneToOneField(
        "users.User", on_delete=models.CASCADE, related_name="driver_profile")

    class Meta:
        abstract = True


class BaseMembership(models.Model):
    class MemberType(models.TextChoices):
        FAMILY = "FAMILY", _("Family")
        TAXI_DRIVER = "TAXI DRIVER", _("Taxi Driver")

    member = models.ForeignKey(
        "driver.DriverProfile", on_delete=models.CASCADE, null=True)
    date_joined = models.DateTimeField(default=now)
    member_type = models.CharField(
        _("Type"), max_length=50, choices=MemberType.choices, default=MemberType.TAXI_DRIVER
    )

    class Meta:
        abstract = True
        default_related_name = 'team_members'

    def save(self, *args, **kwargs):
        # Only allow this relationship to be created if
        if self.leader != self.member:
            super(BaseMembership, self).save(*args, **kwargs)

Членство в компании:

# Company Memebership  ===========================================================================
class Membership(BaseMembership):
    leader = models.ForeignKey(
        CompanyProfile, on_delete=models.CASCADE, related_name='owner')

    class Meta:
        default_related_name = 'company_members'

Членство водителей

# Driver Membership ================================================================================
class Membership(BaseMembership):

    leader = models.ForeignKey(
        DriverProfile, on_delete=models.CASCADE, related_name='owner')

Представление приглашения и присоединения (я еще не обновил его, чтобы справиться со вторым решением):

class InvitationView(viewsets.ViewSet): permission_classes = [IsAuthenticated]

@action(["post"], detail=False)
def invite(self, request, format=None):
    serializer = InvitationSerializer(
        data=request.data, context={"request": request})
    serializer.is_valid(raise_exception=True)
    invitation = serializer.save()
    uid = invitation.uid
    data = {"uid": uid}
    return Response(data, status=status.HTTP_201_CREATED)

@action(["post"], detail=False)
def join(self, request, format=None):
    serializer = JoinSerializer(
        data=request.data)
    serializer.is_valid(raise_exception=True)
    uid = serializer.uid
    try:
        invitation = Invitation.objects.get(uid=uid)
    except (Invitation.DoesNotExist):
        return Response(
            "invalid invitation code", status=status.HTTP_400_BAD_REQUEST
        )
    membership = invitation.membership
    invitation.delete()
    driver = request.user.driver_profile
    if driver != membership.leader:
        membership.driver = driver
        membership.save()
        return Response("joined successfully", status=status.HTTP_201_CREATED)
    else:
        membership.delete()
        return Response("leader can't join as a driver to his team", status=status.HTTP_400_BAD_REQUEST)

Я бы настроил свои таблицы следующим образом:

Компания
companyid, PK
название компании, NN

Team
teamid, PK
имя команды, NN

Компания-Команда
. companyid, FK, Company.companyid, NN
teamid, FK, Team.teamid, NN

Driver
driverid, PK
имя драйвера, NN

Водитель-группа
. driverid, FK, Driver.driverid, NN
teamid, FK, Team.teamid, NN

Client
clientid, PK
имя клиента, NN

  • PK: первичный ключ
  • FK: внешний ключ
  • NN, not null

В каждую таблицу можно добавить много полей, но эта структура учитывает ваши отношения "многие ко многим" и обеспечивает большую гибкость для ваших SQL-запросов и будущего обслуживания.

Для Client вы не указали, какие отношения к ним применяются. Но водитель и клиент - это разные объекты. Если, конечно, вы не хотите использовать другой уровень абстракции и иметь таблицу пользователей с типами водитель и клиент. Если один и тот же человек не может быть одновременно клиентом и водителем, я бы не стал добавлять эту сложность.

Вернуться на верх