Opening times in Django with less hit on db - python

I'm trying to create a structure to show the opening times from food companies, ordered by "open" status. But I don't know how can I get the informations on my template. For example:
#models.py
class Company(Model):
#... fields ...
def __unicode__(self):
return u'%s'%self.title
Here I'll store all times and days.
class OpeningHours(Model):
class Meta:
verbose_name = _(u"Horário de Abertura")
verbose_name_plural = _(u"Horários de Abertura")
unique_together = ('company', 'weekday')
company = ForeignKey(Company, related_name="opening_times", verbose_name=_(u"Empresa"))
weekday = IntegerField(choices=WEEKDAYS, verbose_name=_(u"Dia da Semana"))
fromHour = TimeField(verbose_name=_(u"Abre ás:"), null=True, blank=True)
toHour = TimeField(verbose_name=_(u"Fecha ás:"), null=True, blank=True)
def __unicode__(self):
return "%s %s (%s - %s)" % (self.company, self.weekday, self.fromHour, self.toHour)
And then I'm catching all the companies just like that on my views:
#views.py - This is how I'm processing the views
companies = sorted(Company.objects.filter(category__food=True).order_by('?')[0:4], key=lambda c: c.show_open(), reverse=True)
So, now the problem are in template, I need someway to catch this informations:
template.html - This is what I need to do on template
{% for company in companies %}
{% if company.open %}
OPEN
{% else %}
CLOSED
{% endif %}
<!-- I need to know when it's today, when it's tomorrow or when it's another day -->
{% ifequal company.open today %}
Next day will open is today at {{ company.next_time_open }}
{% ifequal company.open another_day %}
Next day will open is Sunday at {{ company.next_time_open }}
{% else %}
Wait, it's open right now from 22h till 00h
{% endif %}
{% endfor %}

First off, doing .order_by('?') is randomizing your queryset, which you are then sorting in Python. Ordering randomly causes the query to take more process time on the database side, and then sorting it in Python is its own additional process time. I would consider ordering your queryset using Django's methods, instead, by specifying the field(s) you want to order by in .order_by().
Secondly, one way to reduce database hits is to use .select_related() on the queryset. This will include related models in the queryset under a single SQL statement, so that later calls to the related models in the template don't cause new database hits.
Third, there is a lot of code in your different sections that reference fields and methods that I assume you've defined, but without seeing them directly, I can't make a judgment on what exactly you're doing. It is not possible at this time to give you a more direct answer.

Related

Django: How to iterate over two one-two-many table relationships?

I am trying to build a simple web page that queries three tables. There is a Company table that has a one-to-many relationship with a Position table, as well as a one-to-many relationship with a Project table.
The goal is to have the page display a given company once, along with all positions and and projects associated with said company. Then, move on to display the next company, any positions held there and projects completed.
Below is the closest I've come to getting this right. But, the obvious problem is that if there is more than one project associated with a given company, you'll see that company listed more than once.
I'm new to Django, so in the interest of learning, I wanted to beat my own head sufficiently hard before asking for help; but I could really use some fresh ideas at this point.
Also: I can see how a nested for loop might work here, but I'm just not clear on how the mechanics of that would work with the query, and then within the template.
Models:
from django.db import models
class Company(models.Model):
company_id = models.AutoField(primary_key=True)
company_name = models.CharField(max_length=20)
company_logo = models.ImageField(upload_to='images/')
def __str__(self):
return self.company_name
class Position(models.Model):
position_id = models.AutoField(primary_key=True)
position_title = models.CharField(max_length=55)
company_id = models.ForeignKey('professional.Company',
on_delete=models.CASCADE,
blank=True,
null=True)
begin_date = models.DateField()
end_date = models.DateField()
def __str__(self):
return self.position_title
class Project(models.Model):
project_id = models.AutoField(primary_key=True)
project_name = models.CharField(max_length=55)
company_id = models.ForeignKey('professional.Company',
on_delete=models.CASCADE,
blank=True,
null=True)
project_description = models.CharField(max_length=500)
project_image = models.ImageField(upload_to='images/')
def __str__(self):
return self.project_name
View:
from django.views.generic import TemplateView, ListView
from professional.models import Company
class ProfessionalHome(TemplateView):
template_name = 'professional/professional_home.html'
class TechnologyListView(ListView):
template_name = 'professional/__technology.html'
context_object_name = 'technology_list'
def get_queryset(self):
return Company.objects.values('company_name','position__position_title', 'project__project_name')
HTML and template:
{% for job in technology_list %}
<h1>{{job.company_name}}</h1>
<h1>Position: {{job.position__position_title}}</h1>
<h1>project: {{job.project__project_name}}</h1>
{% endfor %}
Instead of values in get_queryset method, you can return the actual queryset and then iterate over it to build your view.
def get_queryset(self):
return Company.objects.all()
Then in your template:
{% for job in technology_list %}
<h1>{{job.company_name}}</h1>
{% for position in job.position_set.all() %}
<h1>Position: {{position.position_title}}</h1>
{% endfor %}
{% for project in job.position_set.all() %}
<h1>project: {{project.project_name}}</h1>
{% endfor %}
{% endfor %}
If you want to iterate over companies, then you should use the Company model as the basis for your view, not Technology. Also, you should avoid values and values_list unless you know you have a good reason, which you don't here. You can use prefetch_related() to reduce the number of reverse queries. So:
class TechnologyListView(ListView):
def get_queryset(self):
return Company.objects.all.prefetch_related('project','position')
...
{% for company in company_list %}
<h1>{{company.company_name}}</h1>
{% for position in company.position_set.all %}
<h1>Position: {{ position.position_title }}</h1>
{% endfor %}
{% for project in company.project_set.all %}
<h1>project: {{ project.project_name }}</h1>
{% endfor %}
{% endfor %}
(Note, you should avoid giving your ForeignKey fields names ending in "_id". The Django field refers to the entire Company, not the ID; the fields should be called just company. The underlying database will be suffixed with _id anyway. Also, you don't need to use model_name prefixes on all your fields; it will be obvious from the object they are accessed on.)

Filter on variable of ManyToMany-field inside Django Template

I have a job-object and a content-object.
A job object gets created when a user wants to retrieve a set of content-objects in exchange for credits.
models.py:
class JobId(models.Model):
user = models.ForeignKey(User, blank=True, null=True, default=None)
job_time = models.DateTimeField(auto_now_add=True)
class content(models.Model):
job_id_list = models.ManyToManyField(JobId , related_name='JobId', blank=True)
job_id_list_not_provided = models.ManyToManyField(JobId , related_name='JobIdFailed', blank=True)
free_content = models.CharField(max_length=500, null=True, blank=True)
paying_content = models.CharField(max_length=500, null=True, blank=True)
For all content-objects part of the job, the JobId-object is added to the job_id_list - not keeping credit levels in mind. Different user can all run multiple jobs on the content objects.
For too-big jobs exceeding the credit amount of the user, the content-objects that would push the credit level below zero, get also the JobID-object added to the job_id_list_not_provided field of the content-object.
For a a specific user, we can retrieve the two sub-sets of found and not-found content-objects with following queries:
views.py:
found_results_list = results_list.filter(job_id_list_not_provided__user= None).distinct()
not_found_results_list = results_list.filter(job_id_list_not_provided__user=request.user).distinct()
My challenge:
Result lists are over 100-objects in size, so I would like to use pagination to get a nice view on my page
When not considering pagination, I could simply pass the 2 lists (found and not found) and loop over each list with a template from django:
Html:
<table>
<body>
{% for result in found_results_list %}
<tr>
<td>{{result.free_content}}</td>
<td>{{result.paying_content}}</td>
</tr>
{% empty %}
<tr>no result</tr>
{% endfor %}
{% for result in not_found_results_list %}
<tr>
<td>{{result.free_content}}</td>
<td>pay for the content</td>
</tr>
{% empty %}
<tr>no result</tr>
{% endfor %}
</tbody>
</table>
But what to do if I want to use pagination? It seems you can only use one result-list.
views.py
(I used .distinct() as sometimes the objects have too much jobs added to it from the same user)
results_list = xlinkdatabase_validated.objects.filter(job_id_list__user=request.user).distinct()
Main problem is:
I don't know to check inside the template if the paying_content can be visible if is start from an overall result_list both including found and not_found objects.
I tried using {{result.job_id_list_not_provided}} template inside Html, but this returns all job-objects of the content-object, even if these are not related to the specific user, which is logic of course.
How would I tackle this problem?
Thanks
I eventually solved my own issue by building a custom template tag that I unleash on my content-objects inside my view.
extra tags.py:
from django import template
from frontendapp.models import *
register = template.Library()
#register.filter
def has_viewing_rights(result, user):
has_viewing_rights = True
job_id_list_not_providedd = result.job_id_list_not_provided.all()
for item in job_id_list_not_providedd:
if item.user == user:
has_viewing_rights = False
return has_viewing_rights
html:
{% if result|has_viewing_rights:request.user%}
provided
{%else%}
not provided
{%endif%}

querying foreignkey nested for loop django

I am trying to return a list of categories for a business, and for each category I would like to list all the items related to the category.
I was returning all of my items, not by category, but I have decided I want them sorted by category. This is what I have tried (among other attempts as well) I simply am having trouble getting the items into there categories. This is my latest attempt
In my models.py I have
Business(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
logo = models.CharField(max_length=300)
ItemCategory(models.Model):
name = models.CharField(max_length=50)
Item(models.Model):
name = models.CharField(max_length=100)
business = models.ForeignKey(Business)
category = models.ForeignKey(ItemCategory)
short_description = models.CharField(max_length=250)
in my views.py
def business_profile(request, business_id):
items = Item.objects.filter(business_id = business_id).select_related('itemcategory')
return render(request, 'business_profile.html', {"items": items})
in my template I am trying
{% for itemcategory in items.itemcategory_set.all %}
{{ itemcategory.name }}
{% for item in itemcategory %}
{{ item.name }} <br>
{{ item.short_description }}
{% endfor %}
{% endfor %}
From my research into other questions and reading the documents, i felt this would be right.. However from my results I can not get the correct output in my template.
Would this be the correct practice? Or should I try getting the categories first with
categories = ItemCategory.objects.filter(business = business_id)
and then possibly add a select_related('item') to process all the items for the category?
When I refer to data in the template but my model has 2 words (ItemCategory) - for instance when I move backward for a foreign key or use the model in select_related('') - would I use item_category or do you simply use itemcategory with no underscore?
UPDATE: I answered below in comment with explanation.
To list the items by category, I used #yusuf.oguntola's idea and initially got the business with .get(), then I created a queryset to get all the items. My function in views.py included
business = Business.objects.get(id = business_id)
items = business.item_set.all().order_by('category')
note: the business_id was passed in url pattern
However, the key change was in my template. I found this awesome built in functionality for templates in the django documents here
I implemented it in template like so...
{% regroup items by category as category_list %}
<ul>
{% for category in category_list %}
<li>{{ category.grouper }}
<ul>
{% for item in category.list %}
<li>{{ item.name }}<br> {{ item.short_description }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
One of the most important parts is you have to have order_by() and put the parameter of the field you are ordering by - in my case, category. If this is not included, it will simply put the items listed in chronological order and will repeat your field instance.
items = Item.objects.filter(business_id = business_id) is wrong.
You should rather say:
items = Item.objects.filter(business__id = business_id) #Notice the double underscore.
You may also rather get the business and simply say:
business.item_set.all() #This would retrieve all Item for the business.
select_related() expects a fieldname as a paramter. So use a field name that exists on the Item model. Like:
select_related('category')
Where's the meal? May be you can proceed from there though.

Object is not iterable: Filtering QuerySet to display latest instance

I am trying to create a filter in a QuerySet using Django that returns the most recent instance that was submitted by the current logged in user.
I so far have the following in my view.py file:
def transfer(request):
title = 'Transfers'
queryset = Transfer.objects.filter(user=request.user).latest('timestamp')
context = {
"title": title,
"queryset": queryset,
}
if request.method == "POST":
print request.POST
return render(request, "transfer.html", context)
However this returns the error
TypeError: 'Transfer' object is not iterable
My models.py file looks like this so far:
from django.db import models
from django.contrib.auth.models import User
class Transfer(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(User)
amount = models.DecimalField(max_digits=10, decimal_places=2)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
and my html file is calling the QuerySet in the following way:
{% if request.user.is_authenticated %}
<h2>Your Transfers</h2>
{% if queryset %}
{% for instance in queryset %}
<p>Amount: {{ instance.amount }} </p>
<p>User: {{ instance.user }} </p>
<p>Date: {{ instance.timestamp }}</p>
{% endfor %}
{% else %}
<h3>You have not made any transfers.</h3>
{% endif %}
{% endif %}
Any suggestions would be hugely appreciated!
latest returns a single object: the latest one. So you're no longer passing an iterable queryset to the template, but a single instance which naturally you can't iterate.
It's not clear what you want to do here; perhaps you just need to remove the for loop, and just refer to the instance directly. Alternatively, if you do want differing you can iterate over, you might want to remove the latest call and just order by reverse timestamp.
Update you query like this
ueryset = Transfer.objects.filter(user=request.user).order_by('timestamp')[count_limit:]
[count_limit:] means how many objects from top you want to get. For getting top 5 you can do [:5]
You do not have to add id field, as django automatically add this.

Django SQL query duplicated n times

I have a book model and a rating model,
class Book(models.Model):
title = models.CharField(max_length=255)
slug = AutoSlugField(unique=True, populate_from='title')
description = models.TextField()
# more fields
class Rating(models.Model):
book = models.ForeignKey('library.Book')
score = models.DecimalField(max_digits=2, decimal_places=1)
the Query,
books = {'books': Book.objects.filter(pk__in=Rating.objects.all().order_by('-score'
).values_list('book__id', flat=True))[:10] }
template,
{% for i in books %}
{{ i.title }}, {{ i.rating_set.all.first.score }} <br/>
{% endfor %}
renders the model to the template, but the django debug toolbar shows as Duplicated n times where n is the number of objects in the list. when I use queryset caching, its normal.
whats going on behind, how can I fix this?
thanks.
Didn't test but you should definitely prefetch rating_set to not make additional database hit for each book to find their highest score:
rated_books = Rating.objects.all().order_by('-score').values_list('book', flat=True)
books = Book.objects.prefetch_related('rating_set').filter(pk__in=rated_books)[:10]
In the template, I also suspect .first and .all as they may cause an additional db hit. Besides, you don't need to call .first because we already know these rated books have at least one rating object.
{% for book in books %}
{{ book.title }}, {{ book.rating_set.all.0.score }} <br/>
{% endfor %}
Update: You need to use rating_set.all.0 instead of rating_set.0 to selec first rate
Read about select_related and prefetch_related.
Book.objects.filter(pk__in=Rating.objects.all().order_by('-score').values_list('book__id', flat=True)).preferch_related('rating_set')[:10]
In template You want to access to book rating {{ i.rating_set.all.0.score }}. Without select_related/prefetch_related Django in each row make new query. With prefetch_related Django made 1 query and fetch all ratings.
In Your case the problem may be in .first..

Categories