Filter a contenttype with list of content object's field

Question

I creating an app where users can post with its related tags:

class Tag(models.Model):
    name = models.CharField(max_length=255, unique=True)

class Post(models.Model):
    user = models.ForeignKey(User)
    body = models.TextField()
    tags = models.ManyToManyField(Tag)
    pub_date = models.DateTimeField(default=timezone.now)
    activity = GenericRelation(Activity, related_query_name="posts")

class Photo(models.Model):
    user = models.ForeignKey(User)
    file = models.ImageField()
    tags = models.ManyToManyField(Tag)
    pub_date = models.DateTimeField(default=timezone.now)
    activity = GenericRelation(Activity, related_query_name="photos")

class Activity(models.Model):
    actor = models.ForeignKey(User)
    verb = models.PositiveIntegerField(choices=VERB_TYPE)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    pub_date = models.DateTimeField(default=timezone.now)

What I want to do is get/filter maximum of 5 latest/recent Activity objects, with the a list of users, and a list of tags from the list of Post objects tags field and return json using django-rest-framework to view in the client side.

Eg activities:

  • UserA created a new Post obect with tags(#class, #school)
  • UserB created a new Post object with tags(#professor, #teacher)
  • UserC created a new Post object with tags(#school, #university)
  • UserD created a new Post object with tags(#university, #school)

So say I want to filter Activity with user_list=[UserA, UserC] and tag_list = [#class, #teacher]

It should return:

  • UserA created a new Post obect with tags(#class, #school)
  • UserC created a new Post object with tags(#school, #university)
  • UserB created a new Post object with tags(#professor, #teacher)

To filter the Activity with users, I can query this way:

Activity.objects.filter(actor__in=user_list)

But, how do I filter Activity with the content_object's (i.e.Post or Photo) field (i.e. Post.tags or Photo.tags)? Now I am doing this way:

Activity.objects.filter(posts__tags__in=tag_l)
Activity.objects.filter(photos__tags__in=tags)

So to sum up, If I have need activities with list of users and list of tags I have to do like this:

activites = Activity.objects.filter(
    Q(actor__in=user_list) |
    Qposts__tags__in=tag_list) |
    Q(photos__tags__in=tag_list)
)

But suppose there will be more than two ContentType model classes then I'd have to again add another Q(moreModel__tags__in=tag_list). So, I hope there's a better way to optimize the process.


Show source
| django   | django-models   | django-rest-framework   | django-queryset   2017-01-03 20:01 2 Answers

Answers ( 2 )

  1. 2017-01-03 21:01

    I will give you an example from django updown, it have a similiar model: https://github.com/weluse/django-updown/blob/master/updown/models.py#L31

    >>> from updown.models import Vote
    >>> Vote.objects.first()
    <Vote: john voted 1 on Conveniently develop impactful e-commerce>
    >>> v = Vote.objects.first()
    >>> v.content_object
    <Thread: Conveniently develop impactful e-commerce>
    >>> v.content_object.__class__
    <class 'app_forum.models.Thread'>
    >>> [ v.content_type for v in Vote.objects.all() if v.content_object.__class__.__name__ == 'Thread' ]
    [<Thread: Conveniently develop impactful e-commerce>, <Thread: Quickly evisculate exceptional paradigms>]
    >>> 
    # You can also use with
    >>> user_ids = [ u.content_type.id for u in Vote.objects.all() if u.content_object.__class__.__name__ == 'User' ]
    >>> user_ids
    [1, 52, 3, 4]
    >>> from django.contrib.auth.models import User
    >>> User.objects.filter(pk__in=user_ids)
    [<User: John>, <User: Alex>, <User: Roboto>, <User: Membra>]
    >>>
    >>> from django.contrib.contenttypes.models import ContentType
    >>> ContentType.objects.get_for_model(v.content_object)
    <ContentType: Detail Thread>
    >>> 
    

    You also can use ContentType.objects.get_for_model(model_instance) such as this: https://github.com/weluse/django-updown/blob/master/updown/fields.py#L70

    In your problem, maybe can use with this..

    >>> photo_ids = [ ac.content_type.id for ac in Activity.objects.all() if ac.content_object.__class__.__name__ == 'Photo' ]
    >>> Activity.objects.filter(content_type__id__in=photo_ids)
    # or
    >>> photo_ids = [ ac.content_type.id for ac in Activity.objects.all() if content_type.model_class().__name__ == 'Photo']
    >>> Activity.objects.filter(content_type__id__in=photo_ids)
    

    Hope it can help..

  2. 2017-01-06 00:01

    For this method set the related_query_name to the name of the model in lower case or verbose_name of the model.

    You can first filter out the content type present in the Activity model.

    content_types = ContentType.objects.filter(activity__id__isnull=False)
    

    Now use these content types to build the lookup.

    q = Q(actor__in=user_list)
    for content_type in content_types:
        arg = content_type.name + '__tags__in'
        kwargs = {arg: tag_list}
        q = q | Q(**kwargs)
    

    Now you can filter the activities using this lookup.

    activities = Activity.objects.filter(q).distinct()
    
◀ Go back