Оптимизировать решение для многопользовательского типа, два из них имеют общее поле 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 вы не указали, какие отношения к ним применяются. Но водитель и клиент - это разные объекты. Если, конечно, вы не хотите использовать другой уровень абстракции и иметь таблицу пользователей с типами водитель и клиент. Если один и тот же человек не может быть одновременно клиентом и водителем, я бы не стал добавлять эту сложность.