Django: get only objects with max foreignkey count - python

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.

Related

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.

Django Models design: many-to-many relationship with specific needs

I am in the process of designing a couple of new models for my django app and this is what I need:
class Bookmaker(models.Model):
name = models.CharField(max_length=50)
accepted_countries = ?
restricted_countries = ?
class Country(models.Model):
name = models.CharField(max_length=50)
bookmakers = ?
So I need a model Bookmaker and a model Country and they need to be related, BUT every bookmaker should have a list of countries that are accepted and a list of countries that are excluded.
The question marks are both in the Country and in the Bookmaker models as I'm not sure which way the relation should go.
Not sure if I'm on the right path, but I'm thinking that I need a couple of many-to-many relationships.. And, which is the right way (if any) to use the same Country model (those will be different instances) in both accepted_countries and restricted_countries?
Thanks.
You should use two many to many relations with related_name to separate them:
class Bookmaker(models.Model):
name = models.CharField(max_length=50)
accepted_countries = models.ManyToManyField(Country, related_name="accepted_for")
restricted_countries = models.ManyToManyField(Country, related_name="restricted_for")
You can then use the reverse relation as:
bookmakers_for_which_county_is_accepted = Country.objects.values('accepted_for').distinct()
bookmakers_for_which_county_is_restricted = Country.objects.values('restricted_for').distinct()
docs
You need indeed some many to many:
class Bookmaker(models.Model):
name = models.CharField(max_length=50)
accepted_countries = models.ManyToMany('Country',related_name='accepted')
restricted_countries = models.ManyToMany('Country', related_name= 'restricted')
class Country(models.Model):
name = models.CharField(max_length=50)
bookmakers = models.ManyToMany(Bookmaker)
Then if you create a form to edit a bookmaker you will be abble to add the name, the accepted and restricted countries:
forms.py
class BookmakerForm(models.ModelForm):
class Meta:
model = Bookmaker
fields = ['name', 'accepted_countries', 'restricted_countries']
#for having a better default display you can change the widget:
self __init__(self, *args, **kwargs):
super(BookmakerForm, self).__init__(*args, **kwargs)
self.fields['accepted_countries'].widget = CheckboxSelectMultiple()
self.fields['restricted_countries'].widget = CheckboxSelectMultiple()
In the view by default if you just need to check if form.is_valid() and save. django modelform will manage the intermediary step for you.
The bookmakers field in Country allows you to retrieve all the bookmaker associated to a country without having to check in both accepted and restricted (it's more for convenience).
But you will need to add the bookmaker to that list on you view like:
class CreateBookmakerView(CreateView):
model = Bookmaker
form_class = BookmakerForm
success_url = reverse_lazy('your success url name here')
def form_valid(self,form):
bookmaker = form.save()
bookmaker.country_set.add(bookmaker)
bookmaker.save()
return HttpResponseRedirect(self.get_success_url())

How to add multiple foreign key in Django

I am beginner in Django.
I am having two models
class Employee(models.Model):
full_name = models.CharField(max_length=120,default=None)
designation = models.CharField(max_length=80,default=None)
class Leave(models.Model):
employee = models.ForeignKey(Employee, related_name='employee')
number_of_days = models.IntegerField(default=0)
Now I have inline Leave Model with Employee in admin.py
so that I can add as many leaves I want in employees
But when I retrieve this model in views new Leaves are created, all I want is one employee should show total = number of days, but it creates new leaves, not able to build any logic here I am stuck, please ask if u don't understand what I am asking.
Not exactly sure what you are asking, but my guess is you want to display the total number of absent days of an employee in the admin. You can use aggregation and Sum in particular and a custom method on your model:
# models
from django.db.models import Sum
class Employee(models.Model):
def absent_days(self):
return self.leaves.aggregate(s=Sum('number_of_days'))['s'] or 0
absent_days.short_description = 'Absent days' # used as column header/field label
class Leave(models.Model):
# note the changed related name!
employee = models.ForeignKey(Employee, related_name='leaves')
# admin
class EmployeeAdmin(admin.ModelAdmin):
readonly_fields = [..., 'absent_days', ...]
fields = [..., 'absent_days', ...]
list_display = [..., 'absent_days', ...]

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

Searching by related fields in django admin

I've been looking at the docs for search_fields in django admin in the attempt to allow searching of related fields.
So, here are some of my models.
# models.py
class Team(models.Model):
name = models.CharField(max_length=255)
class AgeGroup(models.Model):
group = models.CharField(max_length=255)
class Runner(models.Model):
"""
Model for the runner holding a course record.
"""
name = models.CharField(max_length=100)
agegroup = models.ForeignKey(AgeGroup)
team = models.ForeignKey(Team, blank=True, null=True)
class Result(models.Model):
"""
Model for the results of records.
"""
runner = models.ForeignKey(Runner)
year = models.IntegerField(_("Year"))
time = models.CharField(_("Time"), max_length=8)
class YearRecord(models.Model):
"""
Model for storing the course records of a year.
"""
result = models.ForeignKey(Result)
year = models.IntegerField()
What I'd like is for the YearRecord admin to be able to search for the team which a runner belongs to. However as soon as I attempt to add the Runner FK relationship to the search fields I get an error on searches; TypeError: Related Field got invalid lookup: icontains
So, here is the admin setup where I'd like to be able to search through the relationships. I'm sure this matches the docs, but am I misunderstanding something here? Can this be resolved & the result__runner be extended to the team field of the Runner model?
# admin.py
class YearRecordAdmin(admin.ModelAdmin):
model = YearRecord
list_display = ('result', 'get_agegroup', 'get_team', 'year')
search_fields = ['result__runner', 'year']
def get_team(self, obj):
return obj.result.runner.team
get_team.short_description = _("Team")
def get_agegroup(self, obj):
return obj.result.runner.agegroup
get_agegroup.short_description = _("Age group")
The documentation reads:
These fields should be some kind of text field, such as CharField or TextField.
so you should use 'result__runner__team__name'.

Categories