msln
msln

Reputation: 1543

Django: 'Q' object is not iterable

I have following APIView:

class SubmitFormAPIView(APIView):
    def put(self, request, pk):
        # some other codes
        
        form = Form.objects.get(id=pk)
        tu_filter, target_user = self._validate_target_user(request, form)
        user_status, created = UserFormStatus.objects.get_or_create(
            tu_filter,
            form_id=pk,
            user_id=request.user.pk
        )

        # Some other codes.
    
    def _validate_target_user(request, form):
        if some_conditions:
            return Q(), None
        else:
            try:
                target_user_id = int(request.GET.get('target_user_id))
            except ValueError:
                raise ValidationError()

            target_user = get_user_model().objects.get(id=target_user_id)
            return Q(target_user_id=target_user_id), target_user

but when django wants to execude get_or_create method, raises following error:

TypeError: 'Q' object is not iterable

Note: If _validate_target_user() returns Q(), None, no errors raised and view works fine. The error will be raised when return Q(target_user_id=target_user_id), target_user is returned.

I know, question information is not completed, just I want to know, what may cause this error?

Upvotes: 0

Views: 1437

Answers (2)

Lucas Moeskops
Lucas Moeskops

Reputation: 5443

Instead of returning a Q object, you can also just pass a dictionary of filters instead, like

{ 'target_user_id': target_user_id }

The you can run the get_or_create with **tu_filter as arguments, bypassing the need for Q.

class SubmitFormAPIView(APIView):
    def put(self, request, pk):
        # some other codes
        
        form = Form.objects.get(id=pk)
        tu_filter, target_user = self._validate_target_user(request, form)
        user_status, created = UserFormStatus.objects.get_or_create(
            **tu_filter,
            form_id=pk,
            user_id=request.user.pk
        )

        # Some other codes.
    
    def _validate_target_user(request, form):
        if some_conditions:
            return {}, None
        else:
            try:
                target_user_id = int(request.GET.get('target_user_id))
            except ValueError:
                raise ValidationError()

            target_user = get_user_model().objects.get(id=target_user_id)
            return { 'target_user_id': target_user_id }, target_user

Edit: As to what causes the error, my guess would be that using Q as part of your get_or_create statement is unclear to Django, because it doesn't know what to do with it in case the object needs to be created. A better approach would therefor be:

UserFormStats.objects.filter(tu_filter).get_or_create(form_id=pk, user_id=request.user.pk)

Upvotes: 1

JPG
JPG

Reputation: 88609

From the source of get_or_create(...),

def get_or_create(self, defaults=None, **kwargs):
which indicating that the get_or_create(...) doesn't not accept any args unlike the get() or filter(...) methods. Since your are executing the function as below, Python thinks that the tu_filter is the value for default parameter, which is expected to be an iterable.

get_or_create(
            tu_filter,
            form_id=pk,
            user_id=request.user.pk
        )

Upvotes: 2

Related Questions