How do I access a method from my Model in Django in React - json

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.

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.

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.

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.

Related

To return custom field in DjangoRestFramework after aggregation

There are lot of answers related to custom fields on stackoverflow but when trying with them, I am getting different error, so posting a separate question.
I wanted to return a JSON Response for the following url
urls.py
path('cards/<int:pk>/smarts', smarts.as_view(), name="smarts"),
I will be using below api.py file to return aggregate fields using Transaction model, the query is working fine, I only have to return appropriate response. Here I have one of the fields as Decimal hence tried with DjangoJSONEncoder but got an error.
api.py
class smarts(generics.ListAPIView):
serializer_class = TransactionSerializer
permission_classes = [permissions.IsAuthenticated, TransactionIsOwnerOrNot]
def get_queryset(self):
card = get_object_or_404(self.request.user.cards, pk=self.kwargs['pk'])
qs=card.transactions.values('vendor').annotate(a=Count('pk'),b=Sum('amount')).order_by('-b')
....CODE REQUIRED
return ....
models.py
class Transactions(models.Model):
amount = models.DecimalField(max_digits=19, decimal_places=2)
vendor = models.CharField(max_length=200)
category = models.CharField(max_length=200)
owner = models.ForeignKey(Cards, on_delete=models.CASCADE, related_name="transactions",null=True)
serializer.py
class TransactionSerializer(serializers.ModelSerializer):
class Meta:
model = Transactions
fields = '__all__'
I found the answer after hit and trial, I used custom serializer to return required field.
serializer.py
class SmartSerializer(serializers.Serializer):
vendor = serializers.CharField(max_length=200)
tot = serializers.IntegerField()
tot_amt = serializers.DecimalField(max_digits=19, decimal_places=2)
api.py
class smartstatements(generics.ListAPIView):
permission_classes = [permissions.IsAuthenticated, TransactionIsOwnerOrNot]
serializer_class = SmartSerializer
def get_queryset(self):
card = get_object_or_404(self.request.user.cards, pk=self.kwargs['pk'])
queryset=card.transactions.values('vendor')
.annotate(tot=Count('pk'),tot_amt=Sum('amount'))
.order_by('-tot_amt')
return queryset

Django Query values_list getting last value

Lets say I have a blog and a class user in a model. Furthermore I have a class comment connected with a foreign key.
class User(models.Model):
UserName = models.CharField(max_length=50, blank=True)
UserCountry = models.CharField(max_length=2, blank=True)
class Comment(models.Model):
commentText = models.TextField(max_length=1000)
commentSub = models.ForeignKey(User, related_name='comLink')
created_at = models.DateTimeField(auto_now_add=True)
Now I want to make an csv export in model admin and a I have a queryset with values_list.
I am wondering whether there exists a possibility to get each User once and e.g. only the last comment?
myList = queryset.values_list('UserName', 'UserCountry', 'comLink__commentText')
comLink is the related name. Now I just want the last comment. A timestamp is existing and I have not figured out how to filter or reverse etc.
You can do it with Subquery, I don`t know your model design, so it would be approximately like that:
from django.db.models import OuterRef, Subquery
com = Comment.objects.filter(commentSub=OuterRef('pk')).order_by('-created_at')
myList = queryset.annotate(LastComment=Subquery(com.values('commentText')[:1]))
myList = myList.values_list('UserName', 'UserCountry', 'LastComment')
https://docs.djangoproject.com/en/2.0/ref/models/expressions/#subquery-expressions

Get JSON data (Django)

This my model
Driver Model
class Driver(models.Model):
named = models.CharField(max_length=150)
ident = models.CharField(max_length=100, null=True, blank=True)
def __unicode__(self):
return self.named
manufacture model
class Carlcid(models.Model):
namec = models.CharField(max_length=100)
lc_id = models.CharField(max_length=100, null=True, blank=True)
def __unicode__(self):
return unicode(self.namec)
Car model
class Car(models.Model):
f_idcar = models.ForeignKey(Carlcid, related_name='carlcd')
id_driver = models.ForeignKey(Driver)
def __unicode__(self):
return unicode(self.f_idcar)
This My views
def get_name_driver(request):
name = request.GET.get('name')
query = Car.objects.filter(f_idcar__namec=name)
results=[]
for q in query:
m_json={}
m_json['nama']= q.named
results.append(m_json)
return HttpResponse(results,"application/json")
i get {'nama': <Driver: Michael>}
but i wont get result like this {'nama': "Michael"}, How to get result like that, thanks for help.
You must get the driver name with the following line :
m_json['nama']= q.id_driver.named
Your new view :
def get_name_driver(request):
name = request.GET.get('name')
query = Car.objects.filter(f_idcar__namec=name)
results=[]
for q in query:
m_json={}
m_json['nama']= q.id_driver.named
results.append(m_json)
return HttpResponse(results,"application/json")
In addition to my answer, I want give you an advice :
You should named your foreign key like the model. Yes, in database it will be store the driver id, but when you use Django ORM, you don't get the id of driver but a Driver object.
So I advise you to rename id_driver by driver so you will use
m_json['nama']= q.driver.named
but it's not required, just advise ;)

depth = 1 doesn't work properly and it's saves Null in ManyToManyField and ForeignKey fields in Django Rest Framework

after adding depth = 1 doesn't work properly
=> models.py file
class State(models.Model):
state_name = models.CharField(max_length = 30, unique=True)
def __unicode__(self):
return str(self.state_name)
class City(models.Model):
state = models.ForeignKey(State, related_name='state_city')
city_name = models.CharField(max_length = 30)
def __unicode__(self):
return str(self.city_name)
class Meta:
ordering = ('city_name',)
unique_together = ('state', 'city_name',)
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
owner = models.ForeignKey('auth.User', related_name='snippets')
state = models.ForeignKey(State,blank=True,null=True)
city = models.ManyToManyField(City)
=> serializers.py file
class StateSerializer(serializers.ModelSerializer):
class Meta:
model = State
class CitySerializer(serializers.ModelSerializer):
state_name = serializers.CharField(source='state.state_name', read_only=True)
class Meta:
model = City
class SnippetSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username', read_only=True)
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'owner', 'state', 'city')
depth = 1
I have added ForeignKey and ManyToManyField fields in state and city respectively. It doesn't save values in SnippetSerializer while added depth = 1 in Meta Class (it saves Null value in state and city fields). When I add depth = 1 JSON showing related fields as it should be but it doesn't work properly while add new Snippet. Without depth = 1 it works fine.
I have complex database where tables has many ManyToMany and ForeignKey related fields. Please give me suggestion so I can get related data in JSON.
I have djangorestframework-3.1.2 version. I have used latest version too but same problem. please give me solution and thanks in advance.
I faced the same problem and managed to solve it. Since the problem is with the depth, I just change the depth value in the init method.
class SnippetSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username', read_only=True)
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'owner', 'state', 'city')
depth = 1
def __init__(self, *args, **kwargs):
super(SnippetSerializer, self).__init__(*args, **kwargs)
request = self.context.get('request')
if request and request.method=='POST':
self.Meta.depth = 0
else:
self.Meta.depth = 1
In the code above, I changed the depth dynamically according to what type of request that I made.
But, this is the workaround that I found myself, I'm not sure if this is the best practice but it solve the problem with just little modification.
depth is only for representation (http://www.django-rest-framework.org/api-guide/serializers/#specifying-nested-serialization). If you want to create/update the related fields too you have to overwrite the create/update methods in the serializer (http://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers).
Greetings.

Django: order_by API json value or cached objects

I've got a model method that calculates the score of votes using the HN ranking algorithm in my model that also caches each score. calculate_score() is the main thing to focus on here.
class Submission(models.Model):
submission_type = models.CharField(_('Submission Type'),
max_length=255,
choices=tuple([(media.name.lower(), media.value) for media in MediaTypes]),
blank=False)
title = models.CharField(_('Title'), max_length=100, blank=False)
url = models.URLField(_('Link'), blank=False)
description = models.TextField(_('Description'))
flagged = models.BooleanField(_('Is Flagged for Review'), default=False)
user = models.ForeignKey(User, related_name='user_submissions')
thumbnail = models.ImageField()
date_submitted = models.DateTimeField(default=timezone.now)
by_votes = VoteCountManager()
objects = models.Manager()
def __str__(self):
return self.submission_type + ': ' + self.title
def get_absolute_url(self):
return reverse('submit_detail', args=[
str(self.submission_type),
str(self.id),
])
def get_vote(self):
"""
Returns the number of votes associated with a particular submission
:return: int
"""
return self.submission_votes.count()
def calculate_score(self):
"""
This is a variation of the HN ranking algorithm
:return: score
"""
secs_in_hour = float(60 * 60)
g = 1.2
delta = timezone.now() - self.date_submitted
item_hour_age = delta.total_seconds() / secs_in_hour
votes = self.submission_votes.count() - 1
score = votes / pow((item_hour_age + 2), g)
cached = cache.get('s{0}'.format(self.pk))
if not cached:
cache.set('s{0}'.format(self.pk), score, 30)
return score
I'm using the djangorestframework that serializes my model:
class SubmissionSerializer(serializers.HyperlinkedModelSerializer):
score = serializers.SerializerMethodField('rank')
class Meta:
model = Submission
def rank(self, obj):
return obj.calculate_score()
I have two ways that I think I can solve my problem, but I don't know how to do either of them, and I'm not sure which one is best. Since I'm caching the score for each individual submission. I was trying to order the submissions in a ListView like this pulling from the cache:
class SubmissionList(ListView):
model = Submission
def get_queryset(self):
return super(SubmissionHotList, self).get_queryset().annotate(
score=cache.get('s{0}'.format(x) for x in Submission.pk)
votes=models.Count('submission_votes'),
).order_by(
'-score', '-votes',
)
But I found that order_by only works on the database level, and I would have to create a database field for the calculated score, which I would like to avoid if possible. My other possibility is using the serialized data in my API for Submission in this ListView, but I'm not sure if APIs are only there for external applications, or if I could use them in the same application that the API is generated.
I guess my question is. Would it be better to get the cached objects to list each submission in a particular order, or could I use the API to accomplish this? And if I used the API, how could I parse and order the JSON data in the ListView, while keeping votes as a secondary ordering mechanism?
if you want to display all items without pagination, you will not lose any performance if you do something like
import operator
class SubmissionList(TemplateView):
template_name = "submissions.html"
def get_context_data(self, **kwargs):
context = super(SubmissionList, self).get_context_data(**kwargs)
submissions = Submission.objects.all().annotate(
votes=models.Count('submission_votes')
)
sorted_submissions = sorted(submissions, key=lambda x:(cache.get('s{0}'.format(x.pk)), x.votes), reverse=True)
context['submissions'] = sorted_submissions
return context