combine number of database requests using ORM - django

I am working on a website, where a user can vote and comment on different projects in various categories. Whenever a user returns to the site, their votes will be highlighted and all the comments will be visible.
The problem is that every time the page is loaded there are many database calls due to the design of my models or due to me not finding an efficient way of retrieving the data.
My models are as follows:
class ProjectCategory:
title=models.CharField(...)
class Project:
category=models.ForeignKey(ProjectCategory)
title=models.CharField(...)
def ownedComments(self):
return Comment.objects.filter(project=self).order_by('-submissionTime')
def ownedCommentsPreview(self):
return Comment.objects.filter(project=self).order_by('-submissionTime')[0:3]
def ownedVotes(self):
return Vote.objects.filter(project=self,upVoted=True).count()
class Vote:
project=models.ForeignKey(Project)
user=models.ForeignKey(User)
vote=models.IntegerField(...)
class Comment:
project=models.ForeignKey(Project)
user=models.ForeignKey(User)
title=models.CharField(...)
Here are relevant parts of my views.py:
def getCurrentUserData(request=None):
if (request==None) or (request.user.is_authenticated()==False):
return {'loggedInUsername':'Anonym','userIsLoggedIn':False}
return {'loggedInUsername':request.user.username,'userIsLoggedIn':True}
def getProjectResponse(request,surveyFile=None,additionalInfo={}):
userVotedUp=[]
if surveyFile!=None:
userVotes=Vote.objects.filter(upVoted=True,surveyFile=surveyFile)
for vote in userVotes:
userVotedUp.append(vote.project.id)
answerDict={'userVotedUp':userVotedUp,'prjCategories':PrjCategory.objects.select_related(),'projects': Project.objects.prefetch_related('category').filter(visible=True),'spammerquestion':spammerquestion.SpammerQuestion().getRandomQuestion(),'rotatingLines':helpers.getRandomRotatingLines(),'backgroundimageurl':helpers.getBackgroundImageUrl()}
answerDict.update(getCurrentUserData(request))
answerDict.update(additionalInfo)
return answerDict
def showProject(request,prjNo):
currentPrj=get_object_or_404(Project,prjNo=prjNo,visible=True)
surveyFile=assignSurveyFile(request,create_surveyFile_if_not_existing=False)
return render_to_response('website/singleproject.html', getProjectResponse(request,surveyFile,{'currentPrj':currentPrj,'immediateProjectID':currentPrj.id}),context_instance=RequestContext(request))
The template contains many iterations over projects and categories, there might be several database calls happening here. Relevant parts of the template are:
{% for project in projects %}
self.vote['{{ project.id }}']=ko.observable(new Vote({'totalVoteNumber':ko.observable({{ project.ownedVotes }}),'userVoteState':ko.observable({% if project.id in userVotedUp %}true{% else %}false{% endif %})}));
self.comments['{{ project.id }}']=ko.observableArray([]);
{% for comment in project.ownedCommentsPreview %}
self.comments['{{ project.id }}'].push(new Comment({'description':'{{ comment.description|striptags|safe }}','submissionTime':'{{ comment.submissionTime|humanDT }}','commentUuid':'{{ comment.commentUuid }}','deletable':false}));
{% endfor %}
{% endfor %}
{% for prjCat in prjCategories %}
.prjCatUnderline{{ prjCat.id }} {border-bottom: 0px solid {{ prjCat.color }};}
.prjCatUnderline{{ prjCat.id }}:hover {border-bottom: 3px solid {{ prjCat.color }};}
{% endfor %}
{% for prjCat in prjCategories %}
{{ prjCat.title }}
{% if not forloop.last %}<span class="catpipe">|</span>{% endif %}
{% endfor %}
<script type="text/javascript">
var projectLocations = [{% for project in projects %}{% if project.location != '' %}['{{ project.title }}', {{ project.location }}, {{ project.id }}, '{{ MEDIA_URL }}{{ project.mapImage }}', {{ project.mapImage.width }}, {{ project.mapImage.height }}, '{{ project.id }}'],
{% endif %}{% endfor %}];
</script>
{% for project in projects %}
<div class="item prjCat{{ project.category.id }}">
<div class="prjnumberbox">
<div class="numberbar" style="background-color:{{ project.category.color }}"></div>
<div class="prjnumber">{{ project.prjNo|stringformat:"02d" }}</div>
<div class="numberbar" style="background-color: {{ project.category.color }}"></div>
</div>
<div class="prjheadline"><span>{{ project.title }}</span></div>
<div class="prjimagecontainer">
<!-- ko with: $root.vote['{{ project.id }}'] -->
<div class="prjimageoverlay"><a data-bind="click: function(data,event) { $root.showSingleProjectData('{{ project.id }}',data) },css : { imgVoted :userVoteState }" class="singleProjectOverlay" href="{% url 'website.views.showProject' prjNo=project.prjNo %}"></a></div>
<div class="prjimage"><img src="{{ MEDIA_URL }}{{ project.teaserImage }}" /></div>
<!-- /ko -->
</div>
<div class="prjtextcontainer" {% if project.ownedComments|length_is:"0" %}style="background-image:none;"{% endif %}>
<div class="prjtext">{{ project.teaserText|safe }} <a class="singleProjectOverlay" data-bind="click: function(data,event) { showSingleProjectData('{{ project.id }}',data) }" href="{% url 'website.views.showProject' prjNo=project.prjNo %}">mehr >></a></div>
<div class="votesline"><span class="voteslinefont" data-bind="with: vote['{{ project.id }}']"> <span class="votecount" data-bind="text: totalVoteNumber">{{ project.ownedVotes }}</span> votes </span></div>
<div class="prjcomments nojs">
{% for comment in project.ownedCommentsPreview|slice:":4" %}
<div class="prjcommentitem">
{{ comment.teaserdescription }}
</div>
{% endfor %}
</div>
<div class="prjcomments" data-bind="foreach: comments['{{ project.id }}'], visible: comments['{{ project.id }}']().length > 0">
<!-- ko if: $index() < 4 -->
<div class="prjcommentitem">
<em data-bind="text: submissionTime"> </em><br/>
<span data-bind="text: description"> </span>
[X]
</div>
<!-- /ko -->
</div>
</div>
</div>
{% endfor %}
For the front page I'd like to retrieve all the projects, most recent three comments, the total number of votes for a project and the comments and votes by the user.
It would be great if that was possible with one db call, or with two, a general one to get the number of votes and the comments, and one for the data specific to the user.
CUrrently I am retrieving all the projects with one call, so I am automatically getting all the project categories. Then I get all the comments at once and assign them to the projects in python. I then get the comments and votes for a certain user with one call each.
Is there a way - without using sql directly - to get
comments and votes for a user with a single database call
comments, projects, accumulated number of votes per project and project categories at once?
One idea would be to download the whole database using select_related() and then sorting everything out in Python, but if in the future I have a large number of votes or comments, this might not be a good idea.

Related

Accessing dictionary inside Django template

I am customizing my item query result and marking these items as added in the cart based on DB values, i have succeeded in making this dictionary but the issue is how to access its values now inside the template
my view
menu_items =[]
menus = MenuItem.objects.all().order_by('-date_added')
for item in menus:
print(item.id)
menus_added['item']=item
if item.id in cartitem:
menus_added['is_added']=True
else:
menus_added['is_added']=False
menu_items.append(menus_added)
menus_added = {}
print(menu_items)
restaurants = Restaurants.objects.all()
categories= Category.objects.all()
return render(request,'store/store.html',context={
'product':menu_items, <-- i want access this dictionary
'restaurants':restaurants,
'categories':categories
})
this approach is not working
template
{% for item in product.item %}
<div class="col-md-4">
<figure class="card card-product-grid">
<div class="img-wrap">
<img src="{{item.image_url}}">
</div> <!-- img-wrap.// -->
<figcaption class="info-wrap">
<div class="fix-height">
{{item.item_name}}
<div class="price-wrap mt-2">
<span class="price">{{item.price}}</span>
<del class="price-old">${{item.price}}</del>
</div> <!-- price-wrap.// -->
</div>
{% if is_added %}
Added !
{% else %}
Add to cart
{% endif %}
</figcaption>
</figure>
</div> <!-- col.// -->
{% endfor %}
You don't need for loop here({% for item in product.item %}), just try {{product.image_url}}, not {{item.image_url}}
or {{ product.price }}, not {{item.price}}

Django template rendering slowly

I'm rendering a template and it's taking anywhere between 2-3 seconds to do it. There are quite a few elements on the page (10k checkboxes which change every query) and I'm wondering if anyone knows any helpful tricks to cut down on load time.
Link to site: http://18.207.127.123/search/
FYI: there are no Django Forms or Formsets in this application! :-) I'm working with objects called Lemmas which have Forms as their children... sorry if this causes confusion.
Update #1
Whatever is taking so long, it's here:
<li class="form-item" data-lemma="{{lemma.id}}" data-group="{{group}}" style="display:none">
<input type="checkbox" name="{{lemma.name}}#{{lemma.latin}}#{{lemma.homonym_id}}#{{group}}#{{form}}" onchange="countCheckboxes(this)" id="{{lemma.id}}#{{group}}#{{form}}" checked>
<label for="{{lemma.id}}#{{group}}#{{form}}">{{ form }}</label>
</li>
are the dot methods what's slowing this down?
Update #2 - Found the bottleneck
Apparently all the {{ var }} calls for each of the 10k items is what's taking so long. Each one costs ~50-200ms.
Update #3 - Remove unnecessary tags and autoescape off
<input id="x"...
<label for="x">
can be replaced with
<label>
<input ...>
</label>
saving six variable calls.
Adding
{% autoescape off %}
# body text
{% endautoescape %}
removes the need to make each variable safe.
These two changes bring the rendering time down about 50%.
Is it database hits?
After inspecting my django connections and profiling, I'm only making two database calls as far as I can tell.
My datastructure is a dict which looks like:
results_dict = { LemmaObj1 : [form11, form12, form13, ...],
LemmaObj2 : [form21, form22, form33, ...],
...
where the LemmaObj are Queryset objects. Generating this dict only takes 0.5 seconds. I force evaluation of the lazy queryset by putting the formij into a list comprehension.
views.py
lemma_qs = Lemma.objects.prefetch_related('form_set')
# ...
# some logic goes here
# ...
for lemma in lemma_qs:
form_qs = lemma.form_set.all()
form_list = [form.name for form in form_qs if some_condition(form)]
if form_list:
results_dict[lemma] = form_list
context['results_dict'] = results_dict
return render(request, "query.html", context)
All this is to say that I'm pretty sure the slowdown isn't coming from database hits.
The template in question
In my query.html I have two for loops.
query.html
<div class="lemma-box">
<ol>
<li class="lemma-title">
<div class="lemma-col-1">
lemma [{{results_dict|length}}] (group {{group}})
</div>
<div class="lemma-col-2">
latin
</div>
<div class="lemma-col-3">
homonym id.
</div>
<div class="lemma-col-4">
<input type="button" class="pushable" value="all" onclick="checkAllLemmas('{{group}}', true)"></input>
<input type="button" class="pushable" value="none" onclick="checkAllLemmas('{{group}}', false)"></input>
</div>
</li>
{% for lemma, form_list in results_dict.items %}
<li class="lemma-item" data-lemma="{{lemma.id}}" data-group="{{group}}" onclick="activateLemma(this)">
<div class="lemma-col-1">
<input type="checkbox" onchange="countCheckboxes(this)" onclick="lemmaToggleAll(this)" id="{{lemma.id}}#{{group}}" checked></input>
<label for="{{lemma.id}}#{{group}}">
{{ lemma.name }}
</label>
</div>
<div class="lemma-col-2">
{{ lemma.latin }}
</div>
<div class="lemma-col-3">
{{ lemma.homonym_id }}
</div>
<div class="lemma-col-4">
{% with form_list|length as total %}
<span class="counter">(<span class="total">{{ total }}</span>/<span>{{ total }}</span>)</span>
{% endwith %}
</div>
</li>
{% endfor %}
{% for item in not_found_items_set %}
<li class="lemma-item-not-found">
{{ item }} not found
</li>
{% endfor %}
</ol>
</div>
<div class="form-box">
<ol>
<li class="form-title">
<div class="form-col-1">
forms (group {{group}})
</div>
<div class="form-col-2" data-group="{{group}}">
<input type="button" class="pushable" value="all" onclick="checkAllForms('{{group}}', true)"></input>
<input type="button" class="pushable" value="none" onclick="checkAllForms('{{group}}', false)"></input>
</div>
</li>
{% for lemma, form_list in results_dict.items %}
{% for form in form_list %}
<li class="form-item" data-lemma="{{lemma.id}}" data-group="{{group}}" style="display:none">
<input type="checkbox" name="{{lemma.name}}#{{lemma.latin}}#{{lemma.homonym_id}}#{{group}}#{{form}}" onchange="countCheckboxes(this)" id="{{lemma.id}}#{{group}}#{{form}}" checked>
<label for="{{lemma.id}}#{{group}}#{{form}}">{{ form }}</label>
</li>
{% endfor %}
{% endfor %}
</ol>
</div>
Is this about as fast as I'm going to get?
Jinja2 the answer?
I tried jinja2 briefly by converting query.html to query.jinja and it seemed to make almost no difference. Do I need extra steps to leverage jinja2?
Models.py
Finally, for inspection, my models.py.
models.py
class Form(models.Model):
name = models.CharField(max_length=100, db_index = True)
lemma = models.ForeignKey("Lemma", on_delete = models.CASCADE)
def __str__(self):
return self.name
class Lemma(models.Model):
name = models.CharField(max_length=100, db_index = True)
latin = models.CharField(max_length=100, db_index = True, blank = True, null = True)
homonym_id = models.IntegerField(null = True)
def __str__(self):
return self.name
One thing to do is remove as many template tags as possible by thinking clearly about what your code needs to do. That will cut down on the rendering time substantially. Such as
<input id="x"...
<label for="x">
being replaced with
<label>
<input ...>
</label>
and saving a series of template tags.
You can mark the variables as safe, which again shaves off the rendering time, such as in
{% autoescape off %}
# body text
{% endautoescape %}
The absolute best way (if you need speed and have a ton of data) is to send the JSON to the client and have the JS render it. This is a little messy, but it's much faster (2-3x faster maybe) and gives you more flexibility to manipulate the DOM piecewise.
I'm going for solution (3) for now.

How to make a div appear only once in a loop?

I've created a model Team in models.py in Django for which I've created in views.py the following code:
def team(request):
obj = Team.objects.all().order_by('?')
context = {'team': obj}
return render(request, 'website/team.html', context)
In HTML I've created a team loop which is displaying all the team members available.
{% for team in team %}
<div class="member animated delay1" data-effect="fadeInUp">
<div class="team-image">
<div class="teamDescription">
<p>{{ team.description }}</p>
</div>
<img src="{{ team.picture.url }}">
</div>
<div class="blueLine"></div>
<div class="team-name-function animated delay1" data-effect="fadeInUp">
<h5>{{ team.name }} {{ team.surname }}</h5>
<p>{{ team.title }}</p>
</div>
</div>
{% endfor %}
In this loop, I need to make available one div with the numbers of team members, which has to appear only once and randomly as team members. Currently I have <div class="number">{{ team.count }}</div> outside the loop.
How do I integrate the members counting in the loop and make it appear only once?
Thank you in advance for any solution!
In the view use teams instead of team: context = {'teams': obj}
create a random number between 1 and teams length in view
import random
....
random_number = random.randint(a,len(teams)) # insert it after teams
...
context = {
'team': obj,
'random' : random_number,
}
then in the template use {% for team in teams %}
and if you want to show teams length just one time it's possible to use
{% if forloop.counter==random %}
<div class="number">{{ teams.count }}</div>
{% endif %}

Pagination on queryset on only a segment of the page and not the whole page

I have a page with projects for a specific category that begins with the top-x most earned projects, followed by the top-x most viewed and the third segment contains all (other) projects for this category.
This third segment is long as it contains all projects for this specific category. Therefore, I would like to paginate them. But with standard pagination going to the next page will refresh the page, ending on the top of the page, needing to scroll down again to the third section (every single time you select the next page), which isn't very user friendly.
I saw a site (kickstarter) where they were able to paginate only one segment of the page, leaving the other parts of the site untouched, which is exactly what I want. But I am a novice and I have no idea how to do this.
Thanks for any ideas and help!
My HTML code:
{% for project in projects|dictsortreversed:"created_date"%}
{% if project.num_days > 0 %}
<div class="row no-gutters">
<div class="offset-1 col-4 mb-5 ">
{% if project.image %}
<a href="{% url 'project_detail' project.id %}" target="_blank">
<img class="img-fluid mt-5" src="{{MEDIA_URL}}{{ project.image }}">
</a>
{% else %}
<a href="{% url 'project_detail' project.id %}" target="_blank">
<img class="img-fluid mt-5" src="{% static 'img/No-image-available.png' %}" alt="no image available">
</a>
{% endif %}
</div>
<div class="offset-1 col-6 mt-5">
<a href="{% url 'project_detail' project.id %}" target="_blank">
<h3 class="maintext">{{ project.title}}</h3>
</a>
<p class="subtext">{{ project.category }}</p>
<p>{{ project.description |truncatewords:60}}</p>
<p class="subtext mb-5">goal: ${{project.goal}} &nbsp&nbsp&nbsp&nbsp ends in: {{ project.num_days }} days</p>
</div>
</div>
{% endif %}
{% endfor%}
My view code
def get_project_category(request, project_category):
"""
Projects for a specific category
"""
projects = Project.objects.filter(
category=Category.objects.get(category=project_category).id).order_by('-created_date')
projects.category = project_category
calculations(projects)
context = {
'projects': projects,
}
return render(request, 'project_category.html', context)
I used https://datatables.net/manual/installation.
It makes a table with automatic search options and pagination. It paginates without having to start on top of the page.
AFter that I used CSS to eliminate the styling as a table. So made the header invisible and added an additional column in the table to create spacing between my images and text.

Django iteration (for ... in ...) how to put it in different divs?

I have few instances of model.
my model:
class Record(models.Model):
name = models.ForeignKey(Car)
image = models.ImageField(upload_to='images/')
created = models.DateTimeField(
default=timezone.now)
view:
def allrecords(request):
records = Record.objects.all().order_by('created')
return render(request, 'mycar/allrecords.html', {'records' : records})
I want show it on my website. In my template i have:
{% for record in records %}
<img src={{ record.image.url}}/>
<div>
{{record.name}}
</div>
{% endfor %}
Now i get list of my records, but i would like put the newest record to first div, next to second etc. How can i do that?
I show simple screen how i would like have that (if someone will create new record, it will go to first div and other records will change place. Is any possibility to do something like that?
edit:
<div>
{% for record in records %}
{% if forloop.counter == 1 %}
<img src={{ record.image.url}}/>
<div>
{{record.name}}
</div>
{% endif %}
{% endfor %}
</div>
<div>
{% for record in records %}
{% if forloop.counter == 2 %}
<img src={{ record.image.url}}/>
<div>
{{record.name}}
</div>
{% endif %}
{% endfor %}
</div>
.
.
# till your 5th image
You can use forloop.counter to get the iteration number and check what is the iteration the loop and handle data accordingly.
In addition you can use CSS to make the layout work as you want.
Here is the information for Django template counter
Edit :
{% for record in records %}
<div>
{% if forloop.counter == 1 %}
# Here you can get your first images
<img src={{ record.image.url}}/>
<div>
{{record.name}}
</div>
{% endif %}
</div>
<div>
{% if forloop.counter == 2 %}
# Here you can get your first images
<img src={{ record.image.url}}/>
<div>
{{record.name}}
</div>
{% endif %}
</div>
.
.
# till your 5th image
{% endfor %}
There are two ways to do this. If you want to set this option for a single view then:
def all_records(request):
records = Record.objects.all().order_by('-created')
return render(request, 'mycar/allrecords.html', {'records' : records})
You're almost correct but order_by('created') leads to asceding order while order_by('-created') leads to descending order which is what you require.
Alternatively, if you want to have this setting to apply to all views then set class Meta in your models.py which will ensure that wherever you use Record.objects.all() it returns Records in descending order of created field:
class Record(models.Model):
name = models.ForeignKey(Car)
image = models.ImageField(upload_to='images/')
created = models.DateTimeField(
default=timezone.now)
class Meta:
ordering = ('-created')
It's Django design pattern to make all logical decisions in models and views and only just plugin formatted data in templates. You shouldn't add any complex logic in templates.
I'm assuming the question means that the model might have more than 5 records. If so, a more generic solution would be
<div class='row'>
<div class='firstimage'>
<img src={{ records[0].image.url}}/>
{{record.name}}
</div>
{% for record in records %}
{% if forloop.counter > 1 %}
<div class='subsequentimage'>
<img src={{ record.image.url}}/>
{{record.name}}
</div>
{% endif %}
{% cycle "" "</div><div class='row'>" "" %}
{% endfor %}
</div>
Note the use of the 'cycle' tag to begin a new row div every third cell div.
I don't know what your CSS classes are to distinguish between rows and cells so I used 'row', 'firstimage' (which might be defined to take up twice as much width) and 'subsequentimage' as example classes.
I recommend you to use the context variables:
def all_records(request):
records = Record.objects.all().order_by('-created')
newest = records[:5]
oldest = records[5:]
return render(request, 'mycar/allrecords.html', {'newst' : newest,
'oldest': oldest })
In your template :
{% for new in newst %}
<div>what you want with news</div>
{% endfor %}
{% for old in oldest %}
<div>what you want with olds</div>
{% endfor %}