2

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 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you very much, it works like a charm. I only added Q(limit_usage=0) to cover the items that has no limit. Thanks to your reply I discovered whole new world of conditional expressions
@JurajBezručka you are welcome, I discovered the same while I was preparing to answer.:)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.