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

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.

Related

Django: get only objects with max foreignkey count

The question is quite simple but possibly unsolvable with Django.
For example I have a model
class MyModel(models.Model)
field_a = models.IntegerField()
field_b = models.CharField()
field_c = models.ForegnKey(MyOtherModel)
The question is how to select only objects that have a maximal count of relations with MyOtherModel and preferably(almost mandatory) with only a single query set?
Lets say, we have 100 entries all together, 50 pcs. point to field_c_id=1, 40 pcs. to field_c_id=2 and rest 10 pcs. entries to field_c_id = 3.
I need only those which point to field_c_id=1? as 50 would be maximal count.
Thanks...
Ok first you need a related_name on your MyModel model
in models.py
class MyOtherModel(models.Model)
field_a = models.IntegerField()
class MyModel(models.Model)
field_a = models.IntegerField()
field_b = models.CharField()
field_c = models.ForeignKey(MyOtherModel, on_delete=models.CASCADE, related_name="my_other_model")
Than you can get most used MyOtherModel
in views.py
most_used = MyOtherModel.objects.annotate(my_other_model_count=Count('my_other_model')).order_by('-my_other_model_count')[:1]
put [:1] if you need 1 if you need more you can set any quantity or remove it
Or
And here is another solution which is better than this
First we have to add one field to your MyOtherModel which keeps count of MyModel
class MyOtherModel(models.Model)
field_a = models.IntegerField()
my_model_count = models.IntegerField()
And we have to update count when you add, update or delete object to MyModel for that I recommend to use django signals
in your models.py
from django.dispatch import receiver
from django.db.models.signals import pre_save, pre_delete
#at the bottom of your model
#receiver(pre_save, sender=MyModel)
def products_reputation_count(sender, instance, *args, **kwargs):
if instance.pk:
old_instance = MyOtherModel.objects.get(id=instance.pk)
old_instance.reputation.product_count -= 1
instance.reputation.product_count += 1
else:
instance.reputation.product_count += 1
#receiver(pre_save, sender= MyModel)
def products_reputation_count(sender, instance, *args, **kwargs):
instance.reputation.product_count += 1
and in your views.py
most_used = MyOtherModel.objects.order_by("-my_model_count")[:1]
I think this would be helpful. If you have any question related this answer feel free to ask more. Have a good day.

Django Serialization - Partial update on nested objects

Searched around for a few hours on this and I am surprised I couldn't find an answer but here it goes.
Let's say I have the following models:
class Mission(models.Model):
mission_name = models.CharField(max_length=150)
...
class Player(models.Model):
player_name = models.CharField(max_length=150, unique = True)
state = models.CharField(max_length=150)
currentMission = models.ForeignKey(Mission,on_delete=models.SET_NULL, blank=True, null=True))
Objectives:
When creating a mission, I would like to provide the players' names that are going to participate on this mission (Names are unique). That means, when mission is created, I have to update the currentMission field of each given player. (Players already exist when mission is created)
When I try to GET a mission, I would like to see the names of the players that participate
My attempt
Class MissionSerializer(serializers.ModelSerializer):
#This is to get a list of the players that participate in this mission
players = PlayerSerializer(many=True, read_only=True)
class Meta:
model= Mission
fields = ['mission_name','players']
def create(self,validated_data):
mission = Mission.objects.create(**validated_data)
# Notice that I get the "players" data from "self.initial_data" and not from validated_data
# This is because validated_data does not have the "players" field,
# (I guess because I didn't provide all the required fields on my request for the player. I just provided the players' names )
players_data = self.initial_data['players']
#Update each player's current mission
for player_data in players_data:
qs = Player.objects.filter(player_name=player_data['player_name'])
obj = get_object_or_404(qs)
serializer = PlayerSerializer(obj, data={'currentMission': mission.id}, partial=True)
if (serializer.is_valid()):
serializer.save()
class PlayerSerializer(serializers.ModelSerializer):
class Meta:
model = Player
fields = ('__all__')
def update(self, instance, validated_data):
instance.currentMission = validated_data['currentMission']
instance.save()
return instance
The above works for objective #1. However, it does not work for objective #2. That is, when I GET a mission, the list of players is not present on the result.
My question is, how could I also retrieve this list when performing GET requests?
I think that players field lacks a source. Adding a source to the field should solve your problem.
players = PlayerSerializer(many=True, read_only=True, source='player_set')
Also, I'd recommend to prefetch that players to optimize the database query.
In your view;
mission_queryset = Mission.objects.filter(...).prefetch_related('player_set')

How to return objects by popularity that was calculated with django-hitcount

I have itens in my app that I want to be returned by "popularity". This popularity meaning the number of views the item has.
I'm using django-hitcount to do this. I saw here how I could get the number of hits of each object. But I don't want to load all my Item objects to memory to accomplish what I want because it's an unnecessary overload.
I want to return the N most popular itens to be passed to the view and the access number of each item.
My Item model is as bellow
class Item(models.Model, HitCountMixin):
nome = models.CharField(max_length=255, unique=True)
slug = models.SlugField(max_length=255, null=True)
imagem = models.ImageField(upload_to='itens/item/', null=True, blank=True)
descricao = RichTextUploadingField(null=True, blank=True)
categoria = models.ForeignKey(Categoria)
hit_count_generic = GenericRelation(
HitCount, object_id_field='object_pk',
related_query_name='hit_count_generic_relation')
def __str__(self):
return '{}'.format(self.nome)
def get_absolute_url(self):
from django.urls import reverse
return reverse('itens:detail_item', args=[str(self.slug)])
At first, in my View I was trying to get the most popular itens with this function
def get_most_popular_itens(amount):
return Item.objects.order_by('-hit_count.hits')[:amount]
But it didn't work. I couldn't understand how this contenttype/generic relationship works.
So, I saw how the database tables were and managed to do something functional (see bellow).
But it has one problem. The queryset returned isn't ordered by the number of views and I don't have access to this number.
Even more, it seems to me that my solution is at least bad.
So, I wanted any idea on how I could improve that, maybe taking some advantage from the Generic Relationship?
def get_most_popular_itens(amount):
ct = ContentType.objects.get_for_model(Item)
hit_counts = HitCount.objects.filter(content_type_id=ct.id).order_by('-hits')[:amount]
items = []
for hit in hit_counts:
items.append(hit.object_pk)
return Item.objects.filter(id__in=items)
This should work:
Item.objects.all().order_by('-hit_count_generic__hits')

Django: Linking two tables

first of all, I'm aware that this question might've been answered already, but there are two reasons why I'm opening another question: One, obviously, is I'm struggling with the Django syntax. Secondly, and perhaps more importantly, I'm not quite sure whether my database setup makes even sense at this point. So, please bear with me.
I work in a hospital and one of my daily stuggles is that, oftentimes, one single drug can have a lot of different names. So, I thought that'd be a good task to practice some Django with.
Basically I want two databases: One that simply links the drugs "nick name" to it's actual name. And another one which links the actual name to some additional information, something along the lines of a wiki page.
What I've come up with so far:
(django)django#ip:~/medwiki$ cat medsearch/models.py
from django.db import models
# Create your models here.
class medsearch(models.Model):
proprietary_name = models.CharField(max_length = 100, unique = True)
non_proprietary_name = models.CharField(max_length = 100, unique = True)
def __str__(self):
return self.non_proprietary_name
class medwiki(models.Model):
proprietary_name = models.ForeignKey('medisearch', on_delete=models.CASCADE)
cetegory = models.CharField(max_length = 255)
#wiki = models.TextField() etc.
def __str__(self):
return self.proprietary_name
(django)django#ip-:~/medwiki$
So, I can add a new "medsearch object" just fine. However, when adding the "Category" at medwiki I get __str__ returned non-string (type medsearch). Presumably, because there's more than one key in medsearch? I thus suspect that "FroeignKey" is not suited for this application and I know that there are other ways to link databases in Django. However, I don't know which one to choose and how to implement it correctly.
Hopefully, some of you have some ideas?
EDIT: Here's what I've come up with so far:
class Proprietary_name(models.Model):
proprietary_name = models.CharField(max_length = 100, unique = True)
def __str__(self):
return self.proprietary_name
class Category(models.Model):
category = models.CharField(max_length = 100, unique = True)
def __str__(self):
return self.category
class Mediwiki(models.Model):
proprietary_name = models.ManyToManyField(Proprietary_name)
non_proprietary_name = models.CharField(max_length = 100, unique = True)
category = models.ManyToManyField(Category)
wiki_page = models.TextField()
def __str__(self):
return self.non_proprietary_name
Now I can attribute different categorys and different proprietary_names to one drug. Which works great so far.
So does looking up the non-proprietary_name when I know the proprietary "nick name".
>>> Mediwiki.objects.get(proprietary_name__proprietary_name="Aspirin")
<Mediwiki: acetylsalicylic acid>
>>>
However, I'd also like to display all the proprietary_names, when I know the non_proprietary_name. Do I have to further change the database design, or am I just missing some other thing here?
This would work:
return self.proprietary_name.proprietary_name
But that doesn't really make sense !
The main issue is that you've called the foreign key to medsearch, proprietary_name.
The second issue is just a convention one. In Python (and many programming languages), classes must start with an uppercase letter.
The following would be better:
class MedSearch(models.Model):
proprietary_name = models.CharField(max_length=100, unique=True)
non_proprietary_name = models.CharField(max_length=100, unique=True)
def __str__(self):
return self.non_proprietary_name
class MedWiki(models.Model):
med_search = models.ForeignKey('MedSearch', on_delete=models.CASCADE, related_name='wikis')
cetegory = models.CharField(max_length = 255)
#wiki = models.TextField() etc.
def __str__(self):
return self.med_serach.proprietary_name
As you note, the proprietary_name field on medwiki is a ForeignKey. You can't return that value directly from the __str__ method because that needs to return a string. You need to convert that value into a string before returning it: either use the default string representation of the medsearch instance:
return str(self.proprietary_name)
or choose a specific string field to return:
return self.proprietary_name.proprietary_name

How do I over-ride the save() method for my Django model so that it updates properly?

I am building a ratemyprofessors type of application for my school and also to get some practice.
Currently my models.py looks like this:
from __future__ import unicode_literals
from django.db import models
from django.core.validators import MaxValueValidator, MinValueValidator
from django.contrib.auth.models import User
from django.utils import timezone
UNIVERSITIES = (
.....
)
DEPARTMENTS = (
.....
)
class Professor(models.Model):
name = models.CharField(max_length=255)
name_code = models.CharField(max_length=3, blank=True)
university = models.CharField(max_length=3, choices=UNIVERSITIES)
department = models.CharField(max_length=50, choices=DEPARTMENTS)
total_rating_points = models.IntegerField(default = 0)
number_of_reviews = models.IntegerField(default = 0)
rating = models.FloatField(
validators = [MinValueValidator(0.0), MaxValueValidator(5.0)],
default = 0.0
)
def __str__(self):
return self.name
SCORE_CHOICES = (
.....
)
class Review(models.Model):
author = models.ForeignKey(User, related_name='user_reviews')
professor = models.ForeignKey(Professor, related_name='professor_reviews')
created = models.DateTimeField(default=timezone.now)
updated = models.DateTimeField(default = timezone.now)
rating = models.IntegerField(default=1, choices=SCORE_CHOICES)
text = models.TextField(blank=True)
class Meta:
unique_together = [('author', 'professor')]
def __str__(self):
return 'Professor: ' +self.professor.name +', Score: ' +str(self.rating)
def save(self, *args, **kwargs):
"""
Re-writing the save method to update the associated professor's
rating as soon as a new Review object is created.
Also accounts for review updates by the user.
"""
if self.pk is None:
# This means that this is a new object
if self.professor:
p = self.professor
# Adjusting the total_rating_points and number of reviews
p.total_rating_points += self.rating
p.number_of_reviews += 1
# Adjusting the rating
p.rating = float(p.total_rating_points) / float(p.number_of_reviews)
p.save()
else:
# This object already exists, so this is an update
self.updated = timezone.now()
**WHAT DO I DO NOW?**
super(Review, self).save(*args, **kwargs)
You see, if a user updates his/her rating, the rating of the professor has to be adjusted accordingly. Since this is the core of the application, I wanted to do it in the save() method. It works nicely if it is a very new review. However, how do I update the score?
I mean I know what I have to do:
Subtract the previous score from the total_rating_point of the professor.
Add the new rating to the total_rating_point
Calculate rating by dividing it with number_of_review.
However, how exactly do I retrieve the previous score in the save() method during an update? Also is there a better and more efficient to do what I am trying to do? Thanks!
There's a couple concerns to keep in mind: What if a user deletes their account, a review, etc? Keeping a running total in the way shown would be problematic.
Instead, I'd suggest a structure such as I've shown below; when a Review is updated, save it and then call the save method for Professor. The new save method for Professor runs calculates the sum and count of reviews in place, recalculating each time as it still is only reaching out to the DB for a couple queries.
from django.db.models import Sum
class Professor(models.Model):
...
def save(self,*args,**kwargs):
if self.pk: # prevent hitting the database unless professor already exists
professor_reviews = Review.objects.filter(professor=self)
# Adjusting the total_rating_points and number of reviews
self.total_rating_points = professor_reviews.aggregate(Sum('rating'))
self.number_of_reviews = professor_reviews.count()
# Adjusting the rating
self.rating = float(self.total_rating_points) / float(self.number_of_reviews)
super(Professor,self).save(*args,**kwargs)
class Review(models.Model):
....
def save(self, *args, **kwargs):
if self.pk: #already exists
self.updated = timezone.now()
super(Review, self).save(*args, **kwargs)
self.professor.save() # call after super writes the Review to the DB
Use a post_save signal.
In the models file with the Professor:
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save,sender=Review)
def update_professor_by_review(*args,**kwargs):
updated_review = kwargs['instance']
reviewed_professor = updated_review.professor
# ... update the reviewed_professor as needed according to the review instance
reviewed_professor.save()
return

Categories