Database query across django ManyToManyField - python

I'd like to find how to select all objects whose ManyToMany field contains another object. I have the following models (stripped down)
class Category(models.Model):
pass
class Picture(models.Model):
categories = models.ManyToManyField(Category)
visible = models.BooleanField()
I need a function to select all the Pictures in one or more Categories:
def pics_in_cats(cat_ids=()):
pass
BUT it needs to return a QuerySet if possible so that I can do something like:
pics_in_cats((1,2,3)).filter(visible=True)
It could be done by loading all the relevant Category objects and merging their picture_set attributes, but that seems inefficient. I'd also like to avoid falling back to raw SQL if possible.
Thanks in advance

Why write a custom function and not use something like this? (untested)
pics = Picture.objects.filter(categories__in = [1,2,3]).filter(visible=True)

Related

How to join ManyToMany querysets in Django/Python

For example, I have 2 models:
class User(AbstractUser):
achievement = models.ManyToManyField('Achievement')
class Achievement(models.Model):
name = models.CharField(max_length=50)
I need get all achievements of several users, i think, it should be something like this:
for user in User.objects.filter(bla-bla):
achievement_list += user.achievement
But it returns error "'ManyRelatedManager' object is not iterable"
user.achievement is a ManyRelatedManager, similar to how, say, Achievement.objects is a Manager. Try instead user.achievement.all() or user.achievement.filter(...) (with whatever filter you may want).

django return foreign key object from queryset?

So I have three models
class Post(....
class Project(....
# have a many to many relationship
class ProjectPost(....
post = ..... # foreignkey
project = .... # foreignkey
The data set I want to select is a list of Post objects given a Project object.
This is what I tried:
posts_list = ProjectPost.objects.filter(project=project_object).select_related("post")
But this returns a list of ProjectPost objects rather than a list of Post objects. What is the correct way of doing this?
You may want to use the ManyToManyField()
https://docs.djangoproject.com/en/dev/topics/db/examples/many_to_many/
You should do it like this:
class Post(models.Model):
pass
class Project(models.Model):
posts = models.ManyToManyField(Post)
And then, if you want to access the Posts of a Project, you can do
project_obj.posts.all()
You can use all the Queryset methods
If you want to access the projects of a posts you can do
post_obj.project_set.all()
Same as before, you can use all the Queryset methods.
If for any reason you want to do it that way, you could do:
post_list = ProjectPost.objects.filter(project=project_object).values('post')
Came across this problem myself recently and this was how I solved it. Would love it if someone could comment on whether my solution is efficient.
project_posts = ProjectPost.objects.filter(project=project_object).select_related("post")
posts_lists = map(lambda post: project.post, project_posts)
Objects in posts_lists are now of the correct type.

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.

How do you join all models that reference a model into one table in Django?

I have multiple Django models that reference a model using foreign keys as such:
class LocationHistory(models.Model):
region = models.ForeignKey(WorldGrid)
...
class UserSynthVals(models.Model):
region = models.ForeignKey(WorldGrid)
...
class RegionalVictoryRate(models.Model):
region = models.ForeignKey(WorldGrid)
...
Where WorldGrid is just:
class WorldGrid(models.Model):
latitude = models.FloatField()
longitude = models.FloatField()
...
Now, I am able to get all the models that reference WorldGrid (abstractly for reusability) with:
models_that_reference = [i.model for i in get_model('appname',model_name)._meta.get_all_related_objects()]
And at that point I can loop through them getting their model.objects.all().values(), but I cannot find a way to join those separate lists of objects into one that I could then output to a table.
I would rather do something more along the lines of django's expected use (like what select_related() does) and keep this abstract.
Any help solving this problem would be appreciated, or a new direction to try.
I think you can do this with filters. You want to join WorldGrid to its related models and filter out where there isn't a join.
WorldGrid.objects.filter( regionalvictoryrate_region__isnull=False,
usersynthvals_region__isnull=False,
locationhistory_region__isnull=False )
See the notes here.
https://docs.djangoproject.com/en/dev/topics/db/queries/
In the past I have modified and play with the get_deleted_objects function from the admin, in django.contrib.admin.util to do just that, return all the related objects and display them or manipulate them in some form. It could be a good start to accomplish what you want.
I was able to find a pretty good way of doing this, and the select_related was indeed the key. Once I have a list of the referencing models, I can do:
fields_that_reference = [[m._meta.object_name.lower()+'__'+f.name for f in m._meta.fields if not isinstance(f,related.ForeignKey) ] for m in models_that_reference]
for i in fields_that_reference:
fields +=i
To get all of the fields that I am trying to see at once, and then:
all_objects = get_model('appname',model_name).objects.select_related().values(*fields)
To get them all into one giant list. If your models are historic (with datetime fields), or have circular foreignkeys, you will probably need to prune this result a little more before displaying it, but this gets everything together nicely.

Create an object before calling the save method

I want to create an object in Django before calling the save method. This object will be created from a ForeignKey Value, I've changed the foreignkey field to look like an input field in order to write a value instead of selecting it.
I have 2 classes in 2 different model files
class Category(models.Model):
title = models.ForeignKey(Title, verbose_name="Title")
and
class Title(models.Model):
title = models.CharField("Title", primary_key=True, max_length=200)
When I create a category, I have to pick or write a title that already exists in the database and when I try to create a category with a new title I get this error :
Select a valid choice. That choice is not one of the available choices.
What I want to do is creating a title based on what I write in the ForeignKey field before creating the category so it can be used immediately.
I tried to redefine the save method to save the title object before saving the category but it didn't work.
Any help will be really appreciated.
Thank you
The save is performed after the form validation, you can make the category obj creation during the validation.
Have a look at the form fields' clean methods that you can override on django docs http://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other
Thank you for your code, I've just tested it. But it's not exactly what I'm looking for, I will explain what I want to do.
Let's say that we have Category and Article classes in our model, each one has a title. To make this title reusable, I created another application that will manage fields, I created the class Title and I added it as foreignkey to Category and Article forms.
I switched the select box to an input field using raw_id_fields.
Now, when I create a category or an article, I have to select or write a title, when this title exists, it works perfectly but when it doesn't exist I want to create it before creating the category so it can use it.
I tried to do that in the save method, in the pre_save signal and in the clean method but I always get the error "Select a valid choice. That choice is not one of the available choices."
I'm using a hard coded solution to create the title now, I want just to see if it will work, these are the lines that I inserted in the different methods to create the title before creating the category :
t = Title(title = "MyTitle")
t.save()
I tried to create a Category with MyTitle as title but I get the same error, when I try to create another one using an existing title, it works and the title "MyTitle" is created. That's mean that the creation of the object happens after the form verification. What I want is just doing this before. The title object should be created before the verification.
Thank you very much for your help
You should probably consider putting the code to create Category entries in the model's manager:
class CategoryManager(Manager):
def create_category(category, title):
t = Title.objects.get_or_create(title=title)
return self.create(title=t)
class Category(models.Model):
title = models.ForeignKey(Title, verbose_name="Title")
objects = CategoryManager()
Then use create_category every time you want to create a Category.

Categories