Kakar
Kakar

Reputation: 5599

django - Filter by distinct fields of a model class

I have a model for activities of users:

class Activity(models.Model):
    actor = models.ForeignKey(User)
    action = models.CharField(max_length=100)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')
    pub_date = models.DateTimeField(auto_now_add=True, auto_now=False)

    class Meta:
        verbose_name = 'Activity'
        verbose_name_plural = 'Activities'
        ordering = ['-pub_date']

    def __unicode__(self):
        return ("%s %s") % (self.actor.username, self.action)

views:

def home(request):
    if request.user.is_authenticated():
        user = request.user
        userP = Person.objects.get_or_create(user=user)
        userP = userP[0]
        following = userP.get_following()
        users = []
        actors = list(following.values_list('user', flat=True)) + [user.id]
        activities = Activity.objects.filter(actor__in=actors)            

update

Suppose 3 users likes the same status and user3 was the last one to like that status. Now that will create 3 activities saying "User1 liked status", "User2 liked status", "User3 liked status". What I want is only get one activity per status that is the most recent one. So in this case it would be "User3 liked status".

status model:

class Status(models.Model):
    body = models.TextField(max_length=200)
    image = models.ImageField(blank=True, null=True, upload_to=get_upload_file_name)
    privacy = models.CharField(max_length=1,choices=PRIVACY, default='F')
    pub_date = models.DateTimeField(auto_now_add=True, auto_now=False)
    user = models.ForeignKey(User)
    hearts = generic.GenericRelation(Heart, null=True, blank=True)

class Heart(models.Model):
    user = models.ForeignKey(User)
    pub_date = models.DateTimeField(auto_now_add=True, auto_now=False)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')

Update

This is signals.py to create activity per object (heart or status):

def create_activity_item(sender, instance, signal, *args, **kwargs):
    if kwargs.get('created', True):
        ctype = ContentType.objects.get_for_model(instance)
        if ctype.name == 'Status':
            action = ' shared '

            activity = Activity.objects.get_or_create(
                actor = instance.user,
                action = action,
                content_type = ctype,
                object_id = instance.id,
                pub_date = instance.pub_date
            )

        if ctype.name == 'Heart':
            action = ' gave heart to '

            activity = Activity.objects.get_or_create(
                actor = instance.user,
                action = action,
                content_type = ctype,
                object_id = instance.id,
                pub_date = instance.pub_date
            )

for modelname in [Status, Heart]:
    post_save.connect(create_activity_item, sender=modelname,
                  dispatch_uid="create_activity_item")

Upvotes: 1

Views: 500

Answers (3)

almalki
almalki

Reputation: 4775

This will work only with PostgreSQL:

Activity.objects.order_by('action', 'content_type_id', 'object_id', '-pub_date').distinct('action', 'content_type_id', 'object_id').filter(actor__in=actors)

Upvotes: 1

Todor
Todor

Reputation: 16020

This sounds like a greatest-per-group problem. Can you try this:

activities = Activity.objects.filter(
    id__in=Activity.objects.values('content_type') \
        .annotate(max_id=Max('id')) \
        .values_list('max_id', flat=True) \
)

Upvotes: 0

Daniel Robinson
Daniel Robinson

Reputation: 3387

activities = Activity.objects.filter(actor__in=users).distinct('content_type', 'object_id', 'action')

If your database does not support DISTINCT ON queries, you can do the query then manually run through the objects finding distinct values:

unique_activities = []
activities = Activity.objects.filter(actor__in=users)
for activity in activities:
    unseen = True
    for unique_activity in unique_activities:
        if (activity.action == unique_activity.action and activity.content_type == unique_activity.content_type and activity.object_id == unique_activity.object_id):
            unseen = False
            break
    if unseen:
         unique_activities.append(activity)

print unique_activities

Upvotes: 1

Related Questions