ORDERBY query on 2 django models collectively - django

I have 2 models namely A and B. Both have a common field, say timestamp.
X = A.objects.all()
Y = A.objects.all()
results = chain(X,Y) # Union
Now I want to apply ORDERBY query on results. How can I do that?

While the python way of doing it is by using some kind of sorting, this is normally not the correct way of doing it in Django.
The reason is that in order to join two queries of two different models, you have to transform them to lists (as other answers suggest).
While this seems good, most of the time it is not, because you hit the database, i.e. the database is asked for all elements of both models for creating both lists, and them joining.
The most likely reason why you are joining two models in a single list is because both should be derived from a common model, e.g. an Abstract model. Doing this both models have a natural common field obtained by being subclasses of the abstract model.
The ordering of such a query can then be made using order_by. This is more efficient because queries are lazy.
EDIT:
Example:
class Parent(models.Model):
date_field = models.DateField()
def pretty_show():
# returns general behavior
class B(Parent):
# other specific fields
def pretty_show():
# returns specific behavior
class C(Parent):
# other specific fields
def pretty_show():
# returns specific behavior
X = Parent.objects.all().order_by("date_field")
for x in X:
x.pretty_show()
Notice that Parent class in this case is not Abstract and childs are not proxies, but this choice strongly depends on what you want to achieve. Hope this helps.

You mean:
X = A.objects.all()
Y = B.objects.all()
results = chain(X,Y) # Union
You can cast result to list(). List has order function:
ut.sort(key=lambda x: x.count, reverse=True)
more

You could try to create a list of the model objects and then sort by their time stamp like this (both models must have the same time_stamp attribute):
l= list(x)+list(y)
l.sort(key=lambda l: l.time_stamp)
Hope this is what you are looking for.

Related

How to use select_related with Django create()?

class Parent(models.Model):
# some fields
class Child(models.Model):
parent = models.ForeginKey(Parent)
species = models.ForeignKey(Species)
# other fields
I have a function like this:
1. def some_function(unique_id):
2. parent_object = Parent.objects.get(unique_id=unique_id)
3. new_child = Child.objects.create(name='Joe', parent=parent_object)
4. call_some_func(new_child.parent.name, new_child.species.name)
In the line 4, a db query is generated for Species. Is there any way, I can use select_related to prefetch the Species, so as to prevent extra query.
Can it be done while I use .create(). This is just an example , I am using many other fields too and they are querying the DB every time.
The only way I can think is after line 3 using this code:
child_obj = Child.objects.select_related('species').get(id=new_child.id)
The only parameter that create accepts is force_insert which is not related to what you're asking, so it seems it's not possible. Also, noticing that create performs an INSERT ... RETURNING ... statement, I don't think it would be possible anyway because you cannot return columns from other tables.
Possibly the best approach is what you already suggested: do a get() afterwards with the related fields you need.

How to get the ordering of a Django model query set?

view_set.get_queryset().query.order_by will get you the ordering tuple of a ViewSet class in Django REST Framework, but ModelName.objects.get_queryset().query.order_by is always an empty tuple. How come it's not populated? I can see from the database logs that the query is ordered when running ModelName.objects.all(). I've also tried to run the actual query, in case the ordering is populated lazily, but even that does not work:
>>> all = ModelName.objects.all()
>>> for instance in all:
... True
>>> all.query.order_by
()
This bit of the internals of the SQL compiler might help. It looks like the ordering information is stored in several different places depending on where it is defined, to allow for overriding at different levels:
if self.query.extra_order_by:
ordering = self.query.extra_order_by
elif not self.query.default_ordering:
ordering = self.query.order_by
elif self.query.order_by:
ordering = self.query.order_by
elif self.query.get_meta().ordering:
ordering = self.query.get_meta().ordering
self._meta_ordering = ordering
query.order_by is only set when the queryset is explicitly ordered. If you're relying on default ordering defined on the model class, this will be stored in query.get_meta().ordering.

Join annotations in Django without raw SQL

I have a model that has arbitrary key/value pairs (attributes) associated with it. I'd like to have the option of sorting by those dynamic attributes. Here's what I came up with:
class Item(models.Model):
pass
class Attribute(models.Model):
item = models.ForeignKey(Item, related_name='attributes')
key = models.CharField()
value = models.CharField()
def get_sorted_items():
return Item.objects.all().annotate(
first=models.select_attribute('first'),
second=models.select_attribute('second'),
).order_by('first', 'second')
def select_attribute(attribute):
return expressions.RawSQL("""
select app_attribute.value from app_attribute
where app_attribute.item_id = app_item.id
and app_attribute.key = %s""", (attribute,))
This works, but it has a bit of raw SQL in it, so it makes my co-workers wary. Is it possible to do this without raw SQL? Can I make use of Django's ORM to simplify this?
I would expect something like this to work, but it doesn't:
def get_sorted_items():
return Item.objects.all().annotate(
first=Attribute.objects.filter(key='first').values('value'),
second=Attribute.objects.filter(key='second').values('value'),
).order_by('first', 'second')
Approach 1
Using Djagno 1.8+ Conditional Expressions
(see also Query Expressions)
items = Item.objects.all().annotate(
first=models.Case(models.When(attribute__key='first', then=models.F('attribute__value')), default=models.Value('')),
second=models.Case(models.When(attribute__key='second', then=models.F('attribute__value')), default=models.Value(''))
).distinct()
for item in items:
print item.first, item.second
Approach 2
Using prefetch_related with custom models.Prefetch object
keys = ['first', 'second']
items = Item.objects.all().prefetch_related(
models.Prefetch('attributes',
queryset=Attribute.objects.filter(key__in=keys),
to_attr='prefetched_attrs'),
)
This way every item from the queryset will contain a list under the .prefetched_attrs attribute.
This list will contains all filtered-item-related attributes.
Now, because you want to get the attribute.value, you can implement something like this:
class Item(models.Model):
#...
def get_attribute(self, key, default=None):
try:
return next((attr.value for attr in self.prefetched_attrs if attr.key == key), default)
except AttributeError:
raise AttributeError('You didnt prefetch any attributes')
#and the usage will be:
for item in items:
print item.get_attribute('first'), item.get_attribute('second')
Some notes about the differences in using both approaches.
you have a one idea better control over the filtering process using the approach with the custom Prefetch object. The conditional-expressions approach is one idea harder to be optimized IMHO.
with prefetch_related you get the whole attribute object, not just the value you are interested in.
Django executes prefetch_related after the queryset is being evaluated, which means a second query is being executed for each clause in the prefetch_related call. On one way this can be good, because it this keeps the main queryset untouched from the filters and thus not additional clauses like .distinct() are needed.
prefetch_related always put the returned objects into a list, its not very convenient to use when you have prefetchs returning 1 element per object. So additional model methods are required in order to use with pleasure.

filter with select_related on Django

I have a problem with the usage of select_related feature of Django with the filter operation, here's my problem, I have three classes :
class A:
# various fields here
class B(models.model):
related_A = models.ForeignKey(A)
related_C = models.ForeignKey(C)
attribute1 = models.CharField(..)
# Other attributes
class C(models.model):
# Attributes
What I'm trying to do is, getting class A by filtering on class B on the key related_C according to another parameter attribute1 (from class B).
To illustrate it properly, I have a function get_class_A(self) in my class C
get_class_A(self,param):
classes_B = B.objects.filter(related_C = self,attribute1 = param)
It returns a QuerySet of classes B. What I want to do is to follow the ForeignKey pointing to A in order to transform this QuerySet of B into a list of objects A.
I tried various things such as :
classes_A = B.objects.select_related('A').filter(related_C = self, attribute1 = param)
and some variations but nothing worked. Does anyone knows how to do this ?
Thanks
def get_class_A(self, param):
return A.objects.filter(b__related_c=self, b__attribute1=param).distinct()
What you've described looks a lot like a ManyToMany relationship between A and C. If you declare it as such, and include your extra attributes by specifying B as a through model, Django will create the relationship between A and C for you.
Also, select_related() has nothing to do with filtering results, it's just a tool that can allow you to reduce the number of database queries. From the docs:
This is a performance booster which results in a single more complex query but means later use of foreign-key relationships won’t require database queries.

django efficient way to combine 2 queries or make a compound one?

my (simplified) models are like this:
class Story(models.Model):
wikiedit = models.BooleanField(default=False)
writers = models.ManyToManyField(User,null=True,blank=True)
class Writer(models.Model):
user = models.OneToOneField(User)
Now I am trying to build a queryset which includes all stories that meet: wikiedit = True or user in writers
So inside my view I make two queries:
wikistories = Story.objects.filter(wikiedit=True)
writerstories = request.user.objects.story_set.filter
But I would like to make this with just one database hit if it is possible. I guess it would be more efficient. I cannot find if the in operator is supported in this m2m relations in the sense:
Story.objects.filter(writers__contains=request.user) #but this is a TypeError
Maybe is just more efficient to make 2 querysets and then join them in a list, but would love to have it in one.
Any clues? Thx!
To check an M2M relation, you just do =, not contains:
Story.objects.filter(writers=request.user)
And you can use Q objects to do an or query:
Story.objects.filter(Q(wikiedit=True) | Q(writers=request.user))