Django: Multiple COUNTs from two models away - sql

I am attempting to create a profile page that shows the amount of dwarves that are assigned to each corresponding career. I have 4 careers, 2 jobs within each of those careers and of course many dwarves that each have a single job. How can I get a count of the number of dwarves in each of those careers? My solution was to hardcore the career names in the HTML and to make a query for each career but that seems like an excessive amount of queries.
Here's what I "want" to see:
Unassigned: 3
Construction: 2
Farming: 0
Gathering: 1
Here's my models. I add some complexity by not connecting Careers directly to my Dwarves model (they have connected by their jobs).
from django.contrib.auth.models import User
from django.db import models
class Career(models.Model):
name = models.CharField(max_length = 64)
def __unicode__(self):
return self.name
class Job(models.Model):
career = models.ForeignKey(Career)
name = models.CharField(max_length = 64)
career_increment = models.DecimalField(max_digits = 4, decimal_places = 2)
job_increment = models.DecimalField(max_digits = 4, decimal_places = 2)
def __unicode__(self):
return self.name
class Dwarf(models.Model):
job = models.ForeignKey(Job)
user = models.ForeignKey(User)
created = models.DateTimeField(auto_now_add = True)
modified = models.DateTimeField(auto_now = True)
name = models.CharField(max_length = 64)
class Meta:
verbose_name_plural = 'dwarves'
def __unicode__(self):
return self.name
EDIT 1
my view looks something like:
def fortress(request):
careers = Career.objects.annotate(Count('dwarf_set'))
return render_to_response('ragna_base/fortress.html', {'careers': careers})
and template:
{% for career in careers %}
<li>{{ career.dwarf_set__count }}</li>
{% endfor %}
The error is:
Cannot resolve keyword 'dwarf_set' into field. Choices are: id, job, name
SOLUTION
view:
def fortress(request):
careers = Career.objects.all().annotate(dwarfs_in_career = Count('job__dwarf'))
return render_to_response('ragna_base/fortress.html', {'careers': careers})
template:
{% for career in careers reversed %}
<li>{{ career.name }}: {{ career.dwarves_in_career }}</li>
{% endfor %}
EVEN BETTER SOLUTION
careers = Career.objects.filter(Q(job__dwarf__user = 1) | Q(job__dwarf__user__isnull = True)) \
.annotate(dwarves_in_career = Count('job__dwarf'))
Don't forget to from django.db.models import Count, Q
What I like about the above solution was it not only returns careers that have dwarves working but even the careers that have none which was the next problem I encountered. Here's my view for completeness:
<ul>
{% for career in careers %}
<li>{{ career.name }}: {{ career.dwarves_in_career }}</li>
{% endfor %}
</ul>

Django's ORM isn't gonna make this uber-simple. The simple way is to do something like:
for career in Career.objects.all():
career.dwarf_set.all().count()
That will execute 1 query for each job (O(n) complexity).
You could try to speed that up by using Django's Aggregation feature, but I'm not entirely sure if it'll do what you need. You'd have to take a look.
The third option is to use custom SQL, which will absolutely get the job done. You just have to write it, and maintain it as your app grows and changes...

Does this do what you want?
from django.db.models import Count
Career.objects.annotate(Count('dwarf'))
Now each career object should have a dwarf__count property.

Can't you just get a count grouped by career? And do an outer join if you need the zero rows returned too.

Related

using curly double brackets inside another curly double brackets in django template

I started studying Django few days back, while doing a project I came across a situation.
In the views.py, I'm passing
def chat_rooms(request):
context = dict()
context['title'] = 'Chat'
rooms = Chat_rooms.objects.filter(Q(user1 = request.user.id) | Q(user2 = request.user.id) )
context['rooms'] = rooms
for room in rooms:
rmssg = Chats.objects.filter(room_id = room.id)
lmssg = Chats.objects.latest('id')
context[str(room.id)] = lmssg
return render(request,'previlageGroup/chat_rooms.html',context)
In the Django template,chat_room.html
{% for room in rooms %}
<div class="card" style="width: 100%;">
<a href="{% url 'pgroup:chat.screen' room.id %}">
<div class="card-body">
<p class="card-text">
{{ {{room.id}}.message}}
</p>
</div>
</a>
</div>
{% endfor %}
In models.py
class Chat_rooms(models.Model):
user1 = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user1')
user2 = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user2')
created_on = models.DateTimeField(auto_now_add=True)
class Chats(models.Model):
date = models.DateTimeField(auto_now_add=True)
has_viewed = models.BooleanField(default= False)
message = models.CharField(max_length = 200)
sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sender')
receiver = models.ForeignKey(User, on_delete=models.CASCADE, related_name='receiver')
room = models.ForeignKey(Chat_rooms, on_delete=models.CASCADE)
Django is giving me a error, that {{ {{room.id}}.message }} is not possible.
How can I do this? Is there any other way? Thanks in advance
At the end of the day, a django Model object is just like any other object in python, so you could easily do:
for room in rooms:
rmssg = Chats.objects.filter(room_id = room.id)
lmssg = Chats.objects.latest('id')
room.last_msg = lmssg
And access {{ room.last_msg }} in your template. This might cause errors if you ever happen to implement a last_msg field though.
Perhaps a more elegant solution would be implementing a related_name to your room field. If you change the last line of your views.py to:
room = models.ForeignKey(Chat_rooms, on_delete=models.CASCADE, related_name="chats")
You would be able to do things like:
room.chats.all() #get all messages from a chatroom
And, in your templates, you could do:
{{ room.chats.last }}
To get the last message in the chat.

Foreign key django - Database Relationships

I have a model for the Product Perfume, the product have different volume and prices.
I need to render the volume and price for each but i get "'function' object has no attribute 'prices'"
Any ideas? iam grateful for any suggestions
View:
from django.shortcuts import render
from django.views.generic import View, TemplateView
from products.models import Perfume, Pricing
def getIndex(request):
perfumes = Perfume.objects.all
thePrice= perfumes.prices.all()
return render(request, 'index.html', {'perfumes': perfumes, 'thePrice':thePrice})
Model
from django.conf import settings
from django.db import models
from django.utils import timezone
class Perfume(models.Model):
genderChoice = (
('unisex','unisex'), ('male', 'male'), ('female', 'female'))
name = models.CharField(max_length=50, default='')
brand = models.CharField(max_length=40, default='')
gender = models.CharField(max_length=7, choices=genderChoice, default='unisex')
description = models.TextField()
image = models.ImageField(upload_to='images/product_image')
created = models.DateField()
author =models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
active = models.BooleanField(default=False)
show_for_consumer = models.BooleanField(default=False)
def __str__(self):
return self.name
class Pricing(models.Model):
product =models.ForeignKey(Perfume, on_delete=models.CASCADE,related_name='prices')
price= models.DecimalField(max_digits=10, decimal_places=2)
volume= models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return 'Perfume {} - Price{} - Volume {}'.format(self.product.name,self.price, self.volume)
First, you need to correct the missing () in getting the perfumes queryset
perfumes = Perfume.objects.all()
This thePrice = perfumes.prices.all() is incorrect. You have to do this for every perfume, not for a queryset of perfumes.
Since Perfume and Pricing have a one-to-many relationship, you can directly access the prices from every perfume instance. This way you only need to pass the perfumes queryset
def getIndex(request):
perfumes = Perfume.objects.all()
return render(request, 'index.html', {'perfumes': perfumes})
Finally, in your template, you can call the prices like this
{% for product in perfumes %}
...
{% for p in product.prices.all %}
<option>{{p.volume}}ml - {{p.price}}$ </option>
...
{% endfor %}
{% endfor %}

Django - Parameter passed to the template but it can not use in if statement

I try to pass the variable to another page using GET method in django. It is possible for me to do that, but the problem is that the variable that I passed is not available in the if statement. I try to print out the value then it worked fine. Then I try to use it inside if statement then I come to know that it was not working properly. I have no idea regarding that. Can anyone help me? Thank you very much.
This is my views:
def test(request):
Test = Photos.objects.all()
ID = request.GET['id']
Context = {
'ID' : ID,
'test' : Test,
'testing' : 3,
}
return render(request, 'test.html', Context)
def tests(request):
tests = Photos.objects.all()
Context = {
'tests' : tests,
}
return render(request, 'tests.html', Context)
This is my urls.py:
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^$', 'flashapp.views.home'),
url(r'^play$', 'flashapp.views.play'),
url(r'^test$', 'flashapp.views.test'),
url(r'^tests$', 'flashapp.views.tests'),]
In tests.html I have buttons for passing id to test.html using GET method.
This is tests.html:
</head>
<body>
<h1>Welcome to tests pages.....</h1>
{% for i in tests %}
Click For {{ i.id }}<br>
{% endfor %}
</body>
This is test.html:
<html>
<head>
</head>
<body>
<h1>Welcome to Test Page</h1>
<h1>{{ ID }}</h1>
{% for i in test %}
<p>{{ i.id }}..........{{ testing }}.........{{ ID }}</p>
{% if ID == i.id %}
<p>Test</p>
<p>Working....ID = {{ i.id }}</p>
{% else %}
<p>In else</p>
{% endif %}
{% endfor %}
</body>
this is tests.html
this is test.html
I suppose to see the "Working....." but, it gone to else block. I have no idea. Help me please!!!
Thank you very much.
I edited my original answer based on your comment
You are passing a QuerySet as your Test object instead of an object in the first view, so it doesn't have an id property.
When you do this:
def test(request):
Test = Photos.objects.all()
...
You are getting a collection of all the Photos objects into the Test variable, which is not what you want, you only want one instance of Photos. For that kind of queries, you need to use the .get method, that returns a single instance or an exception in case it doesn't find it.
Test = Photos.objects.get(pk=request.GET['id'])
Your code now should look like this:
def test(request):
ID = request.GET['id']
Test = Photos.objects.get(pk=ID)
Context = {
'ID' : ID,
'test' : Test,
'testing' : 3,
}
return render(request, 'test.html', Context)
Now, for completeness' sake, this would fail in case the ID is not on the database, so we can do something like this:
def test(request):
try:
ID = request.GET['id']
Test = Photos.objects.get(pk=ID)
Context = {
'ID' : ID,
'test' : Test,
'testing' : 3,
}
return render(request, 'test.html', Context)
except Photos.DoesNotExist:
raise Http404("No Photo matches the given query.")
Of course, Django has its own shortcuts for these kind of things, so your code can be written like this:
from django.shortcuts import get_object_or_404
def test(request):
#I strongly suggest you don't use uppercase in variable names
id = request.GET['id']
test = get_object_or_404(pk=id)
context = {
'ID' : id,
'test' : test,
'testing' : 3,
}
return render(request, 'test.html', context)

How access tuple item

models.py
class MyModel(models.Model):
GENDER = (('M',"Male"),('F',"Female"))
gender = models.CharField(max_length = 1, choices = GENDER)
template.html
{% for item in mymodels %}
{{ item.GENDER[item.gender] }} #HOW TODO?
{% endfor %}
You can find some answers here
I think that best bet is to use helper function that returns proper value or extend/write new filter.

using Liquid variables inside of a liquid tag call

I made a custom link tag in Liquid and I am trying to be able to pass liquid variables into the call for that tag like so
{{ assign id = 'something' }} // this value is actual dynamic while looping through data
{% link_to article: id, text: 'Click Me!' %} // my custom tag
However this results in the article parameter being passed in as 'id' instead of 'something' as per the assign statement above it.
Does anyone know how to pass variables into tag calls?
I've recently solved this very simply with Jekyll 0.11.2 and Liquid 2.3.0 by passing the name of the variable as the tag parameter.
{% assign v = 'art' %}
{% link_to_article v %}
You can also pass the name of the control var while in a loop, like article above.
In Liquid::Tag.initialize, #markup is the second parameter, the string following the tag name. The assigned variables are available in the top level of the context.
def render(context)
"/#{context[#markup.strip]}/"
end
This obviously only allows one param to be passed. A more complex solution would parse params like x: 2, y: 3.
This solved the case for me context[#markup.strip].
My problem was that i wanted to be able to pass a variable to my custom Liquid tag like this: {% get_menu main_menu navigation.html settings.theme.id %}
In order to do this i first split the variable string into different varaibles on every space character.
class GetMenu < Liquid::Tag
include ApplicationHelper
def initialize(tag_name, variables, tokens)
#variables = variables.split(" ")
#menu_object = #variables[0]
#file_name = #variables[1]
#theme_id = #variables[2]
super
end
def render(context)
# This is where i use context[#theme_id.strip] to get the variable of "settings.theme.id"
content = CodeFile.find_by(hierarchy: 'snippet', name: #file_name.to_s, theme_id: context[#theme_id.strip])
#menu ||= Menu.find_by_slug(#menu_object)
context.merge('menu' => #menu)
Liquid::Template.parse(content.code).render(context)
end
end
Liquid::Template.register_tag('get_menu', GetMenu)
*This is just a more rich example that the answer above by Jonathan Julian
Doesn't look like this is possible, my solution was to just pass the variable name in to the tag and grab it out of the context the tag is being rendered in. Like so:
{% for article in category.articles %}
{% link_to variable: article, text: title %}
{% endfor %}
in my tag code (condensed):
def render(context)
uri = "article/#{context[#options[:variable]]['id']}"
"<a href='#{uri}'>#{build_link_text context}</a>"
end
It would be great to have a tag that can be called with literals and variables like
{% assign v = 'art' %}
{% link_to_article v %}
or
{% link_to_article 'art' %}
or
{% link_to_article "art" %}
and also of course
{% link_to_article include.article %}
In order to so I propose a helper function
def get_value(context, expression)
if (expression[0]=='"' and expression[-1]=='"') or (expression[0]=="'" and expression[-1]=="'")
# it is a literal
return expression[1..-2]
else
# it is a variable
lookup_path = expression.split('.')
result = context
puts lookup_path
lookup_path.each do |variable|
result = result[variable] if result
end
return result
end
end
And in the render just call the helper function to get the value of the literal or variable.
def render(context)
v = get_value(context, #markup.strip)
end
FYI, the initialiser would look like this:
def initialize(tag_name, markup, tokens)
#markup = markup
super
end
This does not strictly answer the question, but it may help others who are new to Liquid (like myself) and try something like this. Instead of implementing a custom tag, consider implementing a custom filter instead. Variables are resolved before they are passed into filters.
Ruby code:
module MyFilters
def link_to_article(input, text)
"<a href='https://example.org/article/#{input}'>#{text}</a>"
end
end
Liquid::Template.register_filter(MyFilters)
Liquid template:
{% assign id = 'something' %}
{{ id | link_to_article: 'Click Me!' }}
Output:
<a href='https://example.org/article/something'>Click Me!</a>
You can also use variables as parameters. So the following would have the same output:
{% assign id = 'something' %}
{% assign text = 'Click Me!' %}
{{ id | link_to_article: text }}
And filters can have zero or more (comma-separated) parameters:
{{ 'input' | filter_with_zero_parameters }}
{{ 'input' | filter_with_two_parameters: 'parameter 1', 'parameter 2' }}