Запрос Django Q-Object возвращает дубликаты записей, не соответствующие базе данных
Я пытаюсь определить принадлежность пользователя к событию (организационная структура высшего уровня в моем приложении). Каждое событие содержит пользователей с одним (и только одним) из трех уровней доступа (менеджер, редактор или наблюдатель). В любой момент времени должен быть хотя бы один manager, по умолчанию это создатель события.
Я использую контекстный процессор для запроса БД на любые события, к которым имеет доступ мой активный пользователь (через одну из трех ролей). Это должно вернуть список всех соответствующих event объектов.
Моя проблема заключается в том, что когда я добавляю в event хотя бы двух дополнительных пользователей другой роли, в списке events для моего пользователя появляются дублирующие записи для этого event (по одной на дополнительную роль и по две на дополнительного пользователя в этой роли помимо первого пользователя). Это означает, что если я добавлю двух редакторов, я увижу две записи для события. Добавив еще двух наблюдателей, я увижу четыре записи, добавив третьего наблюдателя, я увижу шесть записей. Добавление только одного наблюдателя или редактора или любого количества дополнительных менеджеров (когда мой собственный активный пользователь является менеджером) не приведет к появлению дубликатов.
Записи в БД и SQL-запросы выглядят корректно, и при запросе фильтрации членства только для одной роли за раз, результаты тоже корректны. Только при объединении запросов Q-объектов через OR возникает странное поведение.
.
(На данный момент я обошел эту проблему, используя .distinct() в запросе, но это не решает основную проблему.)
Может ли кто-нибудь объяснить мне, что я упускаю? Код ниже, включая некоторые из моих отладочных отпечатков и логику управления ролями для профилей пользователей (хотя это, кажется, работает нормально, судя по содержимому БД) для полноты картины.
Я использую Python 3.9, Django 3.2.5 и SQLite 3.31.1
context_processor.py (без .distinct(); User is manager)
from base.models import *
from django.db.models import Q
def eventlist(request):
args = {}
if request.user.id is not None and request.user.id > 0:
print(request.user.profile)
query = (Q(event_observer=request.user.profile) | Q(event_editor=request.user.profile) | Q(event_manager=request.user.profile)) #returns duplicates of an event if more than one editor/observer each exist in it, NOT expected
user_events = Event.objects.filter(query)
args.update({"user_events": user_events})
print(user_events)
print(Event.objects.filter(Q(event_observer=request.user.profile))) #returns nothing, as expected
print(Event.objects.filter(Q(event_editor=request.user.profile))) #returns nothing, as expected
print(Event.objects.filter(Q(event_manager=request.user.profile))) #returns all proper events, as expected
print(Event.objects.filter(Q(event_manager=request.user.profile) | Q(event_editor=request.user.profile))) #returns duplicates of an event if more than one editor exists in it, NOT expected
print(Event.objects.filter(Q(event_observer=request.user.profile) | Q(event_editor=request.user.profile))) #returns nothing, as expected
return args
base/models.py (События)
import hashlib
from django.db import models
from django.template.defaultfilters import slugify
from base.utils import get_or_none
from django.contrib.auth.models import User
class Event(models.Model):
slug = models.SlugField(max_length=10, default=None)
name = models.CharField(verbose_name="Name", unique=True, max_length=50)
prefix = models.CharField(verbose_name="Prefix (for data entries)", unique=True, max_length=8)
date = models.DateField(verbose_name="Starting Date (DD.MM.YYYY)")
description = models.TextField(verbose_name="Description")
def __str__(self):
return str(self.name)
(... some stuff relating to creating unique slugs and names for new events...)
accounts/models.py (Профили пользователей)
from django.db import models
from django.contrib.auth.models import User
from base.models import Event
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
user = models.OneToOneField(User, related_name="profile", on_delete=models.CASCADE)
is_observer = models.ManyToManyField(Event, related_name="event_observer", blank=True)
is_editor = models.ManyToManyField(Event, related_name="event_editor", blank=True)
is_manager = models.ManyToManyField(Event, related_name="event_manager", blank=True)
def __str__(self):
return self.user.username
def get_permission(self, event):
print('Observer:', self.is_observer.all())
print('Editor:', self.is_editor.all())
print('Manager:', self.is_manager.all())
print()
print(self.is_observer.filter(id=event))
print(self.is_editor.filter(id=event))
print(self.is_manager.filter(id=event))
if self.is_observer.filter(id=event).exists():
return 'observer'
elif self.is_editor.filter(id=event).exists():
return 'editor'
elif self.is_manager.filter(id=event).exists():
return 'manager'
else:
return None
def change_permissions(self, event, level):
if level == "observer":
self.is_observer.add(event)
self.is_editor.remove(event)
self.is_manager.remove(event)
elif level == "editor":
self.is_observer.remove(event)
self.is_editor.add(event)
self.is_manager.remove(event)
elif level == "manager":
self.is_observer.remove(event)
self.is_editor.remove(event)
self.is_manager.add(event)
elif level == "remove":
self.is_observer.remove(event)
self.is_editor.remove(event)
self.is_manager.remove(event)
else:
return 1
return 0
def set_observer(self, event):
return self.change_permissions(event, "observer")
def set_editor(self, event):
return self.change_permissions(event, "editor")
def set_manager(self, event):
return self.change_permissions(event, "manager")
def remove_from_event(self, event):
return self.change_permissions(event, "remove")
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()