Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to annotate field without duplicate fields django

So I basically have this simple model:

class BaseLesson(models.Model):
    YOUTUBE_VIDEO = '0'
    MARKDOWN = '1'

    TYPE_CHOICES = (
        (YOUTUBE_VIDEO, 'youtube-video'),
        (MARKDOWN, 'markdown'),
    )

    type = models.CharField(
        max_length=10, choices=TYPE_CHOICES, default=MARKDOWN, verbose_name=_('type'))

    shown_users = models.ManyToManyField(
        User, related_name='lessons', verbose_name=_('shown users'), blank=True)

    objects = managers.BaseLessonManager()

There is a many-to-many relationship with the User model in shown_users

And I wanna annotate the is_shown status based on the many-to-many table, so I did this:

class BaseLessonManager(InheritanceManager, CachingManager):
    def get_lesson_with_is_shown(self, user):
    
        shown_user_case = django_models.Case(
            django_models.When(shown_users__id=user.id,
                               then=django_models.Value(True)),
            default=django_models.Value(False),
            output_field=django_models.BooleanField())

        return self.get_queryset().annotate(
            is_shown=shown_user_case)

The problem with this is that if user1 and user2 saw the same lesson it will be duplicate, for example:

+-----------------+-----------+
|    lesson_id    |  user_id  |
+-----------------+-----------+
|        1        |     1     |
|        1        |     2     |
|        1        |     3     |
+-----------------+-----------+

For such case, I will get these duplicated lessons:

            {
                "id": 1
                "type": "0",
                "is_shown": true
            },
            {
                "id": 1
                "type": "0",
                "is_shown": false
            },
            {
                "id": 1
                "type": "0",
                "is_shown": false
            }

So it's checking each related lesson field in the SHOWN_USERS table... sample photo: https://imgur.com/GJCPWjk

What I tried so far:

1. Exclude:

I added an exclude expression to get rid of the extra lessons:

return self.get_queryset().annotate(
       is_shown=shown_user_case).exclude(
       django_models.Q(is_shown=False) & django_models.Q(shown_users__id=user.id))

And I think this is super ugly cuz if I have 1000 users and 50 lessons it means I'm taking all 50000 fields then filter 50 of them :(

Is there any cleaner way to do that ?

2. Distinct:

I have tried distinct before and it's not fixing the problem, instead of shown the lesson three times it will show:

  • once (is_shown = True),
  • and another time (is_shown = False)
like image 896
Yaser Al-Najjar Avatar asked May 24 '18 16:05

Yaser Al-Najjar


1 Answers

I managed to fix the problem, and If figured out that my way of querying many-to-many is the problem, instead of shown_users__id == user.id I used id__in==user.lessons.values('id'), full code:

class BaseLessonManager(InheritanceManager, CachingManager):
    def with_is_shown(self, user):

        user_shown_lessons = user.lessons.values('id')

        shown_user_case = django_models.Case(
            django_models.When(id__in=user_shown_lessons,
                           then=django_models.Value(True)),
            default=django_models.Value(False),
            output_field=django_models.BooleanField())

        return self.get_queryset().annotate(
            is_shown=shown_user_case)
like image 131
Yaser Al-Najjar Avatar answered Sep 29 '22 14:09

Yaser Al-Najjar