-1

In the following model...

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    likes = models.IntegerField(default=0)
    dislikes = models.IntegerField(default=0)
    pub_at = models.DateTimeField(auto_now_add=True)
    category = models.ForeignKey(
        Category, on_delete=models.SET_NULL, null=True)

    def __str__(self):
        return f"{self.question_text}"

    def validity(self):
        total_likes = self.likes + self.dislikes

        if total_likes != 0:
            return (self.likes / total_likes) * 100
        else:
            return 100

I want to be able to access Question.objects.get(pk=1).validity() assuming that pk=1 exists in this case. In python shell I can do this easily. But how do I do this using React. I am able to get all my questions and the fields in React without a problem but I don't think I have a way to access the validity method I created.

Michael Torres
  • 512
  • 3
  • 21
  • you can't use the function in the client-side, but you can in a Django template, do you mean by React a Django template? – Ahmed Nour Eldeen Sep 10 '20 at 23:14
  • The way I’m building my React project is my client is a django app. what I mean by that is I did python manage.py startapp client and configured my app so I can use React. So I just want to be able to get the validity() to render it on the client side – Michael Torres Sep 10 '20 at 23:18

3 Answers3

2

In this case I would suggest the following. First, remove the property from the model:

# models.py

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    likes = models.IntegerField(default=0)
    dislikes = models.IntegerField(default=0)
    pub_at = models.DateTimeField(auto_now_add=True)
    category = models.ForeignKey(
        Category, on_delete=models.SET_NULL, null=True)

    def __str__(self):
        return f"{self.question_text}"

Then add a SerializerMethodField (docs) to your serializer. It is read-only and can be used to pass computed values to your views:

# serializers.py

class QuestionSerializer(serializers.ModelSerializer):
    validity = serializers.SerializerMethodField()

    class Meta:
        model = Question
        fields = ['question_text', 'likes', 'dislikes', 'pub_at', 'category', 'validity']

    def get_validity(self, instance):
        total_likes = instance.likes + instance.dislikes
        # Your approach is not wrong. This is a more explicit way of dealing with that particular error type
        try:
            return (instance.likes / total_likes) * 100
        except ZeroDivisionError:
            return 100

Bear in mind that the Foreign Key category will be serialized as its database unique id value (Primary Key) in this case.

Madjazz
  • 450
  • 7
  • 12
  • This does work, I would like to ask what the difference is. For instance, is it not good practice to add methods to the model itself and serialize our model and it's methods (not sure if i'm saying this correctly). In your case I get rid of the method `validity` completely and implement it in the serializer itself, which I thought in general the Question model itself should handle `validity` at it's level – Michael Torres Sep 12 '20 at 10:01
  • 1
    It depends on the approach you choose on how to pass data from the backend to your client. If you serve the client directly on the django app, it is totally ok to have it as a property on your model because it has to take care of both data validation and representation itself. If you decide to serve it as JSON with the rest framework, the responsibility of validating and representing data shifts from the model to the serializer. Since validity is derived from values stored on the model and is not a direct property, it can be delegated to the serializer to represent it. – Madjazz Sep 12 '20 at 18:42
1

You might want to use the @property decorator so that you can access the value the same way you would access any of the other fields on your Question model:

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    likes = models.IntegerField(default=0)
    dislikes = models.IntegerField(default=0)
    pub_at = models.DateTimeField(auto_now_add=True)
    category = models.ForeignKey(
        Category, on_delete=models.SET_NULL, null=True)

    def __str__(self):
        return f"{self.question_text}"

    @property
    def validity(self):
        total_likes = self.likes + self.dislikes
        percentage = (self.likes / total_likes) * 100
        return percentage

Explanations can be found in the docs or here. Keep in mind that it will not be saved like the other attributes as columns on the database when you run migrations.

Madjazz
  • 450
  • 7
  • 12
0

I am answering my own question here I found a solution to. Although, @property does work when rendering using a simple Django template when using React and rendering json responses validity is still not available.

In my serializers.py file I did the following...

class QuestionSerializer(serializers.ModelSerializer):
    validity = serializers.ReadOnlyField()

    class Meta:
        model = Question
        fields = '__all__'

Take away the @property from the models as it is no longer needed. This has worked for me and you can go to the Django rest_framework or test it in your React application to see if you have access to this.

I would like to know if there are any issues doing this and/or a better way. I was also trying to do validity = serializers.Field() instead of validity = serializers.ReadOnlyField() but got an error saying I needed a Field.to_representation() that takes in self, value as positional arguments.

What arguments exactly do I pass in here. I tried self, Question.validity and did not work. I am not sure what I am doing here.

As an update the method in the model I updated to...

def validity(self):
        total_likes = self.likes + self.dislikes

        if total_likes != 0:
            return (self.likes / total_likes) * 100
        else:
            return 100

I did not notice before and does not really matter for the question but division by zero is not allowed being that by default division by zero will always occur.

Michael Torres
  • 512
  • 3
  • 21