Django Rest Framework: advanced queryset filtering based on another model

Viewed 894

I am using Django Rest Framework as a backend for an app.

I have a User that has one Wallet. Then I have Item. If a User wants an Item it creates an instance in his/her Wallet called WalletItem. All works well.

Now I want to limit the number of items for the User using an attribute limit_usage.

First, I added a check to post method adding new instance that checks the number of item instances in User's Wallet. So the user gets 403 when trying to add third WalletItem if limit_usage == 2 for this Item.

I would like to override a get_queryset() method or queryset in list()/retrieve() methods so that if anonymous user calls /items/ there are unfiltered items in response. However if the user is authenticated I would like to filter only those Items that s/he is allowed to put in the Wallet, i.e. those that have not exceeded limit_usage for current user.

class Wallet(models.Model):
    user = models.OneToOneField('auth.User', related_name='wallet')

class Item(models.Model):
    valid_from = models.DateTimeField()
    valid_to = models.DateTimeField()
    limit_usage = models.PositiveSmallIntegerField(default=0)

class WalletItem(models.Model):
    wallet = models.ForeignKey('Wallet', related_name='%(class)ss')
    offer = models.ForeignKey('Item', related_name='offer')

class ItemViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Item.objects.all().order_by('-created_at')
    serializer_class = ItemSerializer

    def list(self, request, *args, **kwargs):
        time_now = now()
        self.queryset = self.queryset.filter(
            valid_from__lte=time_now,
            valid_to__gte=time_now,
        )
        serializer = self.get_serializer(self.queryset, many=True)
        return Response(serializer.data)

I created a method of Item class that should have helped me but I realized I cannot use it in the queryset:

def is_visible_for_user(self, user=None):
    if not self.limit_usage or not user:
        return True
    ct = WalletItem.objects.filter(item=self, wallet=user.wallet).count()
    return self.limit_usage > ct

So I can iterate through the queryset to see if each item can be visible for the user, however I cannot construct the queryset out of this filtered list. I found something similar here on SO: Django REST Framework : filtering with another table but the response did not help me.

1 Answers

You firstly need to check if user is authenticated, it not, then return every Item. Then to filter out Item objects if corresponding WalletItem objects exceeded its limit.

from django.db.models import Count, F, Sum

...

class ItemViewSet(viewsets.ReadOnlyModelViewSet):
    def get_queryset(self):
        queryset = super().get_queryset()
        user = self.request.user
        if user.is_anonymous:
            return queryset

        queryset = queryset.annotate(user_wallet_items=Sum(
            Case(
                When(walletitem__wallet_id=user.wallet_id, then=1),
                default=0, output_field=IntegerField()
            )) \
            .filter(user_wallet_items__lte=F('limit_usage'))
        return queryset

I suggest you to move your filtration based on current time to the same get_queryset() method, since it's belong there.

Note: i've not tested this approach.

Related