django parent child summation in view and pass to a template - python

I am new to Django and would like to do the following i have a simple project and have a parent and child model of a Project and related Project Tasks. One Project will have multiple ProjectTasks.
I want to be able to display the Project and ProjectTask sum through a template.
class Project(models.Model):
projectContact = models.ForeignKey(Contact)
projectTitle = models.CharField(max_length=100)
projectAddress1 = models.CharField(max_length=200)
projectValueProposed = models.IntegerField(default=0)
projectValueActual = models.IntegerField(default=0)
def __str__(self):
return self.projectTitle
class ProjectTask(models.Model):
projectName = models.ForeignKey(Project)
projectTaskDesc = models.CharField(max_length=200)
projectTaskContact = models.ForeignKey(Contact)
projectTaskAmount = models.IntegerField(default=0)
def __str__(self):
return self.projectTaskDesc
My view is as follows.
def index(request):
project_list = Project.objects.all()
project_task = ProjectTask.objects.all()
##project_task = ProjectTask.objects.all() ##filter(projectName=2).aggregate(amount=Sum('projectTaskAmount'))
for pid in project_list:
ptask = project_task.filter(projectName=pid.id)
context = {'project_list':project_list,'project_task':ptask}
return render(request,'construction/index.html',context)
and template is
{% if project_list %}
<table class="tg">
<tr>
<th class="tg-le8v">Project name</th>
<th class="tg-le8v">Customer</th>
<th class="tg-le8v">Proposed Value</th>
<th class="tg-le8v">Actual Value</th>
</tr>
{% for projects in project_list %}
<tr>
<td class="tg-yw4l">{{ projects.projectTitle }}</td>
<td class="tg-yw4l">{{ projects.projectContact }}</td>
<td class="tg-yw4l">{{ projects.projectValueProposed }}xx{{projects.id }}</td>
<td class="tg-yw4l">{{ project_task}}</td>
</tr>
{% endfor %}
</table>
{% else %}
<p>No projects are available.</p>
{% endif %}
I am using Admin to create Project and add projectTask which works well.
I have two options:
1. To update the value of projectValueActual in the Project when i save the Admin page
2. To display dynamically the Project and sum of related ProjectTaskAmount
Need some help on how to prepare the values in the view (if that is the correct place to do it) and pass the context in some way i can display it.

The names of the model properties can be made much less verbose. Also, adding a related_name to the ForeignKey, so you can directly reference it from the Project model.
class Project(models.Model):
contact = models.ForeignKey(Contact)
title = models.CharField(max_length=100)
address1 = models.CharField(max_length=200)
valueProposed = models.IntegerField(default=0)
valueActual = models.IntegerField(default=0)
def __str__(self):
return self.title
class ProjectTask(models.Model):
project = models.ForeignKey(Project, related_name='tasks')
description = models.CharField(max_length=200)
contact = models.ForeignKey(Contact)
amount = models.IntegerField(default=0)
def __str__(self):
return self.description
That makes the view much simpler
def index(request):
ctx = {'project_list': Project.objects.all()}
return render(request, 'construction/index.html', ctx)
I am not quite sure what you want to do in your template, but lets assume you want to list all Tasks under each related Projects and then print the sum of the ProjectTask.amount values.
{% if project_list %}
<table class="tg">
<tr>
<th class="tg-le8v">Project name</th>
<th class="tg-le8v">Customer</th>
<th class="tg-le8v">Proposed Value</th>
<th class="tg-le8v">Actual Value</th>
</tr>
{% for project in project_list %}
<tr>
<td class="tg-yw4l">{{ project.title }}</td>
<td class="tg-yw4l">{{ project.contact }}</td>
<td class="tg-yw4l">{{ project.valueProposed }}xx{{projects.id }}</td>
<td class="tg-yw4l">
{% for task in project.tasks %}
<span>{{ task.amount }}</span>
{% if forloop.last %} = {% else %} + {% endif %}
{% endfor %}
{{ project.sumTaskAmounts }}
</td>
</tr>
{% endfor %}
</table>
{% else %}
<p>No projects are available.</p>
{% endif %}
And finally, to easily get the sum of all the Task.amount properties, you need to add a #property to Project that returns the sum of the amount values for each project's ProjectTask instances.
class Project(models.Model):
...
#property
def sumTaskAmounts(self):
return self.tasks.aggregate(amount=Sum('amount')).amount
Didn't test any of it, but that should more or less do it.

I've made the following changes to implement updating when i save the admin form.
models.py
def update_total_amount(self):
total=0
total=self.task.aggregate(amount=Sum('amount'))
self.valueActual=total['amount']
self.save()
and in admin.py
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for instance in instances:
instance.save()
formset.save_m2m()
instance.project.update_total_amount()
works for what I wanted to do. thanks for the help.

Related

Is there a way to isolate a foreign key from its related model using django?

I am building a django website that displays the prices of items in a shop. Each item belongs to a category so I mande category a foreign key that can have one or more items.
`class Category(models.Model):
category = models.CharField(max_length=64)
def __str__(self):
return self.category
class ShopItem(models.Model):
itemName = models.CharField(max_length=64)
price = models.IntegerField()
category = models.ForeignKey(Category, on_delete=models.CASCADE)
def __str__(self):
return self.itemName`
However, in the html, I am unable to obtain both the category and the shop item
I tried the following...
`---snip---
{% used_category = [] %}
{% for item in shopitem_list %}
{% if item.category not in used_category %}
<tr>
<td>item.category</td>
<td></td>
<td></td>
</tr>
<tr>
{% for rel_item in shopitem_list %}
{% if rel_item.category == item.category %}
<td></td>
<td>rel_item.itemName</td>
<td>rel_item.category</td>
{% endif %}
{% enfor %}
</tr>
{% endif %}
{% endfor %}`
I was hoping this would help me create a table where all the items are sorted below their respective categories but I got an error instead:
TemplateSyntaxError at /
Invalid block tag on line 18: 'used_category', expected 'endblock'. Did you forget to register or load this tag?
You need to create a view function which will pass the required Models information to the Template, thus completing the Model - View - Template (MVT) method which django runs on.
You can't create and update a list within the template, but you can prepare the data in the view function, then iterate over your items differently.
Start by making sure the categories are in your template context. To avoid n+1 queries, prefetch the related items also. Let's say your view looks like this (updated):
from django.shortcuts import render
from .models import Category
def items_list(request):
context = {
"categories": Category.objects.exclude(shop_items=None).prefetch_related(
"shop_items"
)
}
return render(request, "my_template.html", context)
Then in the template iterate over the categories:
{% for category in categories %}
<tr>
<td colspan="3">{{ category.category }}</td>
</tr>
<tr>
{% for item in category.shop_items %}
<td></td>
<td>{{ item.itemName }}</td>
<td>{{ item.price }}</td>
{% enfor %}
</tr>
{% endfor %}
NB The related name shop_items may not work here. To make sure it does, you can manually set the related_name on your foreign key. At the same time I would suggest changing itemName to just name:
class ShopItem(models.Model):
name = models.CharField(max_length=64)
price = models.IntegerField()
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="shop_items")
def __str__(self):
return self.name

Why the ListView not showing data in the template?

I'm working on my first Django project (the final project for codecademy's Django class) and I'm making webpages to show the inventory and menu that a restaurant has. I made the model, view, template, etc. for inventory and it displays the ListView perfectly. I did the same thing for my menu and it doesn't work. The page loads but the table that's supposed to output data is empty.
Any insight on what might be going wrong?
PS I'm new to programming and this is my first stackoverflow post so forgive any formatting errors or other faux pas
## views.py
from django.http import HttpResponse
from django.shortcuts import render
from .models import Inventory, MenuItem, RecipeRequirement, Purchase
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.views.generic import ListView
# Create your views here.
def index(request):
return render(request, "index.html")
class InventoryList(ListView):
template_name = "inventory.html"
model = Inventory
class MenuList(ListView):
template_name = "menu.html"
model = MenuItem
Inventory (below) works fine! :)
{% extends './base.html' %}
{% block content %}
<h2>Inventory</h2>
<table id="inventory">
<tr>
<th>Ingredient</th>
<th>Price</th>
<th>Units Available</th>
</tr>
{% for ingredient in inventory_list %}
<tr>
<tr>
<td>{{ ingredient.ingredient_name }}</td>
<td>{{ ingredient.price }}</td>
<td>{{ ingredient.units_avail }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
This one (Menu) is the problematic one :(
{% extends './base.html' %}
{% block content %}
<h2>Menu</h2>
<table id="menu">
<tr>
<th>Item</th>
<th>Price</th>
<th>In Stock?</th>
</tr>
{% for food in menu_list %}
<tr>
<tr>
<td>{{ food.menu_item_name }}</td>
<td>{{ food.price }}</td>
<td>{{ food.available }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
Models below
from django.db import models
from django.forms import DateTimeField
# Create your models here.
class Inventory(models.Model):
ingredient_name = models.CharField(max_length=30)
price = models.DecimalField(max_digits=5, decimal_places=2)
units_avail = models.IntegerField()
def __str__(self):
return self.ingredient_name + " avail: " + str(self.units_avail)
class MenuItem(models.Model):
menu_item_name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=5, decimal_places=2)
def __str__(self):
return self.menu_item_name + " with Price: " + str(self.price)
def available(self):
return all(recipe_req.enough() for recipe_req in self.reciperequirement_set.all())
class RecipeRequirement(models.Model):
ingredient = models.ForeignKey(Inventory, on_delete=models.CASCADE)
menu_item = models.ForeignKey(MenuItem, on_delete=models.CASCADE)
quantity = models.IntegerField()
def __str__(self):
return self.ingredient.ingredient_name + " in " + self.menu_item.menu_item_name
def enough(self):
return self.quantity <= self.ingredient.units_avail
class Purchase(models.Model):
menu_item = models.ForeignKey(MenuItem, on_delete=models.CASCADE)
timestamp = models.DateTimeField()
def __str__(self):
return self.menu_item.menu_item_name + " at " + self.timestamp
URLs below
from django.urls import path
from . import views
urlpatterns = [
path("", views.index, name="index"),
path("inventory", views.InventoryList.as_view(), name="inventory_list"),
path("menu", views.MenuList.as_view(), name="menu_list"),
]
I could see one problem, you have one extra <tr> tag open in both the templates just below the loop.
Also think some problem with available method you manually defined in MenuItem model, so I removed it, try without it.
Also I'd recommend you to use context_object_name which is by default object_list so:
views.py
class InventoryList(ListView):
template_name = "inventory.html"
model = Inventory
context_object_name="inventories"
class MenuList(ListView):
template_name = "menu.html"
model = MenuItem
context_object_name="menu_list"
inventory.html template
{% extends './base.html' %}
{% block content %}
<h2>Inventory</h2>
<table id="inventory">
<tr>
<th>Ingredient</th>
<th>Price</th>
<th>Units Available</th>
</tr>
{% for ingredient in inventories %}
<tr>
<td>{{ ingredient.ingredient_name }}</td>
<td>{{ ingredient.price }}</td>
<td>{{ ingredient.units_avail }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
menu.html template
{% extends './base.html' %}
{% block content %}
<h2>Menu</h2>
<table id="menu">
<tr>
<th>Item</th>
<th>Price</th>
<th>In Stock?</th>
</tr>
{% for food in menu_list %}
<tr>
<td>{{ food.menu_item_name }}</td>
<td>{{ food.price }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
Note: It's recommended to give / at the end of every route so your urls.py should be:
urls.py
from django.urls import path
from . import views
urlpatterns = [
path("", views.index, name="index"),
path("inventory/", views.InventoryList.as_view(), name="inventory_list"),
path("menu/", views.MenuList.as_view(), name="menu_list"),
]
Note: class based views in Django requires their name to be written as model name as prefix and actual view name as suffix, so you may change it to InventoryListView and MenuItemListView from InventoryList and MenuList respectively.
I notice that the RecipeRequirement model Class has the method available. This method calls the method enough from the RecipeRequirement model Class.
One troubleshooting strategy is to disable (comment out) the methods enough and available as they are introducing logic that would prevent display of menu items. In other words, your code appears to read: If we don't have enough ingredients there is no point returning menu items for recipes that we cannot make.
An alternative troubleshooting strategy is to populate the RecipeRequirement table with sufficient ingredients and it should work. Caveat: This answer is untested.
Here's a tip for a newcomer to coding that has served me well in my brief journey into software development. Errors have been my greatest teacher. When there are errors try to reduce as many elements as you can. When you hit a wall like this consider what is essential and comment out the rest. I suggested commenting out the two methods first because they are not required for the tables to display (or for what you report as being central to your question). Once you get the menu items displaying then you can re-introduce those methods one at time.

django.db.utils.IntegrityError: NOT NULL constraint failed: main_companyclass.FinancialYearEnd

I am running into the above-mentioned error when trying to run my app after creating a form that only updates 1 field.
What I am trying to achieve is as follows:
The user enters a client into the app and that saves it to the database, once that is done the client should be displayed on the checklist page where it will display some details about the client and show a checkbox (isdone_IT14) to say whether the clients' order has been completed.
The problem is that it doesn't seem that when the user ticks the checkbox and saves it, it doesn't seem like its updating that entry in the model.
I had created the below form to assist with that:
class CompanyIT14Form(ModelForm):
class Meta:
model = CompanyClass
fields= ['isdone_IT14']
But it is clearly not the correct solution. Does anyone know a better way to solve this error ?
Please see the below code:
Models.py:
class CompanyClass(models.Model):
#Company Only Fields
CompanyName = models.CharField(max_length=50 , blank=False)
RefNo = models.CharField(max_length=50 , blank=False )
FinancialYearEnd = models.DateField(auto_now=False, auto_now_add=False, null=False)
#Contact Details
ContactPerson = models.CharField( max_length=50, blank=False)
EmailAddress = models.CharField(max_length=50, blank=False)
#Services
IT14 = models.BooleanField(default=True)
# CheckList
isdone_IT14 = models.BooleanField(default=False)
def __str__(self):
return ( self.CompanyName)
Forms.py:
class CompanyForm(ModelForm):
class Meta:
model = CompanyClass
fields = '__all__'
widgets = {
'FinancialYearEnd' : forms.SelectDateWidget,
}
class CompanyIT14Form(ModelForm):
class Meta:
model = CompanyClass
fields= ['isdone_IT14']
Views.py:
class companyIT14(View):
def get(self,request):
it14s = CompanyClass.objects.filter(IT14 = True).order_by('CompanyName')
form = CompanyIT14Form()
content = {'it14s':it14s , 'form':form}
return render(request, 'main/Checklists/companyIT14.html', content)
def post(self,request):
it14s = CompanyClass.objects.filter(IT14=True).order_by('CompanyName')
form = CompanyIT14Form(request.POST)
content = {'it14s': it14s, 'form': form}
if form.is_valid():
form.save()
return redirect('companyIT14')
else:
print(form.errors)
return render(request, 'main/checklistHome.html', content)
Template.html:
{% extends "main/base.html"%}
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
{% block content %}
<body>
<br>
{% if user.is_authenticated %}
<h1 class="h1" style="text-align: center">Companies IT14's</h1>
<table class="table table-hover table-dark" style="width: 80%;text-align: center;">
<thead>
<tr>
<th scope="col">Company</th>
<th scope="col">Ref No.</th>
<th scope="col">Contact Person</th>
<th scope="col">E-mail Address</th>
<th scope="col">Completed?</th>
<th scope="col">Save</th>
</tr>
</thead>
<tbody>
{% for x in it14s %}
<tr>
<th scope="row">{{ x.CompanyName }}</th>
<th>{{ x.RefNo }}</th>
<th>{{ x.ContactPerson }}</th>
<th>{{ x.EmailAddress }}</th>
<form class="form-group mt-4" method="post">
{% csrf_token %}
<th>{{ form.isdone_IT14 }}</th>
<th><button class="btn btn-success" type="submit">Save</button> </th>
</form>
{% endfor %}
</table>
{% endif %}
</body>
{% endblock %}
</html>
You are obviously trying to save a form with None as value of FinancialYearEnd that has got null=False so it's forbidden. If the form is trying to save object with only isdone_IT14 with value it might give None to all the rest and has to raise this error.
I think you may try to change to null=True for now and see what the form is actually changing in objects.

Django accessing Model with property decorator

How do I access the data in the method with the property decorator. I can access it in the shell, and it is what I need, but it comes up blank in my site.
Models.py:
class Project(models.Model):
date_published = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
area = models.ForeignKey(Area, on_delete=models.PROTECT)
title = models.CharField(max_length=128, unique=True)
summary = models.CharField(max_length=256)
others = models.CharField(max_length=128, blank=True)
deadline = models.DateField(null=True, blank=True)
priority = models.ForeignKey(Priority, on_delete=models.PROTECT)
closed = models.DateTimeField(null=True, blank=True)
#property
def updates(self):
updates = []
categories = set(self.update_set.all().values_list(
'category__id', flat=True))
for cat_id in categories:
updates.append(Update.objects.filter(
project=self, category__id=cat_id).order_by('added').last())
return updates
def __str__(self):
return self.title
Views.py:
class ProjectView(ListView):
template_name = 'project_portal/home.html'
queryset = Project.objects.all()
And I am trying to use the following in my template:
<div class="box5">
{% for item in object_list %}
<table>
<tr>
<td>{{ item.updates }}</td>
</tr>
</table>
{% endfor %}
</div>
So far the box is blank. However, I have managed to get this data in the Django shell with the following:
p = Project.objects.all()[0]
p.updates
This returns the correct data in the right order for the first project. What do I need to do to get it to appear in the site?
It looks like your problem is that you are using .values() in your queryset in your view. This will give you a list. The reason why it works in the shell is because you are working with model objects, and hence you are able to get the updates property. So remove .values() from queryset = Project.objects.all().values()
Read up more about .values() here.
Are you sure the template has a var named project? Try changing project for object_list or override the get_context_data method to pass project to the template.
Edit: After reading your edited question I think you want to do something like this.
<div class="box5">
{% for project in object_list %}
<table>
<tr>
{% for category_update in project.updates %}
<td>{{ category_update }}</td>
{% endfor %}
</tr>
</table>
{% endfor %}
</div>
If you keep watching the category update in a wrong way please share with us your Category Model.
So this is now solved. The Models.py and Views.py in the original post are correct now, what was causing the rest of the issue was the Template, here is a short version of what was needed:
{% block home %}
{% if object_list %}
{% for project in project_list %}
<div class="box1">
<h4>{{ project.title }}</h4>
<p>Project Status</p>
<div class="square"></div>
</div>
<div class="box5">
{% for item in project.updates %}
<table>
<tr>
<td>{{ item }}</td>
</tr>
</table>
{% endfor %}
</div>
{% endfor %}
{% endif %}
The point I am making here is that there are two loops, one loops through projects in the object_list, and the second loop loops through items within the project.updates, which is fed by the method with the property decorator. I hope this is clear if anyone finds this looking for help.

Django View check second model for value

I'm somewhat new to Django so please bear with me here. I have a three models in my app, Company, Articles, and Transcripts. Both Articles and Transcripts have a foreignkey() field that links to the Company model. In my main view I list all of the companies, but I would like to indicate if there is an article or transcript for each of those companies.
Here are the models:
class Company(models.Model):
stock_symbol = models.CharField(max_length=5, unique=False)
company_name = models.CharField(max_length=200)
address = models.CharField(max_length=300, blank=True)
website = models.URLField(max_length=200, blank=True)
following = models.BooleanField()
class Articles(models.Model):
company = models.ForeignKey('Company', related_name='articles')
headline = models.CharField(max_length=200)
url = models.URLField(max_length=300, blank=True)
class Transcripts(models.Model):
company = models.ForeignKey('Company', related_name='transcripts')
headline = models.CharField(max_length=200)
url = models.URLField(max_length=300, blank=True)
Below is a view for a single company, however since I'm returning all companies I obviously can't use a primarykey. How would I go about getting this info for each of the companies? Am I setting up my models the right way to do this?
class CompanyDetails(generic.DetailView):
model = Company
template_name = 'company_details.html'
def get_context_data(self, **kwargs):
pk = self.kwargs.get('pk')
context = super(CompanyDetails, self).get_context_data(**kwargs)
context['company'] = Company.objects.filter(id=pk)
context['articles'] = Articles.objects.filter(company_id=pk).order_by('-date')
context['transcripts'] = Transcripts.objects.filter(company_id=pk).order_by('-date')
return context
UPDATE
How do I update the template if I want to show in a column that there is at least one article tied to each company? When I tried the for loop in the answer it created a column for every article, so I tried changing it to the below but it every records shows up as "Blank".
<tbody>
{% for company in all_companies %}
<tr>
<td nowrap="true">{{ company.company_name }}</td>
<td nowrap="true">{{ company.stock_symbol }}</td>
{% if company.following %}
<td align="center"><span class="glyphicon glyphicon-star"></span></td>
{% else %}
<td></td>
{% endif %}
<!--HAS ARTICLE(S) FLAG -->
{% if article in company.articles.all %}
<td align="center"><span class="glyphicon glyphicon-list-alt"></span></td>
{% else %}
<td>Blank</td>
{% endif %}
<td nowrap="true">{{ company.website }}</td>
<td nowrap="true">{{ company.address }}</td>
</tr>
{% endfor %}
</tbody>
In the template, you can follow the foreign key backwards from a company to its articles or transcripts.
{{ company }}
{% for company in company.articles.all %}
{{ article }}
{% endfor %}
If you use this in your detail view, you don't have to set articles or transcripts in the get_context_data method. You shouldn't set context['company'] at all - the detail view does this for you.
In the list view, you can loop through the companies and access the articles and transcripts in the same way.
{% for company in company_list %}
{{ company }}
{% for company in company.articles.all %}
{{ article }}
{% endfor %}
{% endfor %}
This will cause a query for every single company in the queryset. You can prevent this by using prefetch_related.
Company.objects.prefetch_related('articles', 'transcripts')
In your list view, you can use prefetch_related by setting queryset:
class CompanyList(generic.ListView):
# No need to set model now that you have set queryset
queryset = Company.objects.prefetch_related('articles', 'transcripts')
If you just want to know whether or not there are associated articles or transactions, then you don't have to prefetch them. You can annotate the queryset instead:
from django.db.models import Count
# in your view
queryset = Company.objects.annotate(num_articles=Count('article')
Then in your template, you can access company.num_articles.
{% if company.num_articles %}
Company has articles
{% endif %}

Categories