A QuerySet by aggregate field value - python

Let's say I have the following model:
class Contest:
title = models.CharField( max_length = 200 )
description = models.TextField()
class Image:
title = models.CharField( max_length = 200 )
description = models.TextField()
contest = models.ForeignKey( Contest )
user = models.ForeignKey( User )
def score( self ):
return self.vote_set.all().aggregate( models.Sum( 'value' ) )[ 'value__sum' ]
class Vote:
value = models.SmallIntegerField()
user = models.ForeignKey( User )
image = models.ForeignKey( Image )
The users of a site can contribute their images to several contests. Then other users can vote them up or down.
Everything works fine, but now I want to display a page on which users can see all contributions to a certain contest. The images shall be ordered by their score.
Therefore I have tried the following:
Contest.objects.get( pk = id ).image_set.order_by( 'score' )
As I feared it doesn't work since 'score' is no database field that could be used in queries.

Oh, of course I forget about new aggregation support in Django and its annotate functionality.
So query may look like this:
Contest.objects.get(pk=id).image_set.annotate(score=Sum('vote__value')).order_by( 'score' )

You can write your own sort in Python very simply.
def getScore( anObject ):
return anObject.score()
objects= list(Contest.objects.get( pk = id ).image_set)
objects.sort( key=getScore )
This works nicely because we sorted the list, which we're going to provide to the template.

The db-level order_by cannot sort queryset by model's python method.
The solution is to introduce score field to Image model and recalculate it on every Vote update. Some sort of denormalization. When you will can to sort by it.

Related

How to update Django model data

I want to know how I can add data to the Django integer field after the ins.save in my code. For example, if the donation field is equal to 3, after the ins.save, I want to add 1 to it and therefore it will be equal to 4. My code is down below.
Donate View:
def donate(request):
if request.method == "POST":
title = request.POST['donationtitle']
phonenumber = request.POST['phonenumber']
category = request.POST['category']
quantity = request.POST['quantity']
location = request.POST['location']
description = request.POST['description']
ins = Donation(title = title, phonenumber = phonenumber, category = category, quantity = quantity, location = location, description = description, user=request.user, )
ins.save()
return render(request,'donate.html')
You can try this.
from django.db.models import F
......
ins.save()
UserDetail.objects.filter(user=request.user).update(donations=F('donations') + 1)
This will increase the donations of request.user by 1 after ins.save()
Also instead of nullable donation field set default value to zero for your IntegerField in your UserDetail model
class UserDetail(models.Model):
donations = models.IntegerField(default=0)
You don't really need a donations field to count the number of donations of a user as it can be done by a query using the Count aggregation function [Django docs]. For example the below query will give you all users and their donation counts:
from django.db.models import Count
# Assuming 'User' is your user model name
users = User.objects.annotate(donation_count=Count('donation'))
for user in users:
print(user.username, user.donation_count)
# If we already have a user instance
donation_count = user.donation_set.count()
Moving further UserDetail appears to be a model that contains extra details about the user, but it has a problem that you are using a Foreign Key, instead of that you should be using a OneToOneField [Django docs] instead, and if you want donations in this model you can add a property that will make that query to this model:
class UserDetail(models.Model):
points = models.IntegerField(blank=True, null = True,)
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
) # A user detail without a user doesn't make much sense, hence no null or blank allowed
#property
def donations(self):
return self.user.donation_set.count()

Loop through and Merge Querysets

I am sure this one is straight forward but I cannot seem to get my head around it.
I have "Users" who can post "Posts" on my site.
Each user can follow other users.
The idea is to display all the posts posted by the users that current user is following.
Example : Foo followed Bar and Baz. I need to retrieve all the posts from Bar and Baz.
Bar = Post.objects.filter(user=3)
Baz = Post.objects.filter(user=4)
totalpost= list(chain(Bar, Baz))
print(totalpost)
On this occasion, when both variables userXposts and temp are hardcoded, I can easily retrieve ONE list of QuerySets neeatly by chaining both QuerySets.
However, I cannot have those hardcoded. As such, I am attempted to loop through each user posts and add it in a list since my user can follow X amount of users :
QuerySet = Profile.objects.filter(follower=1)
for x in QuerySet:
userXposts = Post.objects.filter(user=x.user.id)
temp = userXposts
totalpost= list(chain(userXposts, temp))
temp = []
print("Totalpost after union of userpost and temp: ", totalpost)
Here, Profile.objects.filter(follower=1) return two sets of QuerySets, one for Baz and one for Bar.
The problem that I have so far is that totalpost endup being a "list of list" (I believe) which forces me to call totalpost[0] for Bar posts and totalpost[1] for Baz posts.
Since I am attempting to use Pagination with Django, I am forced to pass ONE Variable only in p= Paginator(totalpost, 200)
Would you be able to assist in the loop so that I can fetch the data for the first user, add it to a variable, then go to the second user and ADD the second QuerySet data to the list where the First User data is?
Thanks a lot !
EDIT :
Here are the Models :
class User(AbstractUser):
pass
class Profile(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
following = models.ManyToManyField(User, blank=True, related_name="following_name")
follower = models.ManyToManyField(User, blank=True, related_name="follower_name")
def __str__(self):
return f'"{self.user.username}" is followed by {self.follower.all()} and follows {self.following.all()}'
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
timestamp = models.DateTimeField(default=datetime.now)
post = models.CharField(max_length=350, null=True, blank=True)
like = models.ManyToManyField(User, blank=True, related_name="like_amount")
def __str__(self):
return f'#{self.id}: "{self.user.username}" posted "{self.post}" on "{self.timestamp}". Like : "{self.like.all()}" '
Post.objects.filter(user__ following_name__id=1)

How to create custom choice field in Django?

I'm trying to create a form in which user fill in language,description and level of the language (intermediate, advanced...).
The language model:
class Language(models.Model):
shortcut = models.CharField(max_length=6)
name = models.CharField(max_length=50)
first_level_price = models.FloatField() # Basic
second_level_price = models.FloatField() # Intermediate
third_level_price = models.FloatField() # Advanced
def __str__(self):
return self.shortcut+': '+self.name
For each language there exist three levels and prices (first_level_price,second_level_price,third_level_price).
Now, I want user to fill the form. One of the forms should be the level. User just would chose either 'Basic','Intermediate' or 'Advanced'. According to this choice, there would be the price counted.
So if user have chosen 'Basic', there would be price according to first_level_price of the language.
I've tried many ways but no one worked.
level = forms.ChoiceInput(choices=('Basic','Intermediate','Advanced'))
You can update your model like this:
class Language(models.Model):
PRICE_CHOICES = (
('first_level_price', 'Basic'),
('second_level_price', 'Intermediate'),
('third_level_price', 'Advanced'),
)
first_level_price = models.FloatField() # Basic
second_level_price = models.FloatField() # Intermediate
third_level_price = models.FloatField() # Advanced
shortcut = models.CharField(max_length=6)
name = models.CharField(max_length=50)
level = models.CharField(max_length=50, choices=PRICE_CHOICES)
def __str__(self):
return self.shortcut+': '+self.name
Now you can just create a ModelForm using this Model and it should work the way you expect.
When an user selects first_level_price aka Basic membership, you can now grab the price from the first_level_price field.
The choices should a tuple of pairs. The first value in the pair is the data you get into your form instance, the second value of the pair is the displayed value in your template:
level = forms.ChoiceField(choices=(('basic', 'Basic'),
('intermediate', 'Intermediate'),
('advanced', 'Advanced')))
Check django doc details about the choices.

django: set model fields as choices in models.py

Is it possible to set the choices of a field from another table?
for example
class Initial_Exam(models.Model):
Question_category = models.CharField(max_length=20, choices = Job.Job_Position)
class Job(models.Model):
Job_Position = models.CharField(max_length=30, null=True)
something like that
To close this:
As commented above, instead of twisting my implementation, setting the foreign key for Initial_Exam and using __unicode__ on Job did the job
should look like this:
class Job(models.Model):
Job_Position = models.CharField(max_length=30, null=True)
def __unicode__(self):
return self.Job_Requirements
that would display the Job_Position as choices in the admin panel
I thank the community, really appreciate it
You can definitely tie to another model with a ForeignKey relationship, but if you've got a smaller number of choices, here's a pattern you might want to consider for smaller choice lists (I use it for fairly constant choice lists less than 10):
class FrequencyType(models.Model):
FREQ_MONTHLY = 1
FREQ_QUARTERLY = 3
FREQ_YEARLY = 12
FREQ_CHOICES = (
(FREQ_MONTHLY, 'Monthly'),
(FREQ_QUARTERLY, 'Quarterly'),
(FREQ_YEARLY, 'Yearly'),
)
frequency = models.SmallIntegerField(default=FREQ_MONTHLY, choices=FREQ_CHOICES)
or, another example:
class YesNoModel(models.Model):
NO = 0
YES = 1
YESNO_CHOICES = (
(NO, 'No'),
(YES, 'Yes'),
)
weight = models.IntegerField(default=NO, choices=YESNO_CHOICES)
Good luck.

Displaying "info" by selecting foreign key in django admin

I was introduced to Python and Django about two weeks ago, so bear with me. I should also say now that I am using Django 1.6 and Python 3.3.
My project is an order management system. Here's how it works: A customer comes into a store and orders an item. That item is put in an order, which is idle until the employee places the order with a supplier. Basically, I have an Order table, which collects information like customer_name, order_date, status, etc. Then I have an EmployeeOrder table (1-1 relationship with Order) which has all of the elements of an Order, but also takes in employee_placed and employee_order_date (pretty much just extends Order).
What I'm trying to do is write code so that when the user selects which customer Order is being fulfilled by placing an EmployeeOrder, that customer Order's information is displayed. I don't really care how it is displayed right now, a pop up will work just fine. I just don't know how to do it and I haven't come across it anywhere. Right now the drop down box just displays Order 1, Order 2, etc. The user isn't going to remember which items were included in which order, so that's why I want the order information to be shown. Anyway, here's my code so far:
models.py
class Order(models.Model):
customer_order_date = models.DateTimeField('Date helped')
customer_placed = models.ForeignKey(Customer)
employee_helped = models.ForeignKey(Employee)
STATUS_OF_ORDER = (
('IDLE', 'Not yet ordered'),
('SHIP', 'Awaiting delivery'),
('PICK', 'Ready for pickup'),
('UNAV', 'Unavailable for order'),
('BACK', 'Backordered - awaiting delivery'),
('CANC', 'Canceled by customer'),
('ARCH', 'Fulfilled - archived'),
)
status = models.CharField(max_length=4, choices=STATUS_OF_ORDER,
default='IDLE', editable=False)
paid = models.BooleanField('Paid', default=False)
ship = models.BooleanField('Ship', default=False)
comments = models.CharField(max_length=200, blank=True, null=True)
item = models.ManyToManyField(Item)
def __str__(self):
return 'Order ' + str(self.id)
def is_idle(self):
return self.status == 'IDLE'
class EmployeeOrder(models.Model):
order = models.OneToOneField(Order, primary_key=True,
limit_choices_to={'status': 'IDLE'})
employee_order_date = models.DateTimeField('Date ordered')
employee_placed = models.ForeignKey(Employee)
admin.py
class OrderAdmin(admin.ModelAdmin):
list_display = ('customer_order_date', 'customer_placed')
raw_id_fields = ('customer_placed', 'item')
class EmployeeOrderAdmin(admin.ModelAdmin):
list_display = ('employee_order_date', 'employee_placed')
Any and all help is appreciated as I still admit that I am a total noob when it comes to Python and Django!
It sounds to me like you want an employee to be able to use the admin site to create an employee order from a customer order. I think it could be as simple as adding a raw ID field for the customer order. That is, I think you can just change EmployeeOrderAdmin like so:
class EmployeeOrderAdmin(admin.ModelAdmin):
list_display = ('employee_order_date', 'employee_placed')
raw_id_fields = ('order',)
Now when an employee creates an employee order, they will be able to use the OrderAdmin page to find the order they want.
Additionally, suppose you want that pop-up window to display the orders in a particular way. In that case, keep in mind that requests to display that pop-up window will contain an additional GET parameter called pop. You could:
class OrderAdmin(admin.ModelAdmin):
def queryset(self, request):
qs = super(ActiveOfferAdmin, self).queryset(request)
if request.GET.get('pop'):
return qs.order_by(...)
return qs

Categories

Resources