django left join with where clause subexpression - python

I'm currently trying to find a way to do something with Django's (v1.10) ORM that I feel should be possible but I'm struggling to understand how to apply the documented methods to solve my problem.
Edit: So here's the sql that I've hacked together to return the data that I'd like from the dbshell, with a postgresql database now, after I realised that my original sqlite3 backed sql query was incorrect:
select
voting_bill.*,vv.vote
from
voting_bill
left join
(select
voting_votes.vote,voting_votes.bill_id
from
voting_bill
left join
voting_votes
on
voting_bill.id=voting_votes.bill_id
where
voting_votes.voter_id = (select id from auth_user where username='richard' or username is Null)
)
as
vv
on
voting_bill.id=vv.bill_id;
Here's the 'models.py' for my voting app:
from django.db import models
from django.contrib.auth.models import User
class Bill(models.Model):
name = models.CharField(max_length=255)
description = models.TextField()
result = models.BooleanField()
status = models.BooleanField(default=False)
def __str__(self):
return self.name
class Votes(models.Model):
vote = models.NullBooleanField()
bill = models.ForeignKey(Bill, related_name='bill',
on_delete=models.CASCADE,)
voter = models.ForeignKey(User, on_delete=models.CASCADE,)
def __str__(self):
return '{0} {1}'.format(self.bill, self.voter)
I can see that my sql works as I expect with the vote tacked onto the end, or a null if the user hasn't voted yet.
I was working to have the queryset in this format so that I can iterate over it in the template to produce a table and if the result is null I can instead provide a link which takes the user to another view.
I've read about select_related and prefetch_related, but as I said, I'm struggling to work out how I translate this to how I can do this in SQL.

Hope I correctly understood your problem. Try this:
votes = Votes.objects.filter(voter__username='django').select_related('bill')
You can use this. But I think you do not need select_related in this case.
bills_for_user = Bill.objects.filter(votes__voter__username='django').select_related('votes').distinct()
Now you can iterate your bills_for_user
for bill in bills_for_user:
bill_name = bill.name
bill_description = bill.description
bill_result = bill.result
bill_status = bill.status
# and there are several variants what you can with votes
bill_votes = bill.votes_set.all() # will return you all votes for this bill
bill_first_vote1 = bill.votes_set.first() # will return first element in this query or None if its empty
bill_first_vote2 = bill.votes_set.all()[0] # will return first element in this query or Error if its empty
bill_last_vote = bill.votes_set.last()[0] # will return last element in this query or None if its empty
# you can also filter it for example by voting
bill_positive_votes = bill.votes_set.filter(vote=True) # will return you all votes for this bill with 'vote' = True
bill_negative_votes = bill.votes_set.filter(vote=False) # will return you all votes for this bill with 'vote' = False
bill_neutral_votes = bill.votes_set.filter(vote=None) # will return you all votes for this bill with 'vote' = None

Related

How to build a queryset in a specific order in Django

I'm trying to list profiles of users. I want to list them in such a way that profiles with same city of the user should come first, then next priority should be state, then country, and finally, rest of the profiles. This is what I have tried.
model
class Profile(models.Model):
uuid = UUIDField(auto=True)
user = models.OneToOneField(User)
country = models.ForeignKey(Country, null=True)
state = models.ForeignKey(State, null=True)
city = models.ForeignKey(City, null=True)
views.py
current_user = Profile.objects.filter(user=request.user)
profiles_city = Profile.objects.filter(city=current_user.city)
profiles_state = Profile.objects.filter(state=current_user.state)
profiles_country = Profile.objects.filter(country=current_user.country)
profiles_all = Profile.objects.all()
profiles = (profiles_city | profiles_state | profiles_country | profiles_all).distinct()
But it is yielding the same result as Profile.objects.all()
Please help me. thanks in advance
You need the order_by method of QuerySet that orders objects based on passed parameters; this is done on database:
Profile.objects.order_by(
'current_user__city',
'current_user__state',
'current_user__country',
)
Edit:
If you want to sort by the city, state and country names of the logged in user, you can do this on the Python level, using sorted, and a custom key callable:
from functools import partial
def get_sort_order(profile, logged_in_profile):
# This is a simple example, you need to filter against
# the city-state-country combo to match precisely. For
# example, multiple countries can have the same city/
# state name.
if logged_in_profile.city == profile.city:
return 1
if logged_in_profile.state == profile.state:
return 2
if logged_in_profile.country == profile.country:
return 3
return 4
logged_in_profile = request.user.profile # logged-in user's profile
get_sort_order_partial = partial(get_sort_order, logged_in_profile=logged_in_profile)
sorted(
Profile.objects.all(),
key=get_sort_order_partial,
)
Doing the same on the database level, using Case and When to have a Python if-elif-else like construct:
from django.db.models import Case, When, IntegerField
Profile.objects.order_by(
Case(
When(city=logged_in_profile.city, then=1),
When(state=logged_in_profile.state, then=2),
When(country=logged_in_profile.country, then=3),
default=4,
output_field=IntegerField(),
)
)
This will result in a queryset and also has the added advantage of being faster as all the operations would be done on the database (SELECT CASE WHEN ...).

Django queryset filter model attribute against other model attribute

I don't know if I made myself clear with this question title, but, heres my problem:
I have this model, which is just a transactional model:
class InstanceItemEvaluation(models.Model):
instance = models.ForeignKey(Instance)
item = models.ForeignKey(InstanceItem)
user = models.ForeignKey(User)
factor = models.ForeignKey(Factor)
measure = models.ForeignKey(Measure)
measure_value = models.ForeignKey(MeasureValue, null=True, blank=True)
evaluated_at = models.DateTimeField(null=True, blank=True)
Here is a query I must run to only retrieve valid values from the database:
#staticmethod
def get_user_evaluations_by_instance(user, instance):
qs = InstanceItemEvaluation.objects.filter(
user=user,
instance=instance,
factor__is_active=True,
measure__is_active=True).exclude(
factor__measure=None)
return qs
The query set speaks for itself, I am just filtering the user, and the working instance and so on. This query set output this SQL:
SELECT "workspace_instanceitemevaluation"."id",
"workspace_instanceitemevaluation"."instance_id",
"workspace_instanceitemevaluation"."item_id",
"workspace_instanceitemevaluation"."user_id",
"workspace_instanceitemevaluation"."factor_id",
"workspace_instanceitemevaluation"."measure_id",
"workspace_instanceitemevaluation"."measure_value_id",
"workspace_instanceitemevaluation"."evaluated_at"
FROM "workspace_instanceitemevaluation"
INNER JOIN "measures_measure" ON ( "workspace_instanceitemevaluation"."measure_id" = "measures_measure"."id" )
INNER JOIN "factors_factor" ON ( "workspace_instanceitemevaluation"."factor_id" = "factors_factor"."id" )
WHERE ("measures_measure"."is_active" = True
AND "workspace_instanceitemevaluation"."user_id" = 1
AND "factors_factor"."is_active" = True
AND "workspace_instanceitemevaluation"."instance_id" = 5
AND NOT ("factors_factor"."measure_id" IS NULL));
So far so good. But now I need to put this clause on the query:
AND "factors_factor"."measure_id" = "measures_measure"."id"
Which would mean I am only looking for measure values that are currently associated with my factors. Anyway, I tried to do something like this (look at the last filter):
#staticmethod
def get_user_evaluations_by_instance(user, instance):
qs = InstanceItemEvaluation.objects.filter(
user=user,
instance=instance,
factor__is_active=True,
measure__is_active=True).exclude(
factor__measure=None).filter(
factor__measure=measure)
return qs
But that doesn't even make sense. Now I am kinda stuck, and couldn't find a solution. Of course that's something I can do iterating the result and removing the results I don't need. But I am trying to figure out if it is possible to achieve this SQL query I mentioned using the Django queryset API.
I'm not sure if it will work in this case, but generally you can use F() objects for this.
from django.db.models import F
qs = InstanceItemEvaluation.objects.filter(
user=user,
instance=instance,
factor__is_active=True,
measure__is_active=True).exclude(
factor__measure=None).filter(
factor__measure=F('measure_id'))

optimizing no. of queries being fired in django orm for a given model

I have an object, that is uploaded by the user, it contains several details, but for the sake of clarity, can be simply defined by the following model representation -
After this other users can upvote and downvote what this user has uploaded hence a vote model.
Now I want to get the upvotes and downvotes of all the objects to be displayed in the template. Hence I add two functions to the ObjectDetail class, as upvote and downvote.
The trouble with this model is, say there are 20 objects, for each object 2 queries are fired one to get the upvote and the other to get the downvote. Hence the no. of queries are 40 now for 20 objects.
What would be a good way to tweak this to reduce the number of queries, and display the upvotes and downvotes on each of the object?
class ObjectDetail(models.Model):
title = models.CharField()
img = models.ImageField()
description = models.TextField()
uploaded_by = models.ForeignKey(User, related_name='voted_by')
#property
def upvote(self):
upvote = Vote.objects.filter(shared_object__id = self.id,
vote_type = True).count()
return upvote
#property
def downvote(self):
downvote = Vote.objects.filter(shared_object__id = self.id,
vote_type = False).count()
return downvote
class Vote(models.Model):
vote_type = models.BooleanField(default = False)
voted_by = models.ForeignKey(User, related_name='voted_by')
voted_for = models.ForeignKey(User, related_name='voted_for')
shared_object = models.ForeignKey(ObjectDetail, null=True, blank=True)
dtobject = models.DateTimeField(auto_now_add=True)
On one hand, django does give you the capability to write raw SQL when you have to. But this example is simple, you should not have to use raw SQL to get this information.
Django will put off making a query until you access the results of a queryset. So you can try to compose the whole query using querysets and Q objects and then access the results on the composed query - this should trigger one DB query (or one per model, rather than one per instance) for all the results.
So, how to do that? You want to get all the Vote records for a given set of ObjectDetail records. I'm going to assume you have a list of ids of ObjectDetail records.
Unfortunately, your upvote and downvote properties return the result of "count" on their query sets. This counts as an "access to the results" of the queryset produced by the "filter" call. I would change those method definitions to refer to the backwards-relation object manager vote_set, like so:
#property
def upvote(self):
answer = 0
for vote in self.vote_set.all ():
if vote.vote_type:
answer += 1
return answer
#property
def downvote(self):
answer = 0
for vote in self.vote_set.all ():
if not vote.vote_type:
answer += 1
return answer
Note we just access the query set of votes for the current object. At this stage, we are assuming that the orm can access the cached results.
Now, in the view and/or template, we want to assemble the big complex query.
My example is a functional view:
def home (request):
# just assigning a constant list for simplicity.
# Also was lazy and did 10 examples rather than 20.
objids = [ 1, 5, 15, 23, 48, 52, 55, 58, 59, 60 ]
# make a bunch of Q objects, one for each object id:
q_objs = []
for objid in objids:
q_objs.append(Q(id__exact = objid))
# 'or' them together into one big Q object.
# There's probably a much nicer way to do this.
big_q = q_objs[0]
for q_obj in q_objs[1:]:
big_q |= q_obj
# Make another queryset that will ask for the Vote objects
# along with the ObjectDetail objects.
# Try commenting out this line and uncommenting the one below.
the_objects = ObjectDetail.objects.filter(big_q).prefetch_related('vote_set')
# the_objects = ObjectDetail.objects.filter(big_q)
template = 'home.html'
context = {
'the_objects' : the_objects,
}
context_instance = RequestContext (request)
return render_to_response (template, context, context_instance)
Here are some pointers to related documentation:
https://docs.djangoproject.com/en/1.5/topics/db/queries/#querysets-are-lazy
https://docs.djangoproject.com/en/1.5/ref/models/querysets/#when-querysets-are-evaluated
https://docs.djangoproject.com/en/1.5/topics/db/queries/#complex-lookups-with-q-objects
https://docs.djangoproject.com/en/1.5/topics/db/queries/#following-relationships-backward
It's in the docs at https://docs.djangoproject.com/en/1.5/ref/models/querysets/#django.db.models.query.QuerySet.extra
I am using the extra() clause to inject some raw sql here.
EDIT: this works with an app called 'vot' and at least Sqlite. Change the vot_* table names to your needs.
from django.db.models import Count
objects = ObjectDetail.objects.all().extra(
select={ 'upvotes': '''SELECT COUNT(*) FROM vot_vote
WHERE vot_vote.shared_object_id = vot_objectdetail.id
AND vot_vote.vote_type = 1''',
'downvotes': '''SELECT COUNT(*) FROM vot_vote
WHERE vot_vote.shared_object_id=vot_objectdetail.id
AND vot_vote.vote_type = 0'''})
Now each element in objects has a upvotes and downvotes property.

Query for items using filter for most recent entry in a related table

Hello I seem be having a problem with querying. I have a list of items. Any Item can have a status set to it (In, out, Collected, Destroyed, etc.). Here is my views.
def client_summary(request, client_id):
client = None
items = None
try:
client = models.Client.objects.get(pk = client_id)
items = client.storageitem_set.all()
total_items = items.count()
except:
return HttpResponse(reverse(return_clients))
return render_to_response('client_summary.html', {'items':items, 'total_items':total_items, 'client':client}, context_instance = RequestContext(request))
If I have in my template
{%for item in items%}
{{item.itemstatushistory_set.latest}}
{%endfor%}
This will display the all the latest status. Now I want to only to print out all items that their status is Destroyed only. For some reason I can't seem to do this.
Here is some more information from my models as well.
class StorageItem(models.Model):
type = models.ForeignKey(StorageObject)
client = models.ForeignKey(Client)
company_id = models.PositiveIntegerField(unique = True, blank = True, null = True)
content = models.TextField(blank = True)
alternative_id = models.CharField(verbose_name = 'Client no.', max_length = 60, blank = True)
title = models.CharField(max_length = 100)
format = models.ForeignKey(Format, blank = True, null = True)
location = models.CharField(max_length = 20, blank = True)
item_class = models.TextField(blank = True)
def __unicode__(self):
return self.title
class Status(models.Model):
description = models.CharField(max_length = 60)
notes = models.TextField(blank = True)
def __unicode__(self):
return self.description
class Meta:
verbose_name_plural = 'Status'
get_latest_by = 'date'
ordering = ['date']
class ItemStatusHistory(models.Model):
date = models.DateTimeField(auto_now = True)
contact = models.ForeignKey(Contact)
item = models.ForeignKey(StorageItem)
status = models.ForeignKey(Status)
user = models.ForeignKey(User)
def __unicode__(self):
return str(self.status
EDIT: There are still some problems because the relation between an item could have many statuses. But I want to only list the most recent status only for destroyed items.
Example: Supposing there are 3 items and they have sets item1 = [in, out, destroyed], item2 = [destroyed, in], item3 = [destroyed, collected, destroyed], item4 = [in] where [1st status, 2nd status, 3rd status, etc]. I only want to display the latest status for that item.
Both Mike and kriegar will get a result like [item1, item2, item3, item3].
Because Yuji used the distinct function, he will get [item1, item2, item3].
The answer I need to get at the end should be [item1, item3].
kriegar's solution will work. There's also this one, which searches by Status id instead of text matching on description:
destroyedStatus = Status.objects.get(description="destroyed")
clients_destroyed_items = StorageItem.objects.filter(client=client,
itemstatushistory__status=destroyedStatus)
This assumes descriptions are unique, but you have no such constraint in your model. I have no idea which implementation is faster.
EDIT: By the way, if you've got some crazy system where you have more than one Status with a description of "destroyed", and you wanted to query by Status ids instead of description, you would just do:
destroyedStatusIDs = Status.objects.filter(description="destroyed").values_list("id", flat=True)
clients_destroyed_items = StorageItem.objects.filter(client=client,
itemstatushistory__status__in=destroyedStatusIDs)
BTW, it's considered good practice to set related_name on your ForeignKey, OneToOneField, and ManyToManyField relationships, usually to plurals. So your history class becomes:
class ItemStatusHistory(models.Model):
date = models.DateTimeField(auto_now=True)
contact = models.ForeignKey(Contact, related_name="history")
item = models.ForeignKey(StorageItem, related_name="history")
status = models.ForeignKey(Status, related_name="history")
user = models.ForeignKey(User, related_name="history")
which would change my first example to:
destroyedStatus = Status.objects.get(description="destroyed")
clients_destroyed_items = StorageItem.objects.filter(client=client,
history__status=destroyedStatus)
EDIT 2: Ah, so you only want to consider the current (i.e. latest) Status. This is where aggregation and F objects come in. Basically, the idea is to have the database create a "fake column" in the table which has the date of the latest (i.e. maximum date) Status, then require the date to match as well as the status:
from django.db.models import F, Max
destroyedStatus = Status.objects.get(description="destroyed")
clients_destroyed_items = StorageItem.objects.annotate(
last_change_date=Max("itemstatushistory__date")).filter(client=client,
itemstatushistory__status=destroyedStatus,
itemstatushistory__date=F("last_change_date"))
I haven't tested this, this is the first time I've tried this, and there may be a better way, so comments are welcome.
If you want a queryset of the items that belong to a client and are destroyed:
clients_destroyed_items = StorageItem.objects.filter(client=client,
itemstatushistory__status__description='destroyed')
Lookups that span relationships¶
Django offers a powerful and intuitive
way to "follow" relationships in
lookups, taking care of the SQL JOINs
for you automatically, behind the
scenes. To span a relationship, just
use the field name of related fields
across models, separated by double
underscores, until you get to the
field you want.
This example retrieves all Entry
objects with a Blog whose name is
'Beatles Blog':
Entry.objects.filter(blog_name_exact='Beatles
Blog')
This spanning can be as deep as you'd
like.
It works backwards, too. To refer to a
"reverse" relationship, just use the
lowercase name of the model.

map raw sql to django orm

Is there a way to simplify this working code?
This code gets for an object all the different vote types, there are like 20 possible, and counts each type.
I prefer not to write raw sql but use the orm. It is a little bit more tricky because I use generic foreign key in the model.
def get_object_votes(self, obj):
"""
Get a dictionary mapping vote to votecount
"""
ctype = ContentType.objects.get_for_model(obj)
cursor = connection.cursor()
cursor.execute("""
SELECT v.vote , COUNT(*)
FROM votes v
WHERE %d = v.object_id AND %d = v.content_type_id
GROUP BY 1
ORDER BY 1 """ % ( obj.id, ctype.id )
)
votes = {}
for row in cursor.fetchall():
votes[row[0]] = row[1]
return votes
The models im using
class Vote(models.Model):
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
payload = generic.GenericForeignKey('content_type', 'object_id')
vote = models.IntegerField(choices = possible_votes.items() )
class Issue(models.Model):
title = models.CharField( blank=True, max_length=200)
The code Below did the trick for me!
def get_object_votes(self, obj, all=False):
"""
Get a dictionary mapping vote to votecount
"""
object_id = obj._get_pk_val()
ctype = ContentType.objects.get_for_model(obj)
queryset = self.filter(content_type=ctype, object_id=object_id)
if not all:
queryset = queryset.filter(is_archived=False) # only pick active votes
queryset = queryset.values('vote')
queryset = queryset.annotate(vcount=Count("vote")).order_by()
votes = {}
for count in queryset:
votes[count['vote']] = count['vcount']
return votes
Yes, definitely use the ORM. What you should really be doing is this in your model:
class Obj(models.Model):
#whatever the object has
class Vote(models.Model):
obj = models.ForeignKey(Obj) #this ties a vote to its object
Then to get all of the votes from an object, have these Django calls be in one of your view functions:
obj = Obj.objects.get(id=#the id)
votes = obj.vote_set.all()
From there it's fairly easy to see how to count them (get the length of the list called votes).
I recommend reading about many-to-one relationships from the documentation, it's quite handy.
http://www.djangoproject.com/documentation/models/many_to_one/

Categories