Django Serializer Method Field - python

Can't seem to find the right google search for this so here it goes:
I have a field in my serializer:
likescount = serializers.IntegerField(source='post.count', read_only=True)
which counts all the related field "post".
Now I want to use that field as part of my method:
def popularity(self, obj):
like = self.likescount
time = datetime.datetime.now()
return like/time
Is this possible?

assuming post.count is being used to measure the number of likes on a post and you don't actually intend to divide an integer by a timestamp in your popularity method, then try this:
use a SerializerMethodField
likescount = serializers.SerializerMethodField('get_popularity')
def popularity(self, obj):
likes = obj.post.count
time = #hours since created
return likes / time if time > 0 else likes
however I would recommend making this a property in your model
in your model:
#property
def popularity(self):
likes = self.post.count
time = #hours since created
return likes / time if time > 0 else likes
then use a generic Field to reference it in your serializer:
class ListingSerializer(serializers.ModelSerializer):
...
popularity = serializers.Field(source='popularity')

Related

Django CBV use Values from Modelform to Calculate other Model Fields

I am trying to make a warehouse management system with Django 3.2 based on this models:
class itemtype(models.Model):
item_id = models.IntegerField(primary_key=True)
item_name = models.CharField(max_length=100)
group_name = models.CharField(max_length=100)
category_name = models.CharField(max_length=100)
mass = models.FloatField()
volume = models.FloatField()
used_in_storage = models.BooleanField(default=False, null=True)
class Meta:
indexes = [
models.Index(fields=['item_id'])
]
def __str__(self):
return '{}, {}'.format(self.item_id, self.item_name)
class material_storage(models.Model):
storage_id = models.AutoField(primary_key=True)
material = models.ForeignKey(itemtype, on_delete=models.PROTECT)
amount_total = models.IntegerField(null=True)
price_avg = models.FloatField(null=True)
order_amount = models.IntegerField(null=True)
order_price = models.IntegerField(null=True)
timestamp = models.DateTimeField(default=timezone.now)
def __str__(self):
return '{}, {} avg.: {} ISK'.format(self.material, self.amount, self.price)
"itemtype" defines basically the possible objects which could be stored and "material_storage" shows what is in stock. I tried to combine the total amount of every item as well as the average price paid for it and the amount and price for a single order in the same database row. The idea is to get the last record for the chosen item/material when a new order happens, add the amount of that order and recalculate the avg price.
Theoretically this could be split up on two tables, but I don't see a reason to do so at the moment.
However, I am not able to figure out the actual function code to do the calculations. I am new to Django and therefor a bit overwhelmed by the complexity. I tried to use class based views and model forms, for the easy stuff that worked fine but now I am kind of lost.
Making a form just for adding new rows to that storage table was ok.
class NewAssetForm(forms.ModelForm):
material = MaterialChoiceField(models.itemtype.objects.filter(used_in_storage= True))
def __init__(self, *args, **kwargs):
super(NewAssetForm, self).__init__(*args, **kwargs)
self.fields['amount'].widget.attrs['min'] = 1
self.fields['price'].widget.attrs['min'] = 1
class Meta:
model = models.material_storage
fields = (
'material',
'amount',
'price'
)
widgets = {
'material': forms.Select(),
}
Same for the View to process it.
class NewItemView(FormView):
template_name = 'assetmanager/newasset.html'
form_class = forms.NewAssetForm
success_url = '/storage/current'
def form_valid(self, form):
return super().form_valid(form)
But now I am stuck. I thought this should be a fairly standard task, but I couldn't find a solution for it by now. The Idea was to put it in the form_valid function, take the material from the form to find the latest relevant record, add the new amount as well as calculate the new average price and save all together to the model. So far i only found a few examples comparable with my problem at all and I wasn't able to translate them to my setup, so maybe someone can give me a hint for a more successful search or provide me an example how to approach this topic.
thx in advance.
To modify the values of the form fields, you can override "clean" method and provide values to the form fields. Data can be accessed using "self.cleaned_data", it is a dictionary.
class NewAssetForm(ModelForm):
def clean(self):
super().clean()
# place code that retrieves existing data and calculate new values.
self.cleaned_data['price'] = 'New Value'
cleaned_data will be passed to "form_valid", there you can call the save function. "form.save()" will create a new row, make sure you are passing valid values to the views. Since you are accepting few fields in the form, make sure you have default values for the fields that are not included in the form object.
Thank you for your answer I found a solution by using the form_valid() method within the FormView. The majority of the code is used to create entries based on the existing entries or to check whether there are already entries for the same material.
class NewItemView(FormView):
template_name = 'assetmanager/newasset.html'
form_class = forms.NewAssetForm
success_url = '/storage/current'
def form_valid(self, form):
try:
# check if there is already a record for this material.
material_storage.objects.filter(material_id = form.cleaned_data['material'])[:1]
# getting total amount and average price values from latest entry with said material.
total_amount = material_storage.objects.values('amount_total').filter(material_id=form.cleaned_data['material']).order_by('-timestamp')[:1][0]['amount_total']
avg_price = material_storage.objects.values('price_avg').filter(material_id=form.cleaned_data['material']).order_by('-timestamp')[:1][0]['price_avg']
amount = form.cleaned_data['amount']
price = form.cleaned_data['price']
# calculating new total amount and average price based on old values and new entry.
form.instance.amount_total = total_amount + amount
form.instance.price_avg = ((avg_price * total_amount) + (price * amount)) / (total_amount + amount)
form.save()
except material_storage.DoesNotExist:
# if there is no entry for the chosen material yet, amount = total amount, price = average price.
form.instance.amount_total = form.cleaned_data['amount']
form.instance.price_avg = form.cleaned_data['price']
form.save()
return super().form_valid(form)
For now this solves my problem, however I don't know if the chosen location (form_valid()) makes sense - your answer suggests it would make more sense elsewhere.
Also, checking if an entry already exists for the material and selecting values from such an entry are pretty sure not very elegant and efficient. But as already mentioned, I am a beginner - I would be happy about any suggestions for improvement.
I am also not sure yet if this handles every probable special case which could appear...

django enumerate a model's integer field when another model gets created

from django.db import models
class Game(models.Model):
description = models.TextField(max_length=8192)
class GamePreview(models.Model):
game = models.OneToOneField(Game, on_delete=models.CASCADE)
comments = models.IntegerField(default=0) # Want to + 1 this when a comment gets created
class GameComment(models.Model):
game = models.ForeignKey(Game, on_delete=models.CASCADE)
comment = models.CharField(max_length=512)
#classmethod # does not work
def create(cls, game):
comment = cls(game=game)
preview = GamePreview.objects.get(game=comment.game)
preview.comments += 1
return preview
Basically, I have a GamePreview model that has a IntgerField that should show the amount of comments, but I cannot figure out how I can do preview.comments += 1 when a GameComment gets created...
Please don't, you can annotate the GamePreview object to determin the number of comments.
You thus can remove the comments field:
class GamePreview(models.Model):
game = models.OneToOneField(Game, on_delete=models.CASCADE)
# no comments
and then in case you need the number of related GameComments, you can work with .annotate(…) [Django-doc]:
from django.db.models import Count
GamePreview.objects.annotate(
comments=Count('game__gamecomment')
)
GamePreview objects that arise from this queryset will have an extra attribute .comments that contains the number of related GameComments.
If you really want to increment the number of comments, you can work with:
class GameComment(models.Model):
game = models.ForeignKey(Game, on_delete=models.CASCADE)
comment = models.CharField(max_length=512)
#classmethod # does not work
def create(cls, game, comment):
comment = cls(game=game, comment=comment)
preview = GamePreview.objects.get(game_id=comment.game_id)
preview.comments = F('comments') + 1
preview.save()
return comment
But this is usually not a good idea: if a comment is removed, or no longer belongs to that game but to another, you will need to write some logic to change this. Often it is quite hard to cover all possible cases.

DRF: How to find the model instance with the highest combined value of two fields?

I have a model which looks like this:
class Posts(models.Model):
title = models.CharField(max_length=100)
creation_timestamp = models.DateTimeField(auto_now_add=True)
body = models.CharField(max_length=255)
user_id = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
likes = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="likes",blank=True)
dislikes = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="dislikes",blank=True)
I have added an endpoint in urls.py, so that when the url .../v1/posts/max-interaction-count is visited, the following function in views.py is run:
#api_view(['GET'])
def interaction_count_view(request):
max_post = None
max_interactions = 0
for post in Posts.objects.all():
interactions = post.likes.count() + post.dislikes.count()
if interactions > max_interactions:
max_post = post
max_interactions = interactions
return Response({"Post number " : max_post.pk, "interaction_count ": max_post.likes.count() + max_post.dislikes.count()})
This works, returning the id of the post with the maximum sum of likes and dislikes. However, I believe interaction_count_view might not be very scalable because it was written in python, rather than using built-in django methods which use SQL. Is this the case? If so, what is a better approach to counting the number of likes and dislikes associated with a post instance?
Annotate the expression calculating the number of interactions of a post on the queryset and use latest to get the post which has maximum number of interactions:
from django.db.models import Count
#api_view(['GET'])
def interaction_count_view(request):
max_post = Posts.objects.annotate(interactions=Count('likes') + Count('dislikes')).latest('interactions')
if max_post:
return Response({"Post number" : max_post.pk, "interaction_count": max_post.interactions})
return Response({"Post number": None, "interaction_count": 0, "message": "No posts exist!"})
Note: I changed your keys in the dictionary that you pass to response they had extra spaces ("Post number " changed to => "Post number", etc.)
Note: Model names should ideally be singular so Post instead of Posts

How to filter a form based on a DateTimeField using timedelta

I have the following model for Sessions:
courses/models.py:
class Session(models.Model):
course = models.ForeignKey(Course, on_delete=models.CASCADE)
course_date_start = models.DateTimeField()
course_date_end = models.DateTimeField()
is_in_session = True
def session_id(self):
new_session_date = self.course_date_start.strftime('%Y')
return f'{new_session_date}{self.course.number}{self.pk}'
def __str__(self):
return f'{self.course.number} - {self.course.title} - {self.session_id()}'
And in my users/forms.py:
class EnrollStudentInCourseForm(forms.Form):
global roster_limit
student = forms.ModelChoiceField(queryset=Student.objects.all())
courses=SessionRemainingSlotsForm(queryset=Session.objects.annotate(num_students=Count('student')).filter(num_students__lt=roster_limit))
I'd like to filter out the fields so that it only shows sessions where course_date_start plus a week, to cover a student joining the class a little late.
I think I was messing up my math because I solved it:
What I did was use the timedelta function in my form filter to to only show sessions that are still within the cutoff date:
courses = SessionRemainingSlotsForm(queryset=Session.objects.annotate
(num_students=Count('student')).filter(course_date_start__gte=date.today() - timedelta(days=7),
num_students__lt=roster_limit))

Django - Query parameter takes model type? RelatedObjectDoesNotExist: Posting has no textbook

I have installed this Django app: http://django-auditlog.readthedocs.io/en/latest/_modules/auditlog/models.html#LogEntry
The log entries are setup with two different models which are below:
class Posting(models.Model):
textbook = models.ForeignKey(Textbook)
condition = models.CharField(max_length=200)
price = models.DecimalField(max_digits=5, decimal_places=2)
user = models.ForeignKey(User)
image = models.ImageField(upload_to='postingpics/%Y/%m/%d', default="/textchange/nophoto.png")
post_date = models.DateTimeField('date_posted')
comments = models.CharField(max_length=50, default="")
def __str__(self):
return str(self.textbook)
def was_posted_recently(self):
return self.post_date >= timezone.now() - datetime.timedelta(days=1)
was_posted_recently.admin_order_field = 'post_date'
was_posted_recently.boolean = True
was_posted_recently.short_description = 'Posted recently'
class Wishlist(models.Model):
textbook = models.ForeignKey(Textbook)
user = models.ForeignKey(User)
wish_date = models.DateTimeField('date_wish')
def __str__(self):
return str(self.textbook)
def was_wished_recently(self):
return self.wish_date >= timezone.now() - datetime.timedelta(days=1)
was_wished_recently.admin_order_field = 'date_wish'
was_wished_recently.boolean = True
was_wished_recently.short_description = 'Wished recently'
auditlog.register(Posting)
auditlog.register(Wishlist)
So in the database the model EntryLog is getting a row each time something happens to Posting or Wishlist whether that be delete, create, or update.
I am trying to complete the query below because I want to count the number of occurrences that a Posting is deleted. Separately I will do the same query for Wishlist.
LogEntry.objects.filter(action=2 , content_type=Posting).count()
content_type is of type Foreign key for the record.
When I run this query I get the error message:
RelatedObjectDoesNotExist: Posting has no textbook.
This makes me think that Posting is the wrong value to put in for content_type. Am I thinking about this correctly? Should the value for content_type be something different? I understand usually when you refer to a Foreign Key you are querying on the Foreign keys fields like Posting__condition or something like that.
Thanks in advance.
The content_type argument should be a ContentType object, not a model class.
Instead of doing doing this manually you should use the LogEntryManagers get_for_model method:
LogEntry.objects.get_for_model(Posting).filter(action=2).count()

Categories