Django Annotate Count - python

Could someone help me to understand why func Count calculates 1 for the actor that does not have any publicated scene?:
actors = Actor.objects.filter(state=Actor.State.PUBLISHED)\
.annotate(scenes_cnt=Count('scenes', filter=Q(state=Scene.State.PUBLISHED)))
I have one actor who has only one scene with state=Scene.State.PREVIEW but the code above calculates scenes_cnt=1 for this actor. I'm confused. Thanks in advance!
I try to calculate publicated scenes for actors. Expect to get scenes_cnt=0 if actor does not have any scene with state=Actor.State.PUBLISHED

Here's the abstract model for models that have State field:
class StatedModel(models.Model):
class Meta:
abstract = True
class State(models.TextChoices):
PREVIEW = 'PR'
COMMING = 'CM'
PUBLISHED = 'PB'
state = models.CharField(max_length=2, choices=State.choices, default=State.PREVIEW)

Here's what I got for different requests:
scenes = Scene.objects.filter(actors__id=331).values('id')
>>> [{"id": 444}]
scenes = Scene.objects.filter(actors__id=331, state=Scene.State.PUBLISHED).values('id')
>>> []
actors = Actor.objects.filter(id=331).annotate(scenes_cnt=Count('scenes', filter=Q(state=Scene.State.PUBLISHED))).values('id', 'scenes_cnt')
>>> [{"id": 331, "scenes_cnt": 1}]
actors = Actor.objects.filter(id=331, scenes__state=Scene.State.PUBLISHED).values('id', 'scenes__id')
>>> []
actors = Actor.objects.filter(id=331, scenes__state=Scene.State.PREVIEW).values('id', 'scenes__id')
>>> [{"id": 331, "scenes__id": 444}]
So, the main question why scenes_cnt = 1 ??

Related

Check if object has relation with other in ManyToMany field and aggregate field

I have a couple models as follows:
class Student(models.Model):
name = models.CharField()
classes = models.ManyToManyField(Class, related_name="students")
class Class(models.Model):
nombre = models.CharField
What I need is a way such that, when querying students in certain class, instead of just returnig the students enrolled in that class, it returns a QuerySet of all the students, each one with an aggregated field indicating if such student is enrolled in that class, e.g.:
[{'name':'student A', 'enrolled_in_physics':True}, {'name':'student B', 'enrolled_in_physics':False}]
I think it can be achieved through F() expressions along with ExpressionWrapper, but have no idea of how implement them; additionaly, the documentation and the examples are not very noob-friendly. Any help is appreciated, thanks!.
EDIT: Ok, I think using the word "list" is not the correct one, I need a normal QuerySet, such that let me do something like this:
student_a = query[0]
student_a.name
>>>'A'
student_a.enrolled_in_physics
>>>True
UPDATED
You could try defining a method within the student class:
class Student(models.Model):
name = models.CharField()
classes = models.ManyToManyField(Class, related_name="students")
def enrolled_in_class(self,class_name):
if len(Class.objects.filter(nombre=class_name,students__in=self)) > 0:
return True
else:
return False
students = Student.objects.all()
student_a = students[0]
student_a.name
student_a.enrolled_in_class('physics')
Original Answer:
Edit: Sorry misread question, I'll try and get an answer together for what you actually wanted: A list of all students, and binary true false if they are enrolled in a specific class.
Alright, I'm reading this as a:
I need a query that returns a list of dictionaries, indicating the student, and enrollment in a class.
#Assume: class_name = 'physics'
def get_class_list(class_name):
filtered_class = Class.object.get(nombre=class_name)
student_names = Students.objects.filter(classes__contains=filtered_class).values_list('name',flat=True)
class_list = []
for name in student_names:
class_list = {}
class_list['name'] = name
enrolled_class_string = 'enrolled_in_' + class_name
class_list[enrolled_class_string] = True
return class_list
This will give you a list of dictionaries with the keys named 'name', and 'class__name'

objects.all() not returning any objects in Django

I have the following models in models.py:
from django.contrib.auth.models import User
from django.db import models
class Usertypes(models.Model):
user = models.OneToOneField(User)
usertype = models.TextField()
def __unicode__(self):
return self.user_name
class Games(models.Model):
name = models.CharField(max_length=100,unique=True)
category = models.CharField(max_length=100)
url = models.URLField()
developer = models.ForeignKey(User)
price = models.FloatField()
def __unicode__(self):
return self.name
class Scores(models.Model):
game = models.ForeignKey(Games)
player = models.ForeignKey(User)
registration_date = models.DateField(auto_now=False, auto_now_add=False)
highest_score = models.PositiveIntegerField(null=True,blank=True)
most_recent_score = models.PositiveIntegerField(null=True,blank=True)
def __unicode__(self):
return self.most_recent_score
I am now creating objects of all 3 types. I have created some User and Games objects earlier, so when I run the following commands, the outputs as given below are obtained:
>>> u = User.objects.all()
>>> u.count()
5
>>> g = Games.objects.all()
>>> g.count()
9
Now, I am trying to create some Scores objects using the following commands. The outputs are given below:
>>> fifa = Games.objects.get(pk=18)
>>> user1 = User.objects.get(id=2)
>>> user2 = User.objects.get(id=7)
>>> user3 = User.objects.get(id=9)
>>> p1 = Scores(game=fifa,player=user1,registration_date='2015-01-29')
>>> p2 = Scores(game=fifa,player=user2,registration_date='2014-12-21')
>>> p3 = Scores(game=fifa,player=user3,registration_date='2015-01-29')
>>> user1
<User: admin>
>>> fifa
<Games: Games object>
>>> p1
<Scores: Scores object>
>>> p2
<Scores: Scores object>
>>> p3
<Scores: Scores object>
The problem is:
>>> s = Scores.objects.all()
>>> s.count()
0
I don't understand why, despite having created 3 Scores objects, Scores.objects.all() returns nothing. Can someone help? Thanks in advance!!
You never inserted the scores to the database (so of course the database can't give them back to you):
p1.save() # This will save the object to the database
Note that you could also use Scores.objects.create(...) (instead of Score()). This will initialize an object and immediately save it to the database:
p1 = Scores.objects.create(game=fifa,player=user1,registration_date='2015-01-29')
Now these methods would result in three queries being made to the database, which isn't ideal (you hit the roundtrip latency 3 times).
Fortunately, you can easily optimize and make a single query using bulk_create:
Scores.objects.bulk_create([
Scores(game=fifa,player=user1,registration_date='2015-01-29')
Scores(game=fifa,player=user2,registration_date='2014-12-21')
Scores(game=fifa,player=user3,registration_date='2015-01-29')
])
Make sure you are running .save(), which actually saves the item in the database. Without it, you aren't saving anything in the database therefore you get nothing when you retrieve objects from the database

Find related objects and display relation

I am using django-follow to allow users to "follow" objects - in this example, Actors in films.
I am pulling back a list of film actors using
actors_user_is_following = Follow.objects.get_follows(Actor).filter(user=request.user.id)
But what I also want to do is suggest films to the user based on the actors they are following. This does not need to be a complex algorithm of what they already like and suggesting relative films, just a simple "because you follow this actor and this actor is in this film, suggest it to the user"
I have this rather clunky way of doing this right now...
context['follows'] = {
'actors': Follow.objects.get_follows(Actor).filter(user=request.user.id),
'genres': Follow.objects.get_follows(Genre).filter(user=request.user.id),
}
actor_ids = []
for actor in context['follows']['actors']:
actor_ids.append(actor.target_artist_id)
genre_ids = []
for artist in context['follows']['genres']:
genre_ids.append(artist.genre_ids)
context['suggested'] = {
'films': Listing.objects.filter(Q(actors__in=actor_ids) | Q(genres__in=genre_ids))
}
Which works, but I'm sure there is a better way of doing it?
Most importantly I also want to show the user why that film as been recommended by displaying the actors or genres it features that the user is following, so the end result might be something like...
film = {
title: 'Dodgeball'
image: '/images/films/dodgeball.jpg'
followed_actors: ['Ben Stiller', 'Vince Vaughn'] #could be multiple
followed_genres: ['Comedy'] #could be multiple
}
Note I would want to return multiple films.
Here's how my models are coded up:
Film Model defined like so:
from django.db import models
from app.actors.models import Actor
from app.genres.models import Genre
class Film(models.Model):
title = models.CharField(max_length=255)
strapline = models.CharField(max_length=255)
slug = models.SlugField(max_length=100)
image_url = models.CharField(max_length=255)
pub_date = models.DateTimeField('date published')
actors = models.ManyToManyField(Actor)
genres = models.ManyToManyField(Genre)
def __unicode__(self):
return self.title
And Actor Model:
from django.db import models
from follow import utils
class Actor(models.Model):
title = models.CharField(max_length=255)
strapline = models.CharField(max_length=255)
image = models.CharField(max_length=255)
image_hero = models.CharField(max_length=255)
bio = models.TextField()
def __unicode__(self):
return self.title
#followable
utils.register(Actor)
Behind the scenes, Follow objects are essentially a many-to-many relationship with fields added each time you register a model.
Your question just talks about actors, but your code also includes genres. It's not especially hard to cover both, I'm just not sure which way is the way you want it.
I think you can get your film objects in one queryset:
films = Film.objects.filter(Q(actors__in=Actor.objects.filter(follow_set__user=request.user)) |
Q(genres__in=Genre.objects.filter(follow_set__user=request.user))).distinct()
As noted in the docs for __in lookups, some database back ends will give you better performance if you evaluate the subqueries before using them:
actor_ids = list(Actor.objects.filter(follow_set__user=request.user).values_list('id', flat=True))
genre_ids = list(Genre.objects.filter(follow_set__user=request.user).values_list('id', flat=True))
films = Film.objects.filter(Q(actors__in=actor_ids) | Q(genres__in=genre_ids)).distinct()
If you just want to return the matching films, I think those are the most concise way to express it.
For the part where you're adding the reasons to the films - I don't see a more elegant way to handle that than to iterate through the films queryset and add the information by hand. I would definitely define the querysets for actor_ids and genre_ids before doing so, although whether or not I evaluated them early would still depend on the db back end.
annotated_films = []
for film in films:
film.followed_actors = film.actors.filter(id__in=actor_ids)
film.followed_genres = film.genres.filter(id__in=genre_ids)
annotated_films.append(film)

How do you use factory_boy to model a MongoEngine EmbeddedDocument?

I'm trying to use factory_boy to help generate some MongoEngine documents for my tests. I'm having trouble defining EmbeddedDocumentField objects.
Here's my MongoEngine Document:
class Comment(EmbeddedDocument):
content = StringField()
name = StringField(max_length=120)
class Post(Document):
title = StringField(required=True)
tags = ListField(StringField(), required=True)
comments = ListField(EmbeddedDocumentField(Comment))
Here's my partially completed factory_boy Factory:
class CommentFactory(factory.Factory):
FACTORY_FOR = Comment
content = "Platinum coins worth a trillion dollars are great"
name = "John Doe"
class BlogFactory(factory.Factory):
FACTORY_FOR = Blog
title = "On Using MongoEngine with factory_boy"
tags = ['python', 'mongoengine', 'factory-boy', 'django']
comments = [factory.SubFactory(CommentFactory)] # this doesn't work
Any ideas how to specify the comments field? The problem is that factory-boy attempts to create the Comment EmbeddedDocument.
I'm not sure if this is what you want but I just started looking at this problem and this seems to work:
from mongoengine import EmbeddedDocument, Document, StringField, ListField, EmbeddedDocumentField
import factory
class Comment(EmbeddedDocument):
content = StringField()
name = StringField(max_length=120)
class Post(Document):
title = StringField(required=True)
tags = ListField(StringField(), required=True)
comments = ListField(EmbeddedDocumentField(Comment))
class CommentFactory(factory.Factory):
FACTORY_FOR = Comment
content = "Platinum coins worth a trillion dollars are great"
name = "John Doe"
class PostFactory(factory.Factory):
FACTORY_FOR = Post
title = "On Using MongoEngine with factory_boy"
tags = ['python', 'mongoengine', 'factory-boy', 'django']
comments = factory.LazyAttribute(lambda a: [CommentFactory()])
>>> b = PostFactory()
>>> b.comments[0].content
'Platinum coins worth a trillion dollars are great'
I wouldn't be surprised if I'm missing something though.
The way that I'm doing it right now is to prevent the Factories based on EmbeddedDocuments from building. So, I've setup up an EmbeddedDocumentFactory, like so:
class EmbeddedDocumentFactory(factory.Factory):
ABSTRACT_FACTORY = True
#classmethod
def _prepare(cls, create, **kwargs):
return super(EmbeddedDocumentFactory, cls)._prepare(False, **kwargs)
Then I inherit from that to create factories for EmbeddedDocuments:
class CommentFactory(EmbeddedDocumentFactory):
FACTORY_FOR = Comment
content = "Platinum coins worth a trillion dollars are great"
name = "John Doe"
This may not be the best solution, so I'll wait on someone else to respond before accepting this as the answer.

Django: Saving multiple ManyToMany fields within a transaction

this is the representation of my models:
class B(models.Model):
"""I'm a dummy model, so doesn't pay atention of what I do"""
name = models.CharField(max_length=250)
class A(models.Model):
name = models.CharField(max_length=250)
many_b = models.ManyToManyField(B)
Now, suppose I have a list of B objects. And a single A object that will be related to that Bs. Something like this:
a = A.objects.get(id=1)
list_of_b = [B<name='B1'>,B<name='B2'>,B<name='B3'>,]
The way I relate them now is this:
for b_object in list_of_b:
a.many_b.add(b_object)
Is there any way to add all the B objects in a single transaction? Maybe in a single method, like:
a.many_b.addList(b) #This doesn't exist
From the docs:
>>> john = Author.objects.create(name="John")
>>> paul = Author.objects.create(name="Paul")
>>> george = Author.objects.create(name="George")
>>> ringo = Author.objects.create(name="Ringo")
>>> entry.authors.add(john, paul, george, ringo)
So if you have a list, use argument expansion:
a.many_b.add(*list_of_b)
I guess what you want is a kind of bulk insert right?
As far as I know this is just available in the Django TRUNK not in 1.3!
check it out some tutorial:
http://www.caktusgroup.com/blog/2011/09/20/bulk-inserts-django/

Categories

Resources