I have following Model.
class Gallery(BaseModel):
company = models.ForeignKey(to=Company, on_delete=models.CASCADE)
image = models.ImageField(
upload_to=upload_company_image_to,
validators=[validate_image]
)
def __str__(self):
return f'{self.company.name}'
I want to allow maximum upto 5 image to be uploaded by one company so I tried my query as
def clean(self):
print(Gallery.objects.values('company').annotate(Count('image')).count())
I don't know how to compare above query with Integer 5. How do I do that?
You can retrieve the Companys that have more than five images with:
from django.db.models import Count
Company.objects.alias(
num_images=Count('gallery')
).filter(num_images__gt=5)
Prior to django-3.2, you can work with .annotate(…) [Django-doc]:
from django.db.models import Count
Company.objects.annotate(
num_images=Count('gallery')
).filter(num_images__gt=5)
Related
I have written a python script in my project. I want to update the value of a field.
Here are my modes
class News_Channel(models.Model):
name = models.TextField(blank=False)
info = models.TextField(blank=False)
image = models.FileField()
website = models.TextField()
total_star = models.PositiveIntegerField(default=0)
total_user = models.IntegerField()
class Meta:
ordering = ["-id"]
def __str__(self):
return self.name
class Count(models.Model):
userId = models.ForeignKey(User, on_delete=models.CASCADE)
channelId = models.ForeignKey(News_Channel, on_delete=models.CASCADE)
rate = models.PositiveIntegerField(default=0)
def __str__(self):
return self.channelId.name
class Meta:
ordering = ["-id"]
This is my python script:
from feed.models import Count, News_Channel
def run():
for i in range(1, 11):
news_channel = Count.objects.filter(channelId=i)
total_rate = 0
for rate in news_channel:
total_rate += rate.rate
print(total_rate)
object = News_Channel.objects.filter(id=i)
print(total_rate)
print("before",object[0].total_star,total_rate)
object[0].total_star = total_rate
print("after", object[0].total_star)
object.update()
After counting the total_rate from the Count table I want to update the total star value in News_Channel table. I am failing to do so and get the data before the update and after the update as zero. Although total_rate has value.
The problem
The reason why this fails is because here object is a QuerySet of News_Channels, yeah that QuerySet might contain exactly one News_Channel, but that is irrelevant.
If you then use object[0] you make a query to the database to fetch the first element and deserialize it into a News_Channel object. Then you set the total_star of that object, but you never save that object. You only call .update() on the entire queryset, resulting in another independent query.
You can fix this with:
objects = News_Channel.objects.filter(id=i)
object = objects[0]
object.total_star = total_rate
object.save()
Or given you do not need any validation, you can boost performance with:
News_Channel.objects.filter(id=i).update(total_star=total_rate)
Updating all News_Channels
If you want to update all News_Channels, you actually better use a Subquery here:
from django.db.models import OuterRef, Sum, Subquery
subq = Subquery(
Count.objects.filter(
channelId=OuterRef('id')
).annotate(
total_rate=Sum('rate')
).order_by('channelId').values('total_rate')[:1]
)
News_Channel.objects.update(total_star=subq)
The reason is that object in your case is a queryset, and after you attempt to update object[0], you don't store the results in the db, and don't refresh the queryset. To get it to work you should pass the field you want to update into the update method.
So, try this:
def run():
for i in range(1, 11):
news_channel = Count.objects.filter(channelId=i)
total_rate = 0
for rate in news_channel:
total_rate += rate.rate
print(total_rate)
object = News_Channel.objects.filter(id=i)
print(total_rate)
print("before",object[0].total_star,total_rate)
object.update(total_star=total_rate)
print("after", object[0].total_star)
News_Channel.total_star can be calculated by using aggregation
news_channel_obj.count_set.aggregate(total_star=Sum('rate'))['total_star']
You can then either use this in your script:
object.total_star = object.count_set.aggregate(total_star=Sum('rate'))['total_star']
Or if you do not need to cache this value because performance is not an issue, you can remove the total_star field and add it as a property on the News_Channel model
#property
def total_star(self):
return self.count_set.aggregate(total_star=Sum('rate'))['total_star']
How can I rank the users based on their total number of posts.
example First : total number of posts 1-10
Second : total number of posts 11-20
Third : total number of posts 21-30
models.py
from django.db import models
User = get_user_model()
class Post(models.Model):
author = models.ForeignKey(User, on_delete = models.CASCADE)
title = models.CharField(max_length=200)
text = models.TextField()
def get_absolute_url(self):
return reverse("post_detail",kwargs={'pk':self.pk})
def __str__(self):
return self.title
def count_posts_of(user):
return Post.objects.filter(author=user).count()
Ordering Users by the number of posts
We can annotate a User with the number of Posts, and then use order_by on that annotation:
from django.db.models import Count
User.objects.annotate(nposts=Count('post')).order_by('-nposts')
Here the dash (-) means that we sort in descending order (so from many posts to few posts). If you remove the dash, then it orders in ascending order.
Assigning a numerical rank to the Users
We can also assign a numerical rank to each user (so 1-10 map to 1, 11-20 map to 2, etc.) by adding some extra annotation:
from django.db.models import Count, F
from django.db.models.expressions import Func
User.objects.annotate(
nposts=Count('post'),
nrank=Func(F('nposts') / 10, function='CEIL'),
).order_by('-nposts')
Mapping the numeric rank to an textual rank
We can map this rank to a textual rank, by defining for example a #property:
from django.contrib.auth import User
RANK_TEXTS = ['zero', 'first', 'second', 'third']
def rank_text(self):
nrank = getattr(self, 'nrank', None)
if nrank is None:
nrank = (Post.objects(author=self).count() + 9) // 10
return RANK_TEXTS[nrank]
User.rank_text = property(rank_text)
So we monkey patch the User class such that it has a property rank_text. This will first look if we annotated the User with the nrank attribute. If that is not the case, we calculate the nrank manually. Finally we return the textual counterpart.
So we can for example query with:
u1 = User.objects.first()
u1.rank_text # for example "second"
Se here [so-post] how to monkey patch Django models.
say I have two models like so...
class Product(models.Model):
...
overall_rating = models.IntegerField()
...
class Review(models.Model):
...
product = models.ForeignKey(Product, related_name='review', on_delete=models.CASCADE)
rating = models.IntegerField()
...
I want to use the ratings from all of the child Review objects to build an average overall_rating for the parent Product.
Question: I'm wondering how I may be able to achieve something like this using Django signals?
I am a bit of a newbie to this part of Django; have never really understood the signals part before.
This overall_rating value needs to be stored in the database instead of calculated using a method since I plan on ordering the Product objects based on their overall_rating which is done on a DB level. The method may look something like this if I were to implement it (just for reference):
def overall_rating(self):
review_count=self.review.count()
if review_count >= 1:
ratings=self.review.all().values_list('rating',flat=True)
rating_sum = 0
for i in ratings:
rating_sum += int(i)
return rating_sum / review_count
else:
return 0
Thank you
You want to update your Product after each save of Review. So the best and fastest way would be using post save method. For example, after each saved product you can get all reviews and calculate overall rating and then save it to the Product.
#receiver(post_save, sender=Review, dispatch_uid="update_overall_rating")
def update_rating(sender, instance, **kwargs):
parent = instance.product
all_reviews = Review.objects.filter(product=parent)
parent.overall_rating = get_overall_rating(all_reviews)
I have the following model:
...
from django.contrib.auth.models import User
class TaxonomyNode(models.Model):
node_id = models.CharField(max_length=20)
name = models.CharField(max_length=100)
...
class Annotation(models.Model):
...
taxonomy_node = models.ForeignKey(TaxonomyNode, blank=True, null=True)
class Vote(models.Model):
created_by = models.ForeignKey(User, related_name='votes', null=True, on_delete=models.SET_NULL)
vote = models.FloatField()
annotation = models.ForeignKey(Annotation, related_name='votes')
...
In the app, a User can produce Vote for an Annotation instance.
A User can vote only once for an Annotation instance.
I want to get a query set with the TaxonomyNode which a User can still annotate a least one of its Annotation. For now, I do it that way:
def user_can_annotate(node_id, user):
if Annotation.objects.filter(node_id=node_id).exclude(votes__created_by=user).count() == 0:
return False
else:
return True
def get_categories_to_validate(user):
"""
Returns a query set with the TaxonomyNode which have Annotation that can be validated by a user
"""
nodes = TaxonomyNode.objects.all()
nodes_to_keep = [node.node_id for node in nodes if self.user_can_annotate(node.node_id, user)]
return nodes.filter(node_id__in=nodes_to_keep)
categories_to_validate = get_category_to_validate(<user instance>)
I guess there is a way to do it in one query, that would speed up the process quite a lot. In brief, I want to exclude from the TaxonomyNode set, all the nodes that have all their annotations already voted once by the user.
Any idea of how I could do it? With django ORM or in SQL?
I have Django version 1.10.6
Try to use this:
#SQL query
unvoted_annotations = Annotation.objects.exclude(votes__created_by=user).select_related('taxonomy_node')
#remove duplicates
taxonomy_nodes=[]
for annotation in unvoted_annotations:
if annotation.taxonomy_node not in taxonomy_nodes:
taxonomy_nodes.append(annotation.taxonomy_node)
There would be only one SQL query as select_related will return the related taxonomy_node in a single query. Also there might be a better way to remove duplicates, eg: by using .distinct().
What I have done so far:
taxonomy_node_pk = [a[0] for a in Annotation.objects.exclude(votes__created_by=user)
.select_related('taxonomy_node').values_list('taxonomy_node').distinct()]
nodes = TaxonomyNode.objects.filter(pk__in=taxonomy_node_pk)
I am doing two queries but the second one is not very costly.
It is quite faster than my original version.
Still what I do is not really beatifull. There is no way to get a query set of TaxonomyNode from the Annotation set directly? And then applying disctinct() in it?
class Daily(models.Model):
rpt_date = models.DateField('Report Date', primary_key=True)
total_d_sors = models.IntegerField()
loaded_d_sors = models.IntegerField()
#diff_d_count
d_sors_missed_eod = models.CharField(max_length=300)
total_m_sors = models.IntegerField() #monthly
loaded_m_sors = models.IntegerField() #monthly
m_sors_missed_eod = models.CharField(max_length=300)
I have the above class in my models.py but when I display it through a view I need to have an additional column which will have the difference between two existing columns (total_d_sors and missed_d_sors) i.e., diff_d_count=(total_d_sors - missed_d_sors)... can someone help?
I'm seeing examples with cursor implementation; is there any other way?
Why don't you add a property on the model and calculate it on the fly as you're displaying it in your template?
class Daily(models.Model):
#property
def diff_d_count(self):
return self.total_d_sors - self.missed_d_sors
Then you can access it in your template or wherever via obj.diff_d_count.
To find difference between 2 columns you can use,
1. annotate
2. F expression
Your query would be,
Daily.objects.annotate(diff=F('total_d_sors')-F('missed_d_sors'))
Sample working code with template,
from django.db.models import F
from django.template import Context, Template
context = Context({"daily_objects": Daily.objects.annotate(diff=F('total_d_sors')-F('missed_d_sors'))})
template = Template("{% for i in daily_objects %} {{i.id}} || {{i.diff}}. {% endfor %}")
template.render(context)