Django prevent duplicate queries after prefetch related - python

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?

Related

How to display ForeignKey Data in Django Template

I have relation of Project between Tags and a Project can have multiple tags, But I am unable to display the tags data in my template, I am trying to display data according to tag slug. But I ma getting error, Please let me know how I can display Tags data in my Template.
Here is my urls.py file...
path('tag/<tag_slug>', views.projecttag, name='projecttag'),
here is my `models.py file...
class Tags(models.Model):
project = models.ForeignKey(Project, null=True, blank=True, related_name='ProjectTags', on_delete=models.CASCADE)
tag_name = models.CharField(max_length=64, null=True, blank=True)
tag_slug = models.SlugField(max_length=64, null=True, blank=True)
here is my views.py file...
def projecttag(request, tag_slug):
tag = get_object_or_404(Tags, tag_slug=tag_slug)
project = Project.objects.filter(ProjectTags=tag)
context = {'tag':tag, 'project':project}
template_name = 'pages/tags.html'
return render(request, template_name, context)
here is my tags.html file...
{% for property in project %}
<div class="single-property-box">
{{property.name}}
</div>
{% endfor %
According to my understandings, you want to display tags that are connected with your 'project' class. As you explain that a project can have multiple tags, so the 'ProjectTags' field from your 'Project' class must be multi-valued. But when you are filtering Projects in projecttag view, you are just checking ProjectTags=tag. Here tag is a single object. So instead you should check for ProjectTags__in=tag.
And if you already declared Tags class as ManyToManyRelation in Project class then there is no need to create Foreign Key constraint in tags class. As you didn't upload Project Class, so I don't have any idea about how you are relating tags class with it.

Hard coding if statements in templates

I have been working on an e-commerce website for a while. There three types of products there: Clothes, Accessories and Shoes. There is item table and category table. In the category table are the category objects(clothes,accessories, shoes) which maybe in the near future won't be changed. In my case i don't want accessories to have sizes. So when i am rendering the templates i do if statement which checks if the item's category is accessory. If it is I don't render the size field's value(which is null). Everything works good but i think that there is a better way to achieve this functionality without hard coding the if statement. Can you give me an advice for improving the code?
models.py
class ItemCategory(models.Model):
category = models.CharField(max_length=20)
class Item(models.Model):
seller = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
category = models.ForeignKey(ItemCategory, on_delete=models.SET_NULL, null=True)
brand = models.ForeignKey(Brand, on_delete=models.SET_NULL, null=True)
price = models.IntegerField(null=True, blank=True)
price_negotiable = models.BooleanField(default=False)
discount_price = models.IntegerField(null=True, blank=True)
size = models.ForeignKey(Size, on_delete=models.SET_NULL, null=True, blank=True)
condition = models.CharField(choices=CONDITION_CHOICES, max_length=9)
description = models.CharField(max_length=500)
{% if item.category.category != 'accessories' %}
Size: {{ item.size.size }}
{% endif %}
That approach looks normal to me. You could also just not pass any size data for accessories in the backend too. Either way you would need to sort the data in front-end or backend as you are trying to achieve it. If you will have to exclude more than one item than accessories in future, you might want to move your checks into backend. Then you can use list to store all excluded categories and check if item is in the list
if category not in category_exclusion_list:
# do something
Generally you would want complex data filtering or processing to happen in the Backend. Especially because it will be cleaner, easier to read and plus it will support more functionalities, plus python jinja2 does not support everything python does iirc.

Left Join using 3 different tables - django

I have to show all articles from a law. Besides that, if the article is setted as marked in database (is_marked), I have to underline this article. For that, I need to do a left join, I mean, I have to show all articles, and if the article is marked I need to know that to underline it on the view. This marked must be specifc for each user.
my view:
def details(request, pk):
law = get_object_or_404(Law, pk=pk)
articles = Article.objects.filter(law=pk)
context = {
'articles': articles,
}
template_name = 'leis/details.html'
return render(request, template_name, context)
My detail.html:
<div class="article-post">
{% for article in articles %}
{{article}}
{% endfor %}
</div>
That's my model:
class Law(models.Model):
name = models.CharField('Name', max_length=100)
description = models.TextField('Description', blank = True, null=True)
class Article(models.Model):
article = models.TextField('Artigo/Inciso')
number = models.IntegerField('Number', blank=True, null=True)
law = models.ForeignKey(Law, on_delete=models.CASCADE, verbose_name='Law', related_name='articles')
This class is saved with highlights made by a specif user in a specific article in a specif law:
class Highlight(models.Model):
law = models.ForeignKey(Law, on_delete=models.CASCADE, verbose_name='Law', related_name='highlightArticles')
article = models.ForeignKey(Law, on_delete=models.CASCADE, verbose_name='Artigo', related_name='highlightLaw')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name='highlightUsers', related_name='highlightUsers')
is_marked = models.BooleanField('Is it marked?', blank=True, default=False)
description = models.TextField('Description', blank = True, null=True)
How can I join the tables to show all the articles, marked with the specif highlights made by an specif user?
In newer versions of Django, you can perform subqueries inside other queries. Assuming you want to underline an article if the current user has highlighted the article, try the following:
from django.db.models import Exists, OuterRef
articles = (
Article
.objects
.filter(law=pk)
.annotate(
is_marked=Exists(
Highlight
.objects
.filter(
article_id=OuterRef('id'),
user=request.user
)
)
)
)
This way, we're performing a subquery for every article (don't worry about performance, the database engine will optimize this very well) to check if a Highlight object exists that matches the article and the current user.
Every article in this queryset will now contain an extra boolean member is_marked to tell you whether you should highlight it or not.
On a different note, there appear to be some problems with your model definitions. Highlight.article is currently a foreigh key to Law while I think this should be a foreign key to Article.

Django queries return empty strings

I have a problem with my django app, i'm created two Blog objects, one in mysql and another one with my view. I see them both in my database but i can't see them when i get them with a query.
I'm using django 1.9, python 2.7, apache 2.2 with mod_wsgi
Here is my view and my template.
def list_view(request):
blogs = Blog.objects.filter(published=True)
return render_to_response('blog/list.html',
{
"blogs": blogs,
},
context_instance=RequestContext(request))
{% for blog in blogs %}
<h2>{{blog.title}}</h2>
<p>{{blog.content}}</p>
<span>{% trans 'Writen by' %} : {{blog.writer.last_name}} {{blog.writer.first_name}}</span> - <span>{% trans 'Published on' %} {{blog.date_published}}</span>
{% endfor %}
The query gets me a list with 2 Blog objects inside, but they are empty. My template just shows Writen By - Published on twice.
But i can log in and print all my user informations.
Any idea what could be the problem or how i could solve it ? Thanks a lot !
EDIT : add models.py
class Blog(models.Model):
title = models.CharField(_("Title"), max_length=255, null=False, blank=False)
content = models.TextField(_("Content"), null=True, blank=True)
writer = models.ForeignKey(User, verbose_name=_('Writer'), blank=False, null=False)
published = models.BooleanField(_("Pusblished"), default=False)
date_published = models.DateTimeField(_("Date published"))
def __str__(self):
return '%s' % (self.title)
def __init__(self, *args, **kwargs):
super(Blog, self).__init__()
When you overrode Blog.__init__(), you did not send *args and **kwargs to the parent. Django Model magic needs those parameters. If you're not doing anything in __init__() you could just remove that all together.
If you want to keep it, do something like:
def __init__(self, *args, **kwargs):
super(Blog, self).__init__(*args, **kwargs)

Django select_related() and GenericForeignKey

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?

Categories