Display queryset results by month in template - django

Id like to group the results of my queryset in a table by month with the month name as a header for each table.
I have a model like so:
class Tenant(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
telephone = models.CharField(max_length=30)
email = models.CharField(max_length=30)
contract_end = models.DateField(blank=False)
def __str__(self):
return u'%s %s' % (self.first_name, self.last_name)
View like so:
def expired_contracts(request):
now = datetime.datetime.now()
tenant_queryset = Tenant.objects.all()
expired_list = []
for x in range(0, 12):
date = now + relativedelta(months=x)
expired = tenant_queryset.filter(
contract_end__year=date.year,
contract_end__month=date.month
)
expired_list += expired
context = {"expired_list": expired_list}
return render(request, "expired_template.html", context)
Template:
{% for tenant in expired_list %}
<table>
<tr>
<td>{{ tenant.first_name }}</td>
<td>{{ tenant.telephone }}</td>
<td>{{ tenant.contract_end }}</td>
</tr>
</table>
{% endfor %}
I guess I could create a bunch of empty lists and populate them with a loop and if statements, but that seems a little much.
Any other way I could go about this?
Thanks!

itertools.groupby() is an excellent tool for grouping your list.
First you should order your object by the attribute that you are grouping by
tenant_queryset = Tenant.objects.order_by('contract_end')
Grouping by month can be achieved by using itertools.groupby and formatting the date as the month's name as a string date.strftime('%B')
context = {
"expired_list": itertools.groupby(
expired_list,
lambda t: t.contract_end.strftime('%B')
)
}
You can then loop over the months and the tenants for that month in the template like so
{% for month, tenants in expired_list %}
<h3>{{ month }}</h3>
<table>
{% for tenant in tenants %}
<tr>
<td>{{ tenant.first_name }}</td>
<td>{{ tenant.telephone }}</td>
<td>{{ tenant.contract_end }}</td>
</tr>
{% endfor %}
</table>
{% endfor %}

Related

Django render many to many attributes after query, display None

I am using slug to query the model, and render result in HTML.
The code is unable to render actual name of region, it just return None
Model
class Region(models.Model):
name = models.CharField(blank=False, unique=True)
def __str__(self):
return self.name
class Theme(models.Model):
name = models.CharField(blank=False, unique=True)
slug = models.SlugField(default="", null=False)
def __str__(self):
return self.name
class ETF(models.Model):
ticker = models.CharField(max_length=6, blank=False, db_index=True, unique=True)
full_name = models.CharField(max_length=200, blank=False)
# many to many
region = models.ManyToManyField(Region)
theme = models.ManyToManyField(Theme)
views.py
def theme_etf(request, slug): # render ETFs with theme filter
filtered_results = ETF.objects.filter(theme__slug=slug)
return render(request, "etf/list_etf.html", {
"ETFs": filtered_results
})
Part of list_etf.html
{% for ETF in ETFs %}
<tr>
<td>{{ ETF.ticker }}</td>
<td>{{ ETF.full_name }}</td>
<td>{{ ETF.region.name }}</td> # What should I use in this line
</tr>
{% endfor %}
The code is unable to render actual name of region, it just return None
Result
Ticker, Name, Region
ARKF, ARK Fintech Innovation ETF, None
ARKK, ARK Innovation ETF, None
KEJI, Global X China Innovation, None
I would like to have this:
Ticker, Name, Region
ARKF, ARK Fintech Innovation ETF, Global
ARKK, ARK Innovation ETF, Global
KEJI, Global X China Innovation, China
I have the information in the database. I have checked it in admin.
Can an ETF have multiple regions as implied by your database design? If it does not I would suggest you use ForeignKey instead.
You are accessing the region field as if it were a ForeignKey.
In your database design you need to iterate over the objects saved in the ManyToManyField using .all.
{% for ETF in ETFs %}
<tr>
<td>{{ ETF.ticker }}</td>
<td>{{ ETF.full_name }}</td>
<td>{% for region in ETF.region.all %}{{ region.name }}{%endfor%}</td>
</tr>
{% endfor %}
Because you have many-to-many relationship, you cannot simply have single values. So, you have to list values.
{% for ETF in ETFs %}
<tr>
<td>{{ ETF.ticker }}</td>
<td>{{ ETF.full_name }}</td>
<td>
<ol>
{% for region in ETF.region %}
<li>{{region.name}}</li>
{% endfor %}
</ol>
</td>
</tr>
{% endfor %}

Get Display CHOICE in a ForeignKey Django Model

My models:
class Sell(models.Model):
item = models.OneToOneField(Buy, related_name='sell', on_delete=models.CASCADE)
date = models.DateField(default=timezone.now)
code = models.ForeignKey(Order, related_name='order', on_delete=models.CASCADE, null=True, blank=True)
class Order(models.Model):
status_choice = (
("W", "Aguardando aprovação do pagamento"),
("A", "Pagamento aprovado - produto em separação"),
)
code = models.CharField(max_length=100)
status = models.CharField(max_length=1, choices=status_choice, default='W')
My views:
def order(request):
orders = Sell.objects.values('code__code', 'date', 'code__status') \
.annotate(total_paid=Sum('total_paid')). \
filter(buyer__username=request.user.username).order_by('-date')
return render(request, 'orders/orders.html',
{'orders': orders})
My template:
{% for order in orders %}
<tr>
<td>{{ order.code__code }}</td>
<td>{{ order.date }}</td>
<td>{{ order.get_code__status_display }}</td>
</tr>
{% endfor %}
The output I was expecting: "Aguardando aprovação do pagamento" but it's not printing this.
Any help to display the full text instead the only word "W"?
Thank you!
You should use a period/full-stop/. to access objects related by a ForeignKey
{% for order in orders %}
<tr>
<td>{{ order.code.code }}</td>
<td>{{ order.date }}</td>
<td>{{ order.code.get_status_display }}</td>
</tr>
{% endfor %}

Using attribute from a related model django

At my work everyone has to plan until they've reached their planning target. To make this easier I'm building a planning tool to plan the activities. All the activities have standard durations which are saved in the PlanningActivity model. Now I want to show a list of all the planned activities with the standard duration and also sum up the total planned time in a week. How can I use strd_duration in my added Planning's? I've tried so much, but nothing seems to work...
models.py
class PlanningActivity(models.Model):
name = models.CharField(max_length=255)
is_billable = models.BooleanField()
is_auto_realised = models.BooleanField()
strd_duration = models.PositiveIntegerField()
def __str__(self):
return self.name
class Planning(models.Model):
added_by = models.ForeignKey(
User, related_name='planning_activity', on_delete=models.DO_NOTHING
)
activity = models.ForeignKey(
PlanningActivity, on_delete=models.DO_NOTHING
)
date = models.DateField()
note = models.CharField(max_length=255)
def __str__(self):
return str(self.id)
views.py
def home(request):
planning_form = PlanningForm(request.POST)
if request.user.is_authenticated:
planning = Planning.objects.filter(added_by=request.user).order_by('-date')
contracts = UserContract.objects.filter(assigned_user=request.user)
else:
planning = ''
contract = ''
if planning_form.is_valid():
new_planning = planning_form.save(commit=False)
new_planning.added_by = request.user
new_planning.save()
return redirect('/')
else:
planning_form = PlanningForm()
return render(request, 'home.html', {'planning_form': planning_form, 'planning':planning, 'contracts':contracts})
home.html
<table class="table table-striped mt-2">
<thead class="thead-light">
<tr>
<td>Activity</td>
<td>Date</td>
<td>Note</td>
<td>Duration</td>
</tr>
</thead>
<tbody>
{% for plan in planning %}
<tr>
<td>{{ plan.activity }}</td>
<td>{{ plan.date }}</td>
<td>{{ plan.note }}</td>
<td>{{ plan.activity_id }}</td> <== HERE I WANT TO SHOW strd_duration
</tr>
{% endfor %}
</tbody>
</table>
You can access the attribute through the foreign key:
{% for plan in planning %}
<tr>
<td>{{ plan.activity }}</td>
<td>{{ plan.date }}</td>
<td>{{ plan.note }}</td>
<td>{{ plan.activity.strd_duration }}</td>
</tr>
{% endfor %}
Note that in the view, you can optimize the number of queries to the database with a .select_related(…) clause [Django-doc]:
planning = Planning.objects.filter(
added_by=request.user
).select_related('activity').order_by('-date')

Displaying one record from one to many table for each user in user list

I have a list of users.
Every user has a contract history, all contracts stored in same table.
Every user has 1 contract that is currently active.
I'm trying to display some details from the currently active contract in the user list.
Model:
class Contract(models.Model):
user = models.ForeignKey(User, on_delete = models.CASCADE)
contract_start = models.DateField(null = True, blank = True)
contract_end = models.DateField(null = True, blank = True)
View:
def get(self, request):
users = User.objects.all()
date_today = timezone.now().date()
active_contract = Contract.objects.filter(contract_start__lte=date_today, contract_end__gte=date_today)
return render(request, 'user/list.html', {
'users': users,
'active_contract': active_contract,
})
Template:
{% for user in users %}
<tr>
<td>{{ user.last_name }}</td>
<td>{{ user.first_name }}</td>
<td>{{ user.employee.location }}</td>
<td>{{ ?? }}</td>
</tr>
{% endfor %}
In an other view where there is one user, I can add user_id = id to the filter and I can display the details that I want. I can't seem to get this to work in my user list though.
If you are certain that each user has one contract active then you can do
def get(self, request):
date_today = timezone.now().date()
active_contracts = Contract.objects.select_related('user').filter(contract_start__lte=date_today, contract_end__gte=date_today)
return render(request, 'user/list.html', {
'active_contracts': active_contracts,
})
Template
{% for contact in active_contracts %}
<tr>
<td>{{ contact.user.last_name }}</td>
<td>{{ contact.user.first_name }}</td>
<td>{{ contact.user.employee.location }}</td>
<td>{{ contact.contract_start }}</td>
</tr>
{% endfor %}

Passing raw SQL to Django template

I need to display a large amount of data that I don't want paginated because I'm using a jQuery tablesorter, and using Person.objects.all() in a view is very expensive for the database. Takes too long to load, so I'm trying to perform raw SQL in my view.
I tried using Django's generic views, but they were just as slow as the objects.all() method.
Here are my models. Essentially, I want to display all persons while counting how many times they have appeared in, say, var1 or var2.
class Person(models.Model):
name = models.CharField(max_length=64, blank=True, null=True)
last_name = models.CharField(max_length=64,)
slug = models.SlugField()
class Object(models.Model):
title = models.ForeignKey(Title)
number = models.CharField(max_length=20)
var1 = models.ManyToManyField(Person, related_name="var1_apps", blank=True, null=True)
var2 = models.ManyToManyField(Person, related_name="var2_apps", blank=True, null=True)
var3 = models.ManyToManyField(Person, related_name="var3_apps", blank=True, null=True)
# ...
slug = models.SlugField()
from django.db import connection
def test (request):
cursor = connection.cursor()
cursor.execute('SELECT * FROM objects_person')
persons = cursor.fetchall() # fetchall() may not be the right call here?
return render_to_response('test.html', {'persons':persons}, context_instance=RequestContext(request))
Template:
<table class="table tablesorter">
<thead>
<tr>
<th>Name</th>
<th>Var1</th>
<th>Var2</th>
<th>Var3</th>
</tr>
</thead>
<tbody>
{% for person in persons %}
<tr>
<td>{{ person.last_name }}{% if person.name %}, {{ person.name }}{% endif %}</td>
<td>{{ person.var1_apps.count }}</td>
<td>{{ person.var2_apps.count }}</td>
<td>{{ person.var3_apps.count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
What it does it iterate blank lines, but if I just call {{ creator }} it will show the entire SQL table -- which I do not want. I must be doing something wrong with the query, so any help appreciated.
The problem isn't the Person.objects.all(). When you loop through that queryset, you are doing three queries for every item in the queryset to calculate the counts.
The answer is to annotate your queryset with the counts for each field.
# in the view
persons = Person.objects.annotate(num_var1_apps=Count('var1_apps'),
num_var2_apps=Count('var2_apps'),
num_var3_apps=Count('var3_apps'),
)
# in the template
{% for person in persons %}
<tr>
<td>{{ person.last_name }}{% if person.name %}, {{ person.name }}{% endif %}</td>
<td>{{ person.num_var1_apps }}</td>
<td>{{ person.num_var2_apps }}</td>
<td>{{ person.num_var3_apps }}</td>
</tr>
{% end for %}