Django select_related() and GenericForeignKey - python

I have models like these:
class Comment(models.Model):
text = models.TextField(max_length = 250, blank = False)
author = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
class Product(models.Model):
name = models.CharField(max_length = 40)
comments = generic.GenericRelation(Comment)
In this template I show the latest 5 comments of all products:
<ul>
{% for comment in last_comments %}
<li>{{ comment.author }} on {{ comment.content_object }}
<br>{{ comment.text }}</li>
{% endfor %}
</ul>
If I get last_comments with last_comments = Comment.objects.all().order_by('-id')[:5] django debug toolbar says was performed 25 queries.
If I get last_comments with last_comments = Comment.objects.select_related().all().order_by('-id')[:5] django debug toolbar says was performed 20 queries.
But why select_related doesn't select the related content_object also? In django debug toolbar I see 5 queries for getting the product. And are certainly the consequence of {{ comment.content_object }}
Probably the reason is because I use GenericForeignKey in Comment model.
Have you ideas about it?

You could try to refactor your database to look somewhat like this:
class Comment(models.Model):
...
content_object = models.ForeignKey(Content)
class Content(models.Model):
text = models.CharField(max_length=123)
class SomeSpecificContent(models.Model):
...
content = models.ForeignKey(Content)
class OtherSpecificContent(models.Model):
...
content = models.ForeignKey(Content)
which in case of Django would actually be a very similar schema to that of:
class Comment(models.Model):
...
content_object = models.ForeignKey(Content)
class Content(models.Model):
text = models.TextField()
class SomeSpecificContent(Content):
...
class OtherSpecificContent(Content):
...
as that is basically how the inheritence is handled in Django. The latter is probably less flexible and might be a little bit difficult to understand in case where SomeSpecificContent and OtherSpecificContent in fact represent totally different concepts.
Generic relations on the other hand cannot be handled efficiently exactly for this reason that they can link with whatever table you want them to. Therefore if you have a list of 5 objects it might happen, that each one of them is related to a different type of entity. Not sure how Django handles the case where 100 objects are related to 5 types of entities. Does it in fact generate 5+1 queries?

Related

Django how to add query results foreign key model to queryset

How can I get the Author model objects into a queryset through the foreign key of B model? I would like to use the Author objects in my template.
#Models.py
class Book(models.Model):
name = models.CharField(max_length=5)
class Author(models.Model):
# lots of fields
class B(models.Model):
author = models.ForeignKey(Author)
book = models.ForeignKey(Book)
selected_book = "ABC"
book = Book.objects.get(name=selected_book)
original_queryset = B.objects.filter(name=book)
for i in original_queryset:
print(i.author) # returns what i want
queryset = # get all i.author objects somehow
return render(request,"template.html", {"queryset": queryset}
Remove the for loop. then add the following line after the original_queryset = B.objects.filter(name=book) line->
queryset = original_queryset.author
If this doesn't work then let me know. I hope I can help you.
You can rename your B model's author field to authors, that makes your code more meaningful because ForeignKey field can store many data. In your code, you have made your B model's author field as a ForeignKey field, which means books can have multiple authors. You can see the django documentation of ForeignKey reference.
If you change as I told you then don't forget to run migration(python manage.py makemigrations and python manage.py migrate command in your cmd) and change the line queryset = original_queryset.author to queryset = original_queryset.authors
Another approach
You can pass the original_queryset in the context of the template,(i.e: {'queryset':original_queryset }) then in your template.html, you can add tages like this:
{% for book in queryset %}
<ul>
{% for author in book.author %}
<li>author.name</li>
....
{% endfor %}
</ul>
{% endfor %}
By doing these, you can place your books and authors in your template nicely. If it still shows error, message me, I hope I can help you.

Django prevent duplicate queries after prefetch related

I'm working on a fairly simple library project in Django, here are my models, a book can have many copies and each copy can have many loans.
class Author(models.Model):
name = models.CharField(max_length=200, unique=True)
class Book(TimeStampedModel):
isbn = models.CharField(max_length=13, primary_key=True)
title = models.CharField(max_length=200, db_index=True, unique=True)
authors = models.ManyToManyField('Author', related_name='books')
#property
def is_available(self):
"""Returns True if the Book has any available copies"""
return self.copies.exclude(loans__returned=False).exists()
class BookCopy(models.Model):
book = models.ForeignKey('Book', related_name='copies')
class Loan(models.Model):
start_date = models.DateField()
end_date = models.DateField()
returned = models.BooleanField(default=False)
customer = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True,
null=True, related_name='loans')
book_copy = models.ForeignKey(
'BookCopy', on_delete=models.CASCADE, related_name='loans')
I have a fairly simple view containing a list of books
def book_list(request):
book_list = Book.objects.prefetch_related('authors', 'copies__loans')
return render(request, 'books/book_list.html', {'books': books})
To figure out if a book is available I've written the property is_available inside the Book model. However when I call this property inside my template with the following:
{% for book in books %}
{% if not book.is_available %}
-- Template stuff --
{% endif %}
{% endfor %}
According to Django Debug Toolbar I end up with a duplicate query for every Book in the queryset
Reading the Django documentation for prefetch related there's a section discribing the behaviour which I think may be the culprit
Remember that, as always with QuerySets, any subsequent chained methods which imply a different database query will ignore previously cached results, and retrieve data using a fresh database query.
How would I prevent these duplicate queries from occuring?

Issues with foreign key relationship

I have defined a relationship between two models of different app using foreign key and data is inserted into Db accurately with foreign key instance, but I want to fetch that data using Django ORM but I didn't get it, I have googled this issue and also checked stack-overflow questions but still my issues are not solved.
#models.py
class teamInfo(models.Model):
ownerID = models.IntegerField()
teamName = models.CharField(max_length=50)
def __unicode__(self):
return unicode(self.id)
class gameWorld(models.Model):
team = models.ForeignKey(teamInfo)
w = models.IntegerField(null=True)
l = models.IntegerField(null=True)
def __unicode__(self):
return unicode(self.id)
I have tried a few things in my views but nothing worked for me. Here is the latest thing I have tried in my views:
def teamStandings(request,template=None,context=None):
getAllTeamStat = gameWorld.objects.all()
for i in getAllTeamStat.teaminfo_set.select_related() :
raise Exception(i.teaminfo.teamName)
I simply want a Django ORM query which fetches data from both models so I can display the team name in templates
See here and try that:
class TeamStandingsView(ListView):
model = gameWorld
template = # Some template path here!
context_object_name = "games"
in template:
{% for game in games %}
{{ game.team.teamName }}
{% endfor %}
edit: this should work:
def teamStandings(request,template=None,context=None):
getAllTeamStat=gameWorld.objects.all()
for team in getAllTeamStat:
for teaminfo in team.teaminfo_set.all():
print teaminfo.teamName

Trouble handling generic relationship in django

I want to model a situation and I´m having real trouble handling it. The domain is like this: There are Posts, and every post has to be associated one to one with a MediaContent. MediaContent can be a picture or a video (for now, maybe music later). So, what I have is:
mediacontents/models.py
class MediaContent(models.Model):
uploader = models.ForeignKey(User)
title = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
def draw_item(self):
pass
class Meta:
abstract = True
class Picture(MediaContent):
picture = models.ImageField(upload_to='pictures')
class Video(MediaContent):
identifier = models.CharField(max_length=30) #youtube id
posts/models.py
class Post(models.Model):
...
# link to MediaContent
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
media_content = generic.GenericForeignKey('content_type', 'object_id')
What i eventually want to do, is beeing able to call methods like:
post1.media_content.draw_item()
>> <iframe src="youtube.com" ...>
post2.media_content.draw_item()
>> <img src="..."/>
Is this the correct aproach, does it work? Can the template be agnostic of the object underneath?
Your approach looks good to me. You just need to override the draw_item method in your Picture and Video models. Your template will look something like
{% for post in posts %}
{{ post.media_content.draw_item }}
{% endfor %}
and it doesn't matter which model the generic foreign key points to, as long as it has a draw_item method defined.

Django ManyToMany Template Questions

Good Morning All,
I've been a PHP programmer for quite some time, but I've felt the need to move more towards the Python direction and what's better than playing around with Django.
While in the process, I'm come to a stopping point where I know there is an easy solution, but I'm just missing it - How do I display manytomany relationships in a Django Template?
My Django Model: (most of the fields have been removed)
class Category(models.Model):
name = models.CharField(max_length=125)
slug = models.SlugField()
categories = models.ManyToManyField(Category, blank=True, null=True)
class Recipe(models.Model):
title = models.CharField('Title', max_length=250)
slug = models.SlugField()
class Photo(models.Model):
recipe = models.ForeignKey(Recipe)
image = models.ImageField(upload_to="images/recipes", blank=True)
So, there is the basic models I'm using in my application called "recipes."
With that said, there are two questions I'm looking for answers on:
How would I go about displaying the categories for a recipe on it's details page?
How would I go about displaying the image for the recipe on it's details page?
If I go into the Python shell, and input the following, I do get a result:
>>> photos = Photo.objects.filter(recipe=1)
>>> photos
[<Photo: Awesome Pasta>]
>>> for photo in photos:
... print "Photo: %s" % photo.logo
...
Photo: images/recipes/2550298482_46729d51af__.jpg
But when I try something like the following in my template, I get an error saying "Invalid block tag: 'photo.image'."
{% for photo in photos %}
{% photo.image %}
{% endfor %}
Although, even if that did work, the ID is still hard coded into the view, how would you go about having this dynamic for each recipe?
Details Page View.py snippet:
def details(request, slug='0'):
p = get_object_or_404(Recipe, slug=slug)
photos = Photo.objects.filter(recipe=1)
return render_to_response('recipes/recipes_detail.html', {'p': p, 'photos': photos})
Thanks in advance for the help and understanding for what is probably a very simple question to all of you!
UPDATE: When removing the additional fields in the models, I forgot the categories field for the Recipes model.
From what I can see, I think you've got a small syntax error:
{% photo.image %}
should instead be:
{{ photo.image }}
The {% %} notation is used for django template tags. Variables, on the other hand, are expressed with the {{ }} notation.
To make it dynamic, you can take advantage of the fact that your Photo model has a foreign key to Recipe. This means that there will be a reverse relation from the Recipe instance you've loaded using the slug back to the set of photos:
def details(request, slug='0'):
p = get_object_or_404(Recipe, slug=slug)
photos = p.photo_set.all()
Hopefully that will work for you. Glad to see you're enjoying working with Django!

Categories