'Django Q-Object query returns duplicate entries not matching the database

I am attempting to ascertain the membership of a user in an event (the highest level organization structure in my application). Every event holds users with one (and only one) of three access levels (manager, editor or observer). At any point, there has to be at least one manager, that being the creator of the event by default. I am using the a context processor to query the DB for any events that my currently active user has access to (via holding one of the three roles). This should return a list of all matching event objects.
My problem is that when I add at least two additional users of another role to the event, the list of events for my user shows duplicate entries for that event (one per additional role, and two per additional user in that role beyond the first user). Meaning that if I add two editors, I will see two entries for the event. Adding another two observers, I see four entries total, adding a third observer will show me six entries. Adding only one observer or editor or any number of additional managers (when my own active user is a manager) will not cause duplicates to show.
DB entries and SQL Queries appear correct, and whenever querying for membership filtering for only one role at a time, the results are correct too. Only when combining the Q-object queries via OR does the weird behavior occur.
(For the time being, I have worked around the issue by using .distinct() on the query, but that does not solve the underlying problem.)

Can someone explain to me what I am missing? Code below, including some of my debug prints and the logic behind managing roles for user profiles (even though that seems to be working fine, going by the contents of the DB) for completeness' sake.

I am using Python 3.9, Django 3.2.5 and SQLite 3.31.1

context_processor.py (without .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 (Events)

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 (User Profiles)

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()


Solution 1:[1]

You can increase number of x-axis coordinates

import numpy as np
import plotly.graph_objects as go

x, y = np.meshgrid(np.linspace(0, 10, 200), np.linspace(0, 10))
y += 2 * np.sin(x * 2 * np.pi / 10)
z = np.exp(-x / 10)

go.Figure(go.Scatter(x=x.flatten(), y=y.flatten(), mode="markers", marker_color=z.flatten()))

enter image description here

experimental

not fully working, inspired from Matplotlib: save plot to numpy array

import numpy as np
import plotly.express as px
import io
import matplotlib.pyplot as plt


x, y = np.meshgrid(np.linspace(0, 10), np.linspace(0, 10))
y += 2 * np.sin(x * 2 * np.pi / 10)
z = np.exp(-x / 10)

# plt.axes("off")
fig, ax = plt.subplots()
# ax.axes.visible = False
ax = ax.pcolormesh(x, y, z)
with io.BytesIO() as buff:
    fig.savefig(buff, format='raw', pad_inches=0, bbox_inches=0)
    buff.seek(0)
    data = np.frombuffer(buff.getvalue(), dtype=np.uint8)
w, h = fig.canvas.get_width_height()
im = data.reshape((int(h), int(w), -1))

px.imshow(im)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Rob Raymond