Home > front end >  Returning queryset.values() from viewset.get_queryset()?
Returning queryset.values() from viewset.get_queryset()?

Time:04-04

Good day,

I'm having difficulty trying to return queryset.values() from a viewset's get_queryset(). After finding 6 different methods for allegedly dealing with this, I am still left uncertain how to manage it correctly as my only working solution feels hacky.

I use values so that I can get the name of the UserAccount through the foreign-key, rather than it's id. I tried using only() (replacing values() with it), but it doesn't seem to do anything. The end goal is for any user to be be able to get comments and see the names of all the commenters, rather than their ID.


ViewSet

class CommentViewSet(viewsets.ModelViewSet):
    permission_classes = (IsAuthenticated, )
    serializer_class = serializers.CommentSerializer
    queryset = models.Comment.objects.all()
    lookup_field='comment_id'

    # A. This works, but I don't want to define extra actions if I don't have to.
    @action(detail=False, url_path='use-user-name')
    def use_user_name(self, request, **kwargs):
        """
        Returns the user's name rather than its id.
        """
        return Response(self.queryset.values('comment_id', 'operator_id__name', 'dlc', 'comment'))

    # B. This doesn't work.
    def get_queryset(self):
        return self.queryset.values('comment_id', 'operator_id__name', 'dlc', 'comment')

    # C. Nor does this.
    def get_queryset(self):
        queryset = self.queryset.values('comment_id', 'operator_id__name', 'dlc', 'comment')
        return json.dumps(list(queryset), cls=DjangoJSONEncoder)

    # D. Nor this.
    def get_queryset(self):
        return serializers.serialize('json', list(self.queryset), fields=('comment_id', 'operator_id__name', 'dlc', 'comment'))

    # E. Nor this.
    def get_queryset(self):
        return list(self.queryset.values('comment_id', 'operator_id__name', 'dlc', 'comment'))

    # F. Nor this.
    def get_queryset(self):
        return json.loads(serializers.serialize('json', queryset=self.queryset))

Models and Serializers

class Comment(models.Model):
    comment_id = models.AutoField(primary_key=True, db_column='comment_id')
    operator_id = models.ForeignKey(settings.AUTH_USER_MODEL, models.DO_NOTHING, db_column='operator_id')
    dlc = models.DateTimeField()
    comment = models.CharField(max_length=100)

    class Meta:
        managed = False
        db_table = 'comment'


class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Comment
        fields = ('__all__')


# What is defined in "settings.AUTH_USER_MODEL".
class UserAccount(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(max_length=255, unique=True)
    name = models.CharField(max_length=255)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_admin = models.BooleanField(default=False)

    objects = UserAccountManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['name']

Errors Raised

Options B and E produces this error:

  ...
  File ".../lib/python3.8/site-packages/rest_framework/serializers.py", line 664, in <listcomp>
    self.child.to_representation(item) for item in iterable
  File ".../lib/python3.8/site-packages/rest_framework/serializers.py", line 515, in to_representation
    ret[field.field_name] = field.to_representation(attribute)
  File ".../lib/python3.8/site-packages/rest_framework/relations.py", line 273, in to_representation
    return value.pk
AttributeError: 'int' object has no attribute 'pk'

Options C and D produce this error:

Got AttributeError when attempting to get a value for field `dlc` on serializer `CommentSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `str` instance.
Original exception text was: 'str' object has no attribute 'dlc'.

Option F produces this error:

KeyError: "Got KeyError when attempting to get a value for field `dlc` on serializer `CommentSerializer`.\nThe serializer field might be named incorrectly and not match any attribute or key on the `dict` instance.\nOriginal exception text was: 'dlc'."

Output

The following are examples of what would be returned from get_queryset().

# Options A and B produce this:
<QuerySet [
    {'comment_id': 1, 'operator_id__name': 'John Smith', 'dlc': datetime.datetime(2022, 4, 3, 20, 48, 48), 'comment': 'First comment.'},
    {'comment_id': 2, 'operator_id__name': 'Jill Green', 'dlc': datetime.datetime(2022, 4, 3, 20, 48, 49), 'comment': 'Second comment.'}
]>

# C produces this:
[
    {"comment_id": 1, "operator_id__name": "John Smith", "dlc": "2022-04-03T20:48:48", "comment": "First comment."},
    {"comment_id": 2, "operator_id__name": "Jill Green", "dlc": "2022-04-03T20:48:49", "comment": "Second comment."}
]

# Options D and F produce this (note - missing "operator_id__name"):
[
    {"model": "test.comment", "pk": 1, "fields": {"dlc": "2022-04-03T20:48:48", "comment": "First comment."}},
    {"model": "test.comment", "pk": 2, "fields": {"dlc": "2022-04-03T20:48:49", "comment": "Second comment."}}
]

# E produces this:
[
    {'comment_id': 1, 'operator_id__name': 'John Smith', 'dlc': datetime.datetime(2022, 4, 3, 20, 48, 48), 'comment': 'First comment.'},
    {'comment_id': 2, 'operator_id__name': 'Jill Green', 'dlc': datetime.datetime(2022, 4, 3, 20, 48, 49), 'comment': 'Second comment.'}
]

Given the relationship between these two models, what is the best way to get the Comment fields with the operator_id__name swapped in for the operator_id?

What am I doing wrong?

Any help would be appreciated, thank you for taking the time to read the question.

CodePudding user response:

Serializers can access the related fields via the source argument.

name = serializers.ReadOnlyField(source='operator_id.name')

and then to avoid n 1 problem, in your queryset

return super().get_queryset().select_related("operator_id")

Other than this, there are several possible mistakes in your code.

  • Is there a reason why your models are not managed?
  • models.DO_NOTHING is extremely bad unless you have database triggers or constraints in place. In which case you probably should comment them into the code so the next person doesn't warn about it.
  • Django relation fields already append _id to end of the fields, so if this is model is hand-written and not generated you should just call a field relation = ForeignKey() and not relation_id = ForeignKey(). Disregard this if the db is not managed by django.
  • It's mostly best to avoid __all__ unless you know that there won't be any other field added to the model that may not want to be exposed to public.

CodePudding user response:

You could do this:

views.py

class CommentViewSet(viewsets.ModelViewSet):
    permission_classes = (IsAuthenticated, )
    serializer_class = serializers.CommentSerializer
    queryset = models.Comment.objects.all()

serializers.py

class UserAccountSerializer(serializers.ModelSerializer):
    """UserAccount model serializer"""
    class Meta(object):
        model = UserAccount
        fields = ("id", "email", "name",)
        read_only_fields = ("id", "email", "name",)


class CommentSerializer(serializers.ModelSerializer):
    """Comment model serializer"""
    operator_id = UserAccountSerializer(read_only=True)
    class Meta(object):
        model = Comment
        fields = ("comment_id", "operator_id", "dlc",)
        read_only_fields = ("comment_id",)

or

class CommentSerializer(serializers.ModelSerializer):
    """Comment model serializer"""

    class Meta(object):
        model = Comment
        fields = ("comment_id", "operator_id", "dlc",)
        read_only_fields = ("comment_id",)

    def to_representation(self, instance):

        return {
            "comment_id": instance.id,
            "operator_id__name": instance.operator_id.name,
            "dlc" : instance.dlc
        }
  • Related