简体   繁体   中英

Django Rest Framework: advanced queryset filtering based on another model

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, ie 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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM