django sort query result by occurrence count - django

I've got the flowing two models:
class Item(models.Model):
Name = models.CharField(max_length = 32)
class Profile(models.Model):
user = models.ForeignKey(User, unique = True)
ItemList = models.ManyToManyField(Item, related_name = "user_itemlist")
For Item X I want to get a list of Item objects present in ItemList for all Profile objects that contain X in ItemList, sorted by how many times each object appears.
The best I can do so far is:
Item.objects.filter(user_itemlist__in = User.objects.filter(profile__ItemList = X))
and this returns the list of all Item objects I need, with duplicates (if Item Z is present in ItemList for 10 Profile objects it will appear 10 times in the query result).
How can I sort the result of the above query by the number of times each object appears in the result and remove duplicates? Is there any "django" way to do that?

profiles = Profile.objects.filter(profile__ItemList=X)
Item.objects.filter(
user_itemlist__in=profiles
).annotate(itemcount=Count('id')).order_by('-itemcount')

if you're using django 1.0+ you can do this:
from django.db.models import Count
# note that 'profile' is an instance of Profile, not the model itself
sorted_items = profile.ItemList.annotate(itemcount=Count('name'))
sorted_items = sorted_items.order_by('-itemcount')
#number of occurences of top item
sorted_items[0].itemcount

Related

django saving list of foreignkey objects to m2m field w/ through model with ordering

I have a html table w/ javascript that allows ordering of rows which passes the track_id list and row order list through a form on POST.
I just added the class PlaylistTrack using through for the model so I can add ordering to the tracks.m2m field. The view I have below works before I added the through model, but now I am not sure how I should save a list of tracks w/ its associated order number since I can't use add() and I must use create(). How should I use create() in my view to save a list of track_id's and associate the order number w/ list? Can I use bulk_create?
models.py:
class Playlist(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1)
name = models.CharField(max_length=50)
tracks = models.ManyToManyField(Track, through='PlaylistTrack')
def __str__(self):
return self.name
class PlaylistTrack(models.Model):
track = models.ForeignKey(Track)
playlist = models.ForeignKey(Playlist)
order = models.PositiveIntegerField()
class Meta:
ordering = ['order']
views.py:
def add_track(request):
active_playlist = Playlist.objects.get(id=request.POST.get('playlist_id'))
add_tracks = request.POST.getlist('track_id')
if request.POST.get('playlist_id'):
active_playlist.tracks.add(*add_tracks) # how to use create or bulk_create?
return redirect("playlists:list_playlists")
Ozgur's answer has you mostly covered. However, you do NOT need to fetch Playlist or Track instances from the database and you CAN use bulk_create:
def add_track(request):
playlist_id = request.POST.get('playlist_id')
track_ids = enumerate(request.POST.getlist('track_id'), start=1)
PlaylistTrack.objects.bulk_create([
PlaylistTrack(playlist_id=playlist_id, track_id=track_id, order=i)
for i, track_id in track_ids
])
return redirect("playlists:list_playlists")
This reduces the entire procedure to a single db operation where you had (1 + 2n) operations before (n being the number of tracks).
You need to fetch Track objects that will be added:
Track.objects.filter(pk__in=add_tracks)
--
Since order field must be populated, you can't use .add() on M2M field. You gotta create objects by yourself:
def add_track(request):
playlist = Playlist.objects.get(id=request.POST.get('playlist_id'))
for i, track_id in enumerate(request.POST.getlist('track_id'), start=1):
track = Track.objects.get(pk=track_id)
PlaylistTrack.objects.create(track=track, playlist=playlist, order=i)
return redirect("playlists:list_playlists")

Django: How can I annotate on the Sum of values in a ForeignKey relationship?

I have two models, Item and Vote:
class Item(models.Model):
name=CharField(max_length=75)
class Vote(models.Model):
item = ForeignKey(Item)
value = IntegerField()
I want to provide a queryset with a list of items in order of the sum of the value of the votes. I'm trying:
items = Item.objects.annotate(Sum('votes__value')).order_by('votes__value')
but in the situation where I have two votes - one value = 0 and one value = 1 - I'm getting a queryset of two Item instances, for which item1 == item2!
How do I properly construct this queryset? Thanks!
Try this;
items = Item.objects.values('item').annotate(votes_value=Sum('votes')).order_by('votes')
or
items = Item.objects.values('item').annotate(votes_value=Sum('votes'))
Try something like:
Item.objects.annotate(votes_total=Sum('votes__value')).order_by('votes_total')

Custom SQL for Geodjango on ForignKey

I have a following model:
class UserProfile(models.Model):
user = models.OneToOneField(User)
location = models.PointField(blank=True, null=True, srid=CONSTANTS.SRID)
objects = models.GeoManager()
class Item(models.Model):
owner = models.ForeignKey(UserProfile)
objects = models.GeoManager()
Now I need to sort the Items by distance to some point:
p = Point(12.5807203, 50.1250706)
Item.objects.all().distance(p, field='owner__location')
But that throws me an error:
TypeError: ST_Distance output only available on GeometryFields.
From GeoDjango GeoQuerySet.distance() results in 'ST_Distance output only available on GeometryFields' when specifying a reverse relationship in field_name I can see there is already ticket for this.
Now I don't like the solution proposed in that question since that way I would not get the distance and I would lose the distances.
So I was thinking that I could achieve this by making a custom sql query. I know that this:
UserProfile.objects.distance(p)
will produce something like this:
SELECT (ST_distance_sphere("core_userprofile"."location",ST_GeomFromEWKB('\x0101000020e6100000223fd12b5429294076583c5002104940'::bytea))) AS "distance", "core_userprofile"."id", "core_userprofile"."user_id", "core_userprofile"."verified", "core_userprofile"."avatar_custom", "core_userprofile"."city", "core_userprofile"."location", "core_userprofile"."bio" FROM "core_userprofile"
So my question is: is there some easy way how to manually construct such query that would sort items by distance?
Since the geometry you're measuring distance to is on UserProfile, it makes sense to query for UserProfile objects and then handle each Item object they own. (The distance is the same for all items owned by a profile.)
For example:
all_profiles = UserProfile.objects.all()
for profile in all_profiles.distance(p).order_by('distance'):
for item in profile.item_set.all():
process(item, profile.distance)
You may be able to make this more efficient with prefetch_related:
all_profiles = UserProfile.objects.all()
all_profiles = all_profiles.prefetch_related('item_set') # we'll need these
for profile in all_profiles.distance(p).order_by('distance'):
for item in profile.item_set.all(): # items already prefetched
process(item, profile.distance)
If it's important for some reason to query directly for Item objects, try using extra:
items = Item.objects.all()
items = items.select_related('owner')
distance_select = "st_distance_sphere(core_userprofile.location, ST_GeomFromEWKT('%s'))" % p.wkt
items = items.extra({'distance': distance_select})
items = items.order_by('distance')
Raw queries are another option, which let you get model objects from a raw SQL query:
items = Item.objects.raw("SELECT core_item.* FROM core_item JOIN core_userprofile ...")

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))

Django - Get items in many sets

My models:
class ItemSet(models.Model):
name = models.CharField(max_length=30)
item = models.ManyToManyField(Item)
order = models.IntegerField(default=0)
class Item(models.Model):
name = models.CharField(max_length=30)
desc = models.CharField(max_length=100)
A set includes many items and a item can be in many sets.
So, how to get a list of items when we know the id of a item in some sets but itself?
Please give me some codes. Thank you very much!
Example:
We have two sets like this:
(1,2,3,4) and (2,3,5,7,9),
id = 3 then result = (1,2,4,5,7,9). Note: result does not include 3.
Get all Items in a specific ItemSet:
set = ItemSet.objects.get(id=99)
items = set.item.all()
Get all ItemSets containing a specific Item:
item = Item.objects.get(id=88)
sets = item.itemset_set.all()
For more info - read the docs.
If I understand your question correctly, you want a distinct set of all items from all ItemSets which contain a specific Item, excluding the Item itself from the returned set. Does that sound about right?
Edit: Tested.
class ItemSet(models.Model):
name = models.CharField(max_length=30)
items = models.ManyToManyField('Item', related_name='item_sets')
order = models.IntegerField(default=0)
class Item(models.Model):
name = models.CharField(max_length=30)
desc = models.CharField(max_length=100)
# First, get all sets containing the item we're interested in:
item_sets = ItemSet.objects.filter(items__pk=item.pk)
# Next, get all items belonging to those sets, excluding the item we're interested in:
items = Item.objects.filter(item_sets__pk__in=item_sets).exclude(pk=item.pk).distinct()
Note: This actually executes a single query (using Django 1.2.1), though, this might depend on your database backend. You can examine the generated SQL like so:
>>> from django.db import connection
>>> items._as_sql(connection)
...