Django ORM Left Join With GROUP BY and SUM - python

I am using Django 1.8.
I have a User model and a UserAction model. A user has a type. UserAction has a time, which indicates how long the action took. They look like this:
class User(models.Model):
user_type = models.IntegerField()
class UserAction:
user = models.ForeignKey(User)
time = models.IntegerField()
Now what I want to do is get ALL users of a certain type along with the sum of the time of their actions.So it would look something like this:
{user:1,total_time=5000}, {user:2,total_time=230}, {user:3,total_time=0}
Given I have the required type in a var called type, what I am doing is:
UserAction.objects.filter(user__type=type)
.values('user').annotate(total_time=Sum(time))
This almost does what I need it to however it does not include users of the given type that don't have any UserAction associated with them, in that case I would want the total_time to just be 0. I've been doing some searching but am not quite sure how I would do this. I know how I would do it in raw SQL(just do a left join) but the Django ORM is still pretty new to me. Could anyone point me in the right direction here? Any advice would be appreciated, thanks much!

User.objects.filter(user_type=type).values('id').annotate(total_time=Sum(useraction__time))

Related

Best way to model current version/single elevated list item in Django?

Suppose I'm Santa Claus and I'm making a site to keep track of kids' Christmas lists. I might have something like this:
class Kid(models.Model):
name = models.CharField()
class Gift(models.Model):
name = models.CharField()
class WishList(models.Model):
date_received = models.DateTimeField()
kid = models.ForeignKey(Kid, on_delete=CASCADE)
I have implemented the WishList model, instead of just linking Gifts to Kids with ForeignKeys directly, because I would like to keep track of individual Christmas wish lists discretely. I want to archive all the old Christmas lists as I received them. The most obvious way to implement this would be to add something like current = models.BooleanField() to the WishList class, and then filter for wishlist.current=true when I want to get the current wishlist for a given kid. However, there are two problems with this: 1. I don't know how the database query algorithm works behind the scenes, and I don't want to make the computer look through every list to find the current one, when 99% of the time that's the only one I'll be looking at, and 2. this doesn't prevent me from having two current wish lists, and I don't want to have to write special validator code to ensure that.
The second option I thought of would be to simply display the most recently received WishList. However, this doesn't satisfy all of my usage needs--I might have a kid write me saying, "wait, actually, disregard the list I sent in October, and use the one I sent in August instead." I could, of course, make a copy of the August list marked with today's date, but that feels both unnecessary and confusing.
The third option I thought of was that I could get rid of the ForeignKey field in WishList, and instead put the links in the Kid model:
class Kid(models.Model):
name = models.CharField()
current_wishlist = models.OneToOneField(WishList)
archived_wishlists = models.ManyToManyField(WishList)
This last one seems the most promising, but I am unfamiliar with OneToOneFields and ManyToManyFields, and am unsure if it is best practice. It also feels bad to have two separate fields for one type of model relation. Can anyone give me some guidance on what the best way to accomplish this would be?
I'm no sure on what would be the best way, but you could also do something like:
class Kid(models.Model):
name = models.CharField()
current_wishlist = models.ManyToManyField(Gift)
archived_wishlists = models.ManyToManyField(Gift)
and when archiving a gift just do
kidObj.current_wishlist.remove(giftObj)
kidObj.archived_wishlist.add(giftObj)
and viewing them
listofgifts = kidObj.current_wishlist.all()
powerrangergifts = kidObj.current_wishlist.filter(name__icontains='Power Ranger')
standard should be
or
archived_wishlists = models.ManyToManyField(WishList)
it works but you now could add one whishlist to multiple kids
kid - whishlist is a one-many relation therefor you have to put the foreign key into the "many model"
I don't know how the database query algorithm works behind the scenes, and I don't want to make the computer look through every list to find the current one, when 99% of the time that's the only one I'll be looking at
well you dont have to know
this doesn't prevent me from having two current wish lists, and I don't want to have to write special validator code to ensure that.
then go for the OnetoOne but you have to ensure the kid matches the one referenced in the whishlist

Get variable OneToOneField in Django

Lets say I have a Form model:
class Form(models.Model):
name = models.TextField()
date = models.DateField()
and various "child" models
class FormA(models.Model):
form = models.OneToOneField(Form, on_delete=models.CASCADE)
property_a = models.TextField()
class FormB(models.Model):
form = models.OneToOneField(Form, on_delete=models.CASCADE)
property_b = models.IntegerField()
class FormC(models.Model):
form = models.OneToOneField(Form, on_delete=models.CASCADE)
property_c = models.BooleanField()
a Form can be one AND ONLY ONE of 3 types of forms (FormA, FormB, FormC). Given a Query Set of Form, is there any way I can recover what types of Form (A, B or C) they are?
I would need to get a better understanding of your actual use case to know whether this is a good option for you or not, but in these situations, I would first suggest using model inheritance instead of a one to one field. The code you have there is basically doing what multi-table inheritance already does.
Take a read through the inheritance docs real quick first and make sure that multi-table inheritance makes sense for you as compared to the other options provided by django. If you do wish to continue with multi-table inheritance, I would suggest taking a look at InheritanceManager from django-module-utils.
At this point (if using InheritanceManager), you would be able to use isinstance.
for form in Form.objects.select_subclasses():
if isinstance(form, FormA):
..... do stuff ......
This might sound like a lot of extra effort but IMO it would reduce the moving parts (and custom code) and make things easier to deal with while still handling the functionality you need.
You can check it by name or isinstance.
a = FormA()
print(a.__class__)
print(a.__class__.__name__)
print(isinstance(a, Forma))
outputs:
<class __main__.FormA at 0xsomeaddress>
'FormA'
True
------------------- EDIT -----------------
Ok based on your comment, you just want to know which instance is assigned to your main Form.
So you can do something like this:
if hasattr(form, 'forma'):
# do something
elif hasattr(form, 'formb'):
# do something else
elif hasattr(form, 'formb'):
# do something else
After investigating a bit I came up with this
for form in forms:
#reduces fields to those of OneToOne types
one_to_ones = [field for field in form._meta.get_fields() if field.one_to_one]
for o in one_to_ones:
if hasattr(form,o.name):
#do something
Might have some drawbacks (maybe bad runtime) but serves its purpose for now.
Ideas to improve this are appreciated

Is it possible to do arithmetic operation on OuterRef expression?

I'm building an Experience booking system in django and I've the following models.
class Experience(models.Model):
name = models.CharField(max_length=20)
capacity = models.IntegerField()
class Booking(models.Model):
experience = models.ForeignKey(Experience)
occupied = models.IntegerField()
Each experience has a limited number of capacity and when user perform booking, it will be added to the Booking table with occupied number. Now how will I find the experiences which are not occupied completely?
available_experiences = Experience.objects.all().exclude(id__in=Subquery(Booking.objects.filter(occupied__gt=OuterRef('capacity') - request_persons).values_list('experience', flat=True)))
Here, request_persons is the number of required vacancy in an experience. This is not working and showing an error like 'ResolvedOuterRef' object has no attribute 'relabeled_clone'. Is it possible to do arithmetic operation on OutRef() expression like F()?
Without adding request_persons, the above code works. Why it is not possible to add a value to the OutRef() expression?
NOTE: My actual code is much complex one and it will be really great to get an answer without modifying the entire structure of the above code.
By doing arithmetic operations in the query referenced by OuterRef() directly you can resolve this issue:
available_experiences = Experience.objects.annotate(
total=models.F('capacity') - request_persons
).exclude(
id__in=Subquery(Booking.objects.filter(
occupied__gt=OuterRef('total')
).values_list('experience', flat=True))
)
If you found another way without modifying your structure or using RawSQL() or .extra(), let us know!
This seems to be fixed in Django 2.0: https://github.com/django/django/pull/9722/files
The fix can be backported to 1.11.x in a similar fashion:
from django.db.models.expressions import ResolvedOuterRef
if not hasattr(ResolvedOuterRef, 'relabeled_clone'):
ResolvedOuterRef.relabeled_clone = lambda self, relabels: self

Selecting filtered related objects in django

I've got two models like this (simplified):
class Post(models.Model):
content = models.TextField('content')
#...
class Vote(models.Model):
user = ForeignKey(User)
status = models.TextField(choices = some_choices, default = 'likes')
post = models.ForeignKey(Post)
#...
What i want to do is to select (using one query) posts using some filter with one particular (let's say current) user's votes on this posts (it's okay if he didn't vote for it), so I can then output all posts and the user can see which ones he liked, which ones he didn't and which ones he didn't vote at all.
select_related for Vote model will not help here, because related objects cannot be filtered, so I guess I should do something with extra, but I cannot figure out what arguments should I pass.
So I guess, it should be something like:
Post.objects.filter(content__contains="test").extra(tables="app_vote", where={'my_vote_status': 'something here perhaps?'})
Could you please help me to understand how make a query like this?
UPD: schacki provided a good solution, the only problem is that I want to access votes by different users from the template, somtehing like Post.vote_by_me and Post.vote_by_this_user or Post.vote_by_top_user
Well, If you proper want answers, sometimes you should look for them yourself :)
Here's how I've solved my problem:
posts = Post.objects.filter(content__contains="test"
).extra(select={
#extra selects vote status here for current user
"my_vote_status":"""SELECT status FROM blog_vote as vt
WHERE vt.user_id = %s
AND vt.post_id = blog_posts.id
""" % (request.user.pk) #
}, tables=['blog_vote'])
UPD: probably would work without tables argument
If I understand your requirements correctly, you will need two objects to pass into context. Try like this, where me and other_user must be valid user objects.
posts.vote_by_me=Post.objects.filter(content__contains="test",vote_set__status="something here perhaps?",vote_set__user=me)
posts.vote_by_other_user=Post.objects.filter(content__contains="test",vote_set__status="something here perhaps?",vote_set__user=other_user)
It's very difficult to understand what you want, but here is another attempt. First, get your posts:
posts = Post.objects.filter(whatever)
Now you want all the votes by a group of users on the posts, correct?
votes = Vote.objects.filter(post__in=posts, user__in=users)
Now all you have to do is associate the votes to the posts based on, say, the user id:
votes_by_user_by_post = defaultdict(lambda: defaultdict(list))
for v in votes:
votes_by_user_by_post[post.id][v.user_id].append(v)
for post in posts:
post.votes_by_user = votes_by_user_by_post[post.id]
Performance-wise, it's fine to do this in two queries plus some scripting. They aren't complicated queries and the scripting part is just two for-loops.

Django: Grouping and ordering across foreign keys with conditions

I have some Django models that record people's listening habits (a bit like Last.fm), like so:
class Artist(models.Model):
name = models.CharField()
class Song(models.Model):
artist = models.ForeignKey(Artist)
title = models.CharField()
class SongPlay(models.Model):
song = models.ForeignKey(Song)
user = models.ForeignKey(User)
time = models.DateTimeField()
class User(models.Model):
# doesn't really matter!
I'd like to have a user page where I can show the top songs that they've listened to in the past month. What's the best way to do this?
The best I've come up with so far is:
SongPlay.past_month
.filter(user=user)
.values('song__title', 'song__id', 'song__artist__name')
.annotate(plays=Count('song'))
.order_by('-plays')[:20]
Above, past_month is a manager that just filters plays from the last month. Assume that we've already got the correct user object to filter by as well.
I guess my two questions are:
How can I get access to the original object as well as the plays annotation?
This just gives me certain values, based on what I pass to values. I'd much rather have access to the original object – the model has methods I'd like to call.
How can I group from SongPlay to Artist?
I'd like to show a chart of artists, as well as a chart of songs.
You can use the same field in both values and annotate.
You have the primary key of the Song object (you could just use song instead of song__id), so use
Song.objects.get(id=...)
For your second question, do a separate query with song__artist as the field in values and annotate:
from django.db.models import Count
SongPlay.past_month
.filter(user=user)
.values('song__artist')
.annotate(plays=Count('song__artist'))
.order_by('-plays')[:20]
agf has already showed you how to group by song_artist. What I would do to get the actual Song object is store it in memcached, or if the method you are calling is rather simplistic make it a static method or a class method. You might could also initialize a Song object with the data from the query and not actually save it to get access to this method. Might help to know the details of the methods you want to call from the Song object.

Categories