Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filter a contenttype with list of content object's field

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.

like image 304
Robin Avatar asked Jan 03 '17 19:01

Robin


People also ask

What is Django_content_type?

Content types are Django's way of identifying database tables. Every Database table is represented as a row in the content type table which is created and maintained by Django.

What is Django filter?

Django-filter is a generic, reusable application to alleviate writing some of the more mundane bits of view code. Specifically, it allows users to filter down a queryset based on a model's fields, displaying the form to let them do this.

How does Django ContentTypes work?

Instances of ContentType have methods for returning the model classes they represent and for querying objects from those models. ContentType also has a custom manager that adds methods for working with ContentType and for obtaining instances of ContentType for a particular model.

What is Genericrelation in Django?

Basically it's a built in app that keeps track of models from the installed apps of your Django application. And one of the use cases of the ContentTypes is to create generic relationships between models.


1 Answers

I'd say your best bet would be to use a filter from Django filter (link to the rest framework docs), specifically a ModelMultipleChoiceFilter. I'm going to assume you already have an ActivityViewSet to go along with the Activity model.

Firstly you'll want to create a django_filters.FilterSet, probably in a new file such as filters.py, and set up the ModelMultipleChoiceFilter, like so:

import django_filters

from .models import Activity, Tag, User

class ActivityFilterSet(django_filters.FilterSet):
    tags = django_filters.ModelMultipleChoiceFilter(
        name='content_object__tags__name',
        to_field_name='name',
        lookup_type='in',
        queryset=Tag.objects.all()
    )
    users = django_filters.ModelMultipleChoiceFilter(
        name='content_object__user__pk',
        to_field_name='pk',
        lookup_type='in',
        queryset=User.objects.all()
    )

    class Meta:
        model = Activity
        fields = (
            'tags',
            'users',
        )

Then you'll want to tell your viewset to use that filterset:

from .filters import ActivityFilterSet
# ...
class ActivityViewSet(GenericViewSet):
    # all your existing declarations, eg.,
    # serializer_class = ActivitySerializer
    # ...
    filter_class = ActivityFilterSet
    # ...

Once you've got that done, you'll be able to filter your results using GET parameters, eg.,

  • GET /activities?users=1 – everything created by User 1
  • GET /activities?users=1&users=2 – everything created by User 1 or User 2
  • GET /activities?users=1&tags=class – everything created by User 1 with the tag #Class
  • GET /activities?users=1&users=2&tags=class&tags=school – everything created by User 1 or User 2 with the tags #Class or #School
  • and so on
like image 157
ProfSmiles Avatar answered Nov 15 '22 08:11

ProfSmiles