简体   繁体   中英

How do I sort a Django queryset by frequency of occurance from a list and ensure the remaining objects follow?

I have a Django application running Django 3+ and Python 3.8+.

I am trying to fill the front page of my site with relevant accounts to the user currently logged in. So if user john_person is logged in, I want them to see users who have similar resources to them followed by accounts who just have a lot of resources followed by the remaining accounts.

models.py

class UserAccount(AbstractBaseUser, PermissionsMixin):
  email = models.EmailField(max_length=255, unique=True)
  first_name = models.CharField(max_length=255, default='Anonymous')
  last_name = models.CharField(max_length=255, default='User')
  user_name = models.CharField(max_length=255)
  is_private = models.BooleanField(default=False)

class Resource(models.Model):
  user_account=models.ForeignKey(UserAccount, on_delete=models.CASCADE, related_name='resources')
  resource_name = models.CharField(max_length=255)

class Tag(models.Model):
  resource = models.ForeignKey(Resource, on_delete=models.CASCADE, related_name='tags')
  name = models.CharField(max_length=255)

I have a list of common tags (belonging to the logged in user), let's call this:

common_tags=['tag1','tag2','tag3']

This list can vary in length.

My model parent-children are related by a many to one relationship.

User->Resource->Tag

Users have resources, and resources have tags.

My goal:

I need to be able to query all my users and filter it in such a way that I get an ordered queryset where the users who have the most common_tags as their tags come up on top. That set of people need to be sorted by who has more tags in common (not based on how many of each tag they have, just the common occurrences with logged in user). This same query set should also contain the remaining users who do not have any of the common tags. Lastly, this queryset should not have any repeats.

So far I have:

  relevant_accounts= User.objects.all()\
  .annotate(count_resources=Count('resources')).order_by('-count_resources')\
  .order_by(Case(When(resources__tags__name__in=common_tags, then=0),default=1, 
  output_field=FloatField()))

This appears to work, but I get repeated values.

Running.distinct() does not resolve the issue.

Edit: Sample Input/Output

User A: Tags = ['A','A','B','C'] User B: Tags = ['A','B','B','C'] User C: Tags = ['A','B','B','B']

Sample User 1: common_tags=['A','C']

Return: Users A->B->C

Sample User 2: common_tags=['B','C']

Return: Users B->C->A OR C->B->A

Edit: Also tried this. I like the level of customization I get but still duplicates.

is_in_query = Q(resources__tags__name__icontains=common_tags)
    is_not_in_query = ~Q(resources__tags__name__in=common_tags)

    qs_simple = (User.objects.filter(is_private=False).annotate(count_resources=Count('resources'))
    .annotate(
    search_type_ordering=Case(
    When(Q(count_resources__gte=6) & is_in_query, then=1),
    When(Q(count_resources__gte=3) & Q(count_resources__lte=5) & is_in_query, then=2),
    When(Q(count_resources__gte=0) & Q(count_resources__lte=2) & is_in_query, then=3),
    When(Q(count_resources__gte=3) & is_not_in_query, then=4),
    When(Q(count_resources__gte=0) & Q(count_resources__lte=2) & is_not_in_query, then=5),
    default=7,
    output_field=IntegerField(),
    ))
    .order_by('-search_type_ordering',).distinct()
    )

This can be accomplished by using a Count annotation with a filter. First annotate each User with the number of resources and common tags and then order by the annotations

User.objects.annotate(
    count_resources=Count('resources', distinct=True), 
    count_common_tags=Count(
        'resources__tags__name',
        filter=Q(resources__tags__name__in=common_tags)
    )
).order_by(
    '-count_common_tags',
    '-count_resources'
)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM