Extracting Object values from a Queryset without a for loop - django

I have a model called
class UserTag(models.Model):
name = models.CharField(max_length=64, unique= True)
users = models.ManyToManyField(User)
I am trying to filter its contents based on the user like this
usertags = UserTag.objects.filter(users=request.user)
now I want a list of all the tag names for this particular query. I know I can probably use a loop
for tag in usertags:
tags.append(tag.name)
But what if a user has a 1000 tags? wont this slow down the response?
Is there a more efficient way to handling this?

If you just want the tag names, use a values_list query:
tags = UserTag.objects.filter(users=request.user).values_list('name', flat=True)

Related

Django queryset get max id's for a filter

I want to get a list of max ids for a filter I have in Django
class Foo(models.Model):
name = models.CharField()
poo = models.CharField()
Foo.objects.filter(name__in=['foo','koo','too']).latest_by_id()
End result a queryset having only the latest objects by id for each name. How can I do that in Django?
Edit: I want multiple objects in the end result. Not just one object.
Edit1: Added __in. Once again I need only latest( as a result distinct) objects for each name.
Something like this.
my_id_list = [Foo.objects.filter(name=name).latest('id').id for name in ['foo','koo','too']]
Foo.objects.filter(id__in=my_id_list)
The above works. But I want a more concise way of doing it. Is it possible to do this in a single query/filter annotate combination?
you can try:
qs = Foo.objects.filter(name__in=['foo','koo','too'])
# Get list of max == last pk for your filter objects
max_pks = qs.annotate(mpk=Max('pk')).order_by().values_list('mpk', flat=True)
# after it filter your queryset by last pk
result = qs.filter(pk__in=max_pks)
If you are using PostgreSQL you can do the following
Foo.objects.order_by('name', '-id').distinct('name')
MySQL is more complicated since is lacks a DISTINCT ON clause. Here is the raw query that is very hard to force Django to generate from ORM function calls:
Foo.objects.raw("""
SELECT
*
FROM
`foo`
GROUP BY `foo`.`name`
ORDER BY `foo`.`name` ASC , `foo`.`id` DESC
""")

Query intermediate through fields in django

I have a simple Relation model, where a user can follow a tag just like stackoverflow.
class Relation(models.Model):
user = AutoOneToOneField(User)
follows_tag = models.ManyToManyField(Tag, blank=True, null=True, through='TagRelation')
class TagRelation(models.Model):
user = models.ForeignKey(Relation, on_delete=models.CASCADE)
following_tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
pub_date = models.DateTimeField(default=timezone.now)
class Meta:
unique_together = ['user', 'following_tag']
Now, to get the results of all the tags a user is following:
kakar = CustomUser.objects.get(email="kakar#gmail.com")
tags_following = kakar.relation.follows_tag.all()
This is fine.
But, to access intermediate fields I have to go through a big list of other queries. Suppose I want to display when the user started following a tag, I will have to do something like this:
kakar = CustomUser.objects.get(email="kakar#gmail.com")
kakar_relation = Relation.objects.get(user=kakar)
t1 = kakar.relation.follows_tag.all()[0]
kakar_t1_relation = TagRelation.objects.get(user=kakar_relation, following_tag=t1)
kakar_t1_relation.pub_date
As you can see, just to get the date I have to go through so much query. Is this the only way to get intermediate values, or this can be optimized? Also, I am not sure if this model design is the way to go, so if you have any recomendation or advice I would be very grateful. Thank you.
You need to use Double underscore i.e. ( __ ) for ForeignKey lookup,
Like this :
user_tags = TagRelation.objects.filter(user__user__email="kakar#gmail.com").values("following_tag__name", "pub_date")
If you need the name of the tag, you can use following_tag__name in the query and if you need id you can use following_tag__id.
And for that you need to iterate through the result of above query set, like this:
for items in user_tags:
print items['following_tag__name']
print items['pub_date']
One more thing,The key word values will return a list of dictionaries and you can iterate it through above method and if you are using values_list in the place of values, it will return a list of tuples. Read further from here .

Django: Filter a model with multiple parameters for a single ManyToManyField

I have following models.
class Contents (models.Model):
...
tags = models.ManyToManyField('Tag')
class Tag (models.Model):
...
name = models.CharField(max_length=20)
Just to consider, I am trying to get contents which are tagged with both tag1 and tag2.
Is there a way in Django to do something like Contents.objects.filter(tags__name = ['tag1','tag2'])
Here tag1,tag2,... are dynamically generated.
Update:
I have been using for loop. I am looking for an efficient solution.
If you're looking for a list, then you should be able to use the look up __in:
Contents.objects.filter(tags__name__in = ['tag1','tag2'])
See the docs.
For a queryset with both and only both tags, you may need to chain your filter calls:
tags = ['tag1','tag2']
contents = Contents.objects.all()
for tag in tags:
contents = contents.filter(tags__name = tag)
That should filter the queryset so that you only have a queryset where both tags match. The method copied from this quesiton: Django queryset to match all related objects

Django - sorting object based on user defined order in template

I want the user to be able to order a list of objects in a table using javascript. Then, in a django function I would like to sort those object based on the same ordering, not on an attribute.
Is it possible? I was thinking about passing a list of pk from the template to the view and then ordering the objects according to this list, but I have not found a way to do it yet.
I don't think this is possible with queryset. Try following:
pk_list = [2, 1, 3, 4]
pk2obj = {obj.pk: obj for obj in Model.objects.all()}
objects_ordered = [pk2obj[pk] for pk in pk_list]
pkg2obj is mapping between pk and model instance object. To make a dictionary I used dictionary comprehension.
If you want to omit deleted objects:
objects_ordered = [pk2obj[pk] for pk in pk_list if pk in pk2obj]
Else if you want to replace deleted objects with default value (None in following code):
objects_ordered = [pk2obj.get(pk, None) for pk in pk_list]
I've had to solve this exact problem before.
If you want the user to be able to reorder them into a user-defined order, you can easily define a field to store this order.
As you say, initially, you could serve them in order according to id or an upload_date DateTimeField. But you could also have an PositiveIntegerField in the model, named position or order, to represent the user-defined order.
class MediaItem(models.Model):
user = models.ForeignKey(User)
upload_date = models.DateTimeField(auto_now_add = True)
position = models.PositiveIntegerField()
Whenever a user changes the order on the frontend, the JS can send the new order as an array of objects (ie. new_order = [{"pk":3, "position":1}, {"pk":1, "position":2}, {"pk":2, "position":3}]). The view can look up each instance by pk, and change the position:
for obj in new_order:
media_item = MediaItem.objects.get(pk=obj['pk'])
media_item.position = obj['position']
media_item.save()
Then always query using
objects_ordered.objects.order_by('position')
That's how we managed to do it. If you have more specific questions regarding this approach, feel free to ask in the comments.
Edit:
If the same object can be a member of many different groups or lists, and you want to store the position of the membership within that list, you can achieve this using a through model. A through model is useful when you need to store data that relates to the relationship between two objects that are related. In addition to the MediaItem class shown above, this is what your other models would look like:
class Album(models.Model):
media_items = models.ManyToManyField(MediaItem,
related_name = 'album_media_items',
through = 'Membership')
class Membership(models.Model):
album = models.ForeignKey(Album,
related_name = 'album')
media_item = models.ForeignKey(MediaItem,
related_name = 'media_item')
date = models.DateTimeField(auto_now_add = True)
position = models.PositiveIntegerField()
Then, you could query the Membership instances, instead of the MediaItem instances.
# get id of list, or album...
alb = Album.objects.get(pk=id_of_album)
media_items = Membership.objects.filter(album=alb).order_by('position')
for item in media_items:
# return the items, or do whatever...
# keep in mind they are now in the user-defined order
You can do this:
pk_list = [1,5,3,9]
foo = Foo.objects.filter(id__in=pk_list)
#Order your QuerySet in base of your pk_list using Lambda
order_foo = sorted(foo, key = lambda:x , pk_list.index(x.pk))

Custom properties in a query

Given the simplified example below, how would I access my custom "current_status" property within a queryset? Is it even possible?
At the moment, I want to list the all the current Events and show the current status. I can get the property to display in a template ok, but I can't order the queryset by it. Alternatively, would I need to create a custom manager with some kind of nested "if" statement in the 'Select'?
class Event(models.Model):
....
date_registered = models.DateField(null=True, blank=True)
date_accepted = models.DateField(null=True, blank=True)
date_reported = models.DateField(null=True, blank=True)
...
def _get_current_status(self):
...
if self.date_reported:
return "Reported"
if self.date_accepted:
return "Accepted"
if self.date_registered:
return "Registered"
if self.date_drafted:
return "Drafted"
current_status = property(_get_current_status)
Instead of calculating the status as a property, create a proper model field for it and update it in the save method. Then you can use that field directly in the query.
You cannot use a custom property in query, since Django's ORM will try to map it to a database column and fail. Of course you can use it in an evaluated queryset, e.g. when you're iterating about the objects of a query's results!
You can only filter for things like: Event.objects.filter(date_drafted__isnull=False).
http://docs.djangoproject.com/en/dev/ref/models/querysets/#isnull
Thanks to Daniel. I think that I might use your approach. However, I also managed to get it working using the queryset 'extra' method, which might also be useful to other people, although its probably isn't database agnostic.
qs = Event.objects.extra(select={'current_status_id':
'''(CASE
WHEN date_cancelled THEN 0
WHEN date_closed THEN 6
WHEN date_signed_off THEN 5
WHEN date_reported THEN 4
WHEN date_accepted THEN 3
WHEN date_registered THEN 2
WHEN date_drafted THEN 1
ELSE 99
END)
'''})