R. Wenger
R. Wenger

Reputation: 288

Django Rest Framework: Order by user dependent field

I have some products, of which a user can mark some as his favorite products.

The favorites are modeled as a many to many relationship between a user profile and a product.

I would now like to get the products via API call, but ordered by favorites first, everything else second. Now as this depends on the current request, i cannot do this via Product.objects.all().order_by(...), as the model does not know the current user, and I know of now way to tell the query through the serializer or ModelViewSet.

I tried getting two querysets, one with all products, one with all favorites from the user profile in the ModelViewSet, like this:

class ProductViewSet(viewsets.ModelViewSet):
    serializer_class = ProductCreateSerializer
    # ...

    def get_queryset(self):
        favorited_products = self.request.user.profile.favorites.all()
        products = Product.objects.all()

        queryset = favorited_products | products

        return queryset

This does work, as the entities stay in this order. But this view returns a couple of hundred entities, so when I try to limit the queryset with return queryset[:30], the default ordering seems to take over.

Is what I'm trying to do even easily achievable? Did anybody solve a similar problem already?

Here are my models:

class Product(models.Model):
    product_name = models.CharField(max_length=200)
    # ...


    def __unicode__(self):
        return self.product_name


class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    favorites = models.ManyToManyField(Product)

    @receiver(post_save, sender=User)
    def create_user_profile(sender, instance, created, **kwargs):
        if created:
            Profile.objects.get_or_create(user=instance)

    @receiver(post_save, sender=User)
    def save_user_profile(sender, instance, **kwargs):
        instance.profile.save()

And my serializer:

class FavoriteField(serializers.BooleanField):
    def get_attribute(self, instance):

        return self.context['request'].user.profile.favorites.filter(pk=instance.pk).exists()

class ProductSerializer(serializers.ModelSerializer):
    favorite = FavoriteField()

    def update(self, instance, validated_data):
        instance = super().update(instance, validated_data)
        is_favourite = validated_data.get('favorite')  # can be None
        if is_favourite is True:
            self.context['request'].user.profile.favorites.add(instance)
        elif is_favourite is False:
            self.context['request'].user.profile.favorites.remove(instance)
        return instance

Upvotes: 1

Views: 1067

Answers (1)

R. Wenger
R. Wenger

Reputation: 288

I have found a solution, using Count, Case and IntegerField:

user_id = self.request.user.pk
queryset = Product.objects \
        .annotate(favorited=Count(Case(When(favorited_by__user__pk=user_id, then=1), output_field=IntegerField()))) \
        .order_by("-favorited")

Upvotes: 2

Related Questions