commit eec13cfd30c02e265056fe2bb2ea32a53e4e4b2e
parent 4ca9acc214ed47aeca65e743c8811aa76d3fd423
Author: Youth Employment Program Production <youthemployment22@gmail.com>
Date: Tue, 19 Sep 2023 11:02:05 -0600
added hours project_report aggregate, added display times by users and project to templates, refined employee reports, linked generate time select for reports, abstracted get_all_users
Diffstat:
10 files changed, 420 insertions(+), 11 deletions(-)
diff --git a/app/TODO.md b/app/TODO.md
@@ -0,0 +1 @@
+[] hours page change project linking
diff --git a/app/routes.py b/app/routes.py
@@ -774,7 +774,37 @@ def hours(username):#userid goes into call to db to get user[] -> then returns f
return redirect(url_for('hours',username=user['username']))
#hours = mongo.db.time_collection.find({'modified_by.0':user.username})
- return render_template ('dashboard/punchclock/index.html',form=form,hours=hours,availableProjects=availableProjects,total_hours=total_hours,statement_hours=statement_hours,user=user,ORGNAME=OrganizationName)
+
+
+ tspp = mongo.db.time_collection.aggregate( [
+ {
+ "$lookup":{
+ 'from':'projects_collection',
+ 'localField':'project',
+ 'foreignField':'_id',
+ 'as':'project_data'
+ }
+ },
+ {
+ "$group": {
+ "_id":"$project_data",
+ "laborHoursWorked": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
+ "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } },
+ "perdiemCount": { "$sum" :{'$cond':["$per_diem",1,0] } }
+ }
+ },
+ {
+ "$addFields": {
+ "totalHoursWorked": {"$subtract":[ "$laborHoursWorked", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
+ }
+ },
+ {
+ "$sort": { "_id": -1 }
+ }
+ ] )# Time Spent Per Project (filter entries by username, then group and sum hours by project)
+
+
+ return render_template ('dashboard/punchclock/index.html',form=form,hours=hours,availableProjects=availableProjects,total_hours=total_hours,statement_hours=statement_hours,user=user,tspp=tspp,ORGNAME=OrganizationName)
# Don't really need this until additional functionality is added, not in current project scope
#@app.route("/fleet")
@@ -1133,7 +1163,74 @@ def agreement_report():
@app.route('/admin/reports/agreements')
@login_required
def project_report():
- return render_template('admin/reports/project.html')
+ tspp = mongo.db.time_collection.aggregate( [
+ {
+ "$group": {
+ "_id":{"projectId": "$project"},
+ "laborHoursWorked": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
+ "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } },
+ "perdiemCount": { "$sum" :{'$cond':["$perdiem",1,0] } }
+ }
+ },
+ {
+ "$addFields": {
+ "totalHoursWorked": {"$subtract":[ "$laborHoursWorked", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
+ }
+ },
+ {
+ "$sort": { "_id": -1 }
+ }
+ ] )# Time Spent Per Project (filter entries by username, then group and sum hours by project)
+ ptl = mongo.db.time_collection.aggregate( [
+ {
+ "$lookup":{ # TODO TODO TODO THIS WILL REQUIRE CHANGING ALL DB WRITES TO time_collection['project'] TO BECOME ObjectId() OBJECTS THIS WILL LIKELY BREAK THINGS!!!! NEED TO ITERATE THROUGH DB ENTRIES AS WELL AS ENSURE READ OPERATIONS STILL GUNCTION PROPERLY AFTERWARDS
+ 'from':'projects_collection',
+ 'localField':'project',
+ 'foreignField':'_id',
+ 'as':'project_data'
+ }
+ },
+ {
+ "$addFields": {
+ "totalTime": { "$cond":{ "$if":"$clock_out","$then":{"$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } }},"$else":{'Clocked in'}},
+ "totalTime": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
+ "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } },
+ "perdiemCount": { "$sum" :{'$cond':["$per_diem",1,0] } }
+ }
+ },
+ {
+ "$addFields": {
+ "totalHoursWorked": {"$subtract":[ "$totalTime", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
+ }
+ },
+ {
+ "$sort": {'date': -1}
+ }
+ ] )
+
+ by_project = tspp
+# by_project = {}
+# for project in tspp:
+# by_project[project['_id'][0]['project_name']]=project['_id'][0]
+# by_project[project['_id'][0]['project_name']]['totalHoursWorked']=project['totalHoursWorked']
+# by_project[project['_id'][0]['project_name']]['lunchCount']=project['lunchCount']
+# by_project[project['_id'][0]['project_name']]['perdiemCount']=project['perdiemCount']
+
+# for time in ptl:
+# for user in by_user:
+# if time['modified_by'][0] in user:
+# if by_user[user].get('times'):
+# by_user[user]['times'].append(time)
+# else:
+# by_user[user].update({'times':[time]})
+# for project in by_project:
+# if time['project_data'][0]['project_name'] in project:
+# if by_project[project].get('times'):
+# by_project[project]['times'].append(time)
+# else:
+# by_project[project].update({'times':[time]})
+
+ return render_template('admin/reports/project.html',by_project=by_project,tspp=tspp,projectlookup=ptl,ORGNAME=OrganizationName)
# Payperiod Routes
#TODO marked 06.29,23
@@ -1191,7 +1288,7 @@ def time_data_total_report():
everyhours_by_project = mongo.db.time_collection.aggregate([ {'$sort':{'project':1,'modified_by.0':1}} ])
- return render_template ('admin/reports/total_timedata_report.html', usors = everyhours_by_user, projours = everyhours_by_project, hours=hours, tspp=tspp, ORGNAME=OrganizationName)
+ return render_template ('admin/reports/total_timedata_report.html', by_user = everyhours_by_user, by_project = everyhours_by_project, hours=hours, tspp=tspp, ORGNAME=OrganizationName)
####### TESTING END #######
###### TESTING Period selection START #######
@app.route('/dev/select-date-range', methods=["GET","POST"])
@@ -1392,7 +1489,145 @@ def time_bound_report(startday,endday):
#return json_util.dumps(by_user)
#return json_util.dumps(by_project)
- return render_template ('admin/reports/total_timedata_report.html', by_project=by_project, by_user=by_user, usertimes=usertimes, allhours=allhours, hours=hours, tspp=tspp, projectlookup=ptl, ORGNAME=OrganizationName)
+ return render_template ('admin/reports/bound_timedata_report.html', by_project=by_project, by_user=by_user, usertimes=usertimes, allhours=allhours, hours=hours, tspp=tspp, projectlookup=ptl, ORGNAME=OrganizationName)
+## Refactoring fn(s) START ##
+def get_all_user_times():
+ usertimes = mongo.db.time_collection.aggregate([
+ {
+ "$lookup":{
+ 'from':'user_collection',
+ 'localField':'modified_by.0',
+ 'foreignField':"username",
+ 'as':'userinfo'
+ }
+ },
+ {
+ "$sort":{"userinfo.username":1}
+ }
+ ])
+
+ allhours = mongo.db.time_collection.aggregate( [
+ {
+ "$group": {
+ "_id":{"_id":'$_id',
+ "project":"$project"},
+ "totalTime": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
+ "lunchCount":{ "$sum":{'$cond':["$lunch",1,0] } },
+ "perdiemCount":{ "$sum":{'$cond':["$per_diem",1,0] } }
+ }
+ },
+ {
+ "$addFields": {
+ "totalHoursWorked": {"$subtract":[ "$totalTime", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
+ }
+ },
+ {
+ "$sort": { "_id": 1 }
+ }
+ ] )# Total hours worked
+
+
+ hours = mongo.db.time_collection.aggregate( [
+ {
+ "$group": {
+ "_id": {
+ "$first":"$modified_by",
+ },
+ "totalTime": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
+ "lunchCount":{ "$sum":{'$cond':["$lunch",1,0] } },
+ "perdiemCount":{ "$sum":{'$cond':["$per_diem",1,0] } }
+ }
+ },
+ {
+ "$addFields": {
+ "totalHoursWorked": {"$subtract":[ "$totalTime", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
+ }
+ },
+ {
+ "$sort": { "_id": 1 }
+ }
+ ] )# Total hours worked
+ tspp = mongo.db.time_collection.aggregate( [
+ {
+ "$lookup":{
+ 'from':'projects_collection',
+ 'localField':'project',
+ 'foreignField':'_id',
+ 'as':'project_data'
+ }
+ },
+ {
+ "$group": {
+ "_id":"$project_data",
+ "laborHoursWorked": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
+ "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } },
+ "perdiemCount": { "$sum" :{'$cond':["$per_diem",1,0] } }
+ }
+ },
+ {
+ "$addFields": {
+ "totalHoursWorked": {"$subtract":[ "$laborHoursWorked", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
+ }
+ },
+ {
+ "$sort": { "_id": -1 }
+ }
+ ] )# Time Spent Per Project (filter entries by username, then group and sum hours by project)
+
+ ptl = mongo.db.time_collection.aggregate( [
+ {
+ "$lookup":{ # TODO TODO TODO THIS WILL REQUIRE CHANGING ALL DB WRITES TO time_collection['project'] TO BECOME ObjectId() OBJECTS THIS WILL LIKELY BREAK THINGS!!!! NEED TO ITERATE THROUGH DB ENTRIES AS WELL AS ENSURE READ OPERATIONS STILL GUNCTION PROPERLY AFTERWARDS
+ 'from':'projects_collection',
+ 'localField':'project',
+ 'foreignField':'_id',
+ 'as':'project_data'
+ }
+ },
+ {
+ "$addFields": {
+ "totalTime": { "$sum": { "$subtract": [{"$last":"$clock_out"}, {"$last":"$clock_in"}] } },
+ "lunchCount": { "$sum" : { '$cond':["$lunch",1,0] } },
+ "perdiemCount": { "$sum" :{'$cond':["$per_diem",1,0] } }
+ }
+ },
+ {
+ "$addFields": {
+ "totalHoursWorked": {"$subtract":[ "$totalTime", {"$multiply":["$lunchCount", 30 * 60 * 1000]}] }
+ }
+ },
+ {
+ "$sort": {'date': -1}
+ }
+ ] )
+
+ by_project = {}
+ for project in tspp:
+ by_project[project['_id'][0]['project_name']]=project['_id'][0]
+ by_project[project['_id'][0]['project_name']]['totalHoursWorked']=project['totalHoursWorked']
+ by_project[project['_id'][0]['project_name']]['lunchCount']=project['lunchCount']
+ by_project[project['_id'][0]['project_name']]['perdiemCount']=project['perdiemCount']
+
+ by_user ={}
+ for user in hours:
+ by_user[user['_id']]=user
+
+ for time in ptl:
+ for user in by_user:
+ if time['modified_by'][0] in user:
+ if by_user[user].get('times'):
+ by_user[user]['times'].append(time)
+ else:
+ by_user[user].update({'times':[time]})
+ for project in by_project:
+ if time['project_data'][0]['project_name'] in project:
+ if by_project[project].get('times'):
+ by_project[project]['times'].append(time)
+ else:
+ by_project[project].update({'times':[time]})
+ return by_user
+
+## Refartoring fn(s) END ##
+
####### TESTING END #######
@app.route('/admin/reports/pay-period', methods=['GET'])
@@ -1455,11 +1690,12 @@ def calculateHours(username=current_user):
@app.route('/admin/reports/employees')
@login_required
def report_employees():
+ by_user = get_all_user_times()
users = mongo.db.user_collection.find()
hours = mongo.db.time_collection.find()
for user in users:
user['total_hours']=calculateHours(user['username'])
- return render_template ('admin/employee_report/index.html', hours=hours, users=users, ORGNAME=OrganizationName)
+ return render_template ('admin/employee_report/index.html', by_user=by_user, hours=hours, users=users, ORGNAME=OrganizationName)
@app.route('/admin/reports/employee/<username>')
@login_required
diff --git a/app/templates/admin/bound_timedata_report/widget.html b/app/templates/admin/bound_timedata_report/widget.html
@@ -0,0 +1,128 @@
+<section class="agreements">
+ {% with messages = get_flashed_messages() %}
+ {% if messages %}
+ {% for message in messages %}
+ <div id='messagebanner'><p>{{ message }}</p></div>
+ {% endfor %}
+ {% endif %}
+ {% endwith %}
+ <section class="employee-overlook">
+ <!--{#<div class="employee-hours">
+ <table>
+ <tr>
+ <th>Employee</th>
+ <th>Total(hrs)</th>
+ <th>Lunch(#)</th>
+ <th>Per Diem(#)</th>
+ <th>Billable(hrs)</th>
+ </tr>
+ {% for entry in allhours %}
+ <tr>
+ <td><a href="{{url_for('hours',username=entry._id)}}">{{ entry['_id'] }}</a></td>
+ <td><a href="{{url_for('hours',username=entry._id)}}">{{ (entry['totalTime']/(1000*60*60))|round(2) }}</a></td>
+ <td><a href="{{url_for('hours',username=entry._id)}}">{{ entry['lunchCount'] }}</a></td>
+ <td><a href="{{url_for('hours',username=entry._id)}}">{{ entry['perdiemCount'] }}</a></td>
+ <td><a href="{{url_for('hours',username=entry._id)}}">{{ (entry['totalHoursWorked']/(1000*60*60))|round(2) }}</a></td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>#}-->
+ <!--{#<div>Test Div</br>
+ {% for elmnt in usors %}
+ {{elmnt}}</br>
+ {% endfor %}
+
+ </div>#}-->
+ <!--{#<div>Projours Div</br>
+ {% for elmnt in projours %}
+ {{elmnt}}</br>
+ {% endfor %}
+ </div>#}-->
+ <!--{#<div>Project Lookup Div</br>
+ {% for elmnt in projectlookup %}
+ {{elmnt['modified_by'][0]}}
+ {{elmnt['date']}}
+ {{elmnt['project_data'][0]['project_name']}}
+ {{elmnt}}</br></br>
+ {% endfor %}
+ </div>#}-->
+ <div class="pagebreak"><h2>Reports by User</h2></div>
+ <!-- <div class="pagebreak"></div> -->
+ {% for user, times in by_user.items() %}
+ <div class="pagebreak">
+ <dl class="user-summary">
+ <dt><h3>{{ user }}</h3></dt>
+ <dd>Total Hours(hrs):<a href="{{url_for('hours',username=user)}}">{{ (times['totalHoursWorked']/(1000*60*60))|round(2) }}</a></dd>
+ <dd>Total Lunches:<a href="{{url_for('hours',username=user)}}">{{ times['lunchCount'] }}</a></dd>
+ <dd>Total Per Diem:<a href="{{url_for('hours',username=user)}}">{{ times['perdiemCount'] }}</a></dd>
+ </dl>
+ <table>
+ <tr>
+ <th>Date</th>
+ <th>Project</th>
+ <th>Clocked In</th>
+ <th>Clocked Out</th>
+ <th>Lunch</th>
+ <th>Per Diem</th>
+ <th>Time(hrs)</th>
+ </tr>
+ {% for time in times['times'] %}
+ <tr>
+ <td>{{time['date'].date().isoformat()}}</td>
+ <td>{{time['project_data'][0]['project_name']}}</td>
+ <td>{{time['clock_in'][-1].time().isoformat(timespec="minutes")}}</td>
+ {% if time['clock_out'] is defined %}
+ <td>{{time['clock_out'][-1].time().isoformat(timespec="minutes")}}</td>
+ {% else %}
+ <td>Clocked In</td>
+ {% endif %}
+ <td>{{time['lunchCount']}}</td>
+ <td>{{time['perdiemCount']}}</td>
+ <td><a href="{{url_for('hours',username=time['modified_by'][0])}}">{{ (time['totalHoursWorked']/(1000*60*60))|round(2) }}</a></td>
+ </tr>
+ {% endfor %}
+ </table></div>
+ {% endfor %}
+
+ <!-- </section>
+ <section class="project-overlook"> -->
+ <div class="pagebreak"><h2>Reports by Project</h2></div>
+ <!-- <div class="pagebreak"></div> -->
+ {% for project, times in by_project.items() %}
+ <div class="pagebreak">
+ <dl class="user-summary">
+ <dt><h3>{{ project }}</h3></dt>
+ <dd>Total Hours(hrs):<a href="{{url_for('hours',username='brennentmazur')}}">{{ (times['totalHoursWorked']/(1000*60*60))|round(2) }}</a></dd>
+ <dd>Total Lunches:<a href="{{url_for('hours',username='brennentmazur')}}">{{ times['lunchCount'] }}</a></dd>
+ <dd>Total Per Diem:<a href="{{url_for('hours',username='brennentmazur')}}">{{ times['perdiemCount'] }}</a></dd>
+ </dl>
+ <table>
+ <tr>
+ <th>Date</th>
+ <th>Employee</th>
+ <th>Clocked In</th>
+ <th>Clocked Out</th>
+ <th>Lunch</th>
+ <th>Per Diem</th>
+ <th>Time(hrs)</th>
+ </tr>
+ {% for time in times['times'] %}
+ <tr>
+ <td>{{time['date'].date().isoformat()}}</td>
+ <td>{{time['modified_by'][0]}}</td>
+ <td>{{time['clock_in'][-1].time().isoformat(timespec="minutes")}}</td>
+ {% if time['clock_out'] is defined %}
+ <td>{{time['clock_out'][-1].time().isoformat(timespec="minutes")}}</td>
+ {% else %}
+ <td>Clocked In</td>
+ {% endif %}
+ <td>{{time['lunchCount']}}</td>
+ <td>{{time['perdiemCount']}}</td>
+ <td><a href="{{url_for('hours',username=time['modified_by'][0])}}">{{ (time['totalHoursWorked']/(1000*60*60))|round(2) }}</a></td>
+ </tr>
+ {% endfor %}
+ </table></div>
+ {% endfor %}
+
+ </section>
+</section>
diff --git a/app/templates/admin/employee_report/index.html b/app/templates/admin/employee_report/index.html
@@ -7,6 +7,9 @@
<!-- returned values from admin check is array of permissive ACCESS else return 'missing permissions response' -->
<section class="agreements">
<h3>Hours by Employee</h3>
+ {% for user in by_user %}
+ {{ user }}
+ {% endfor %}
<table>
<tr><th>First</th><th>Middle</th><th>Last</th><th>Pay</th><th>Hours</th></tr>
{%- for user in users %}
diff --git a/app/templates/admin/employee_report/widget.html b/app/templates/admin/employee_report/widget.html
@@ -1,6 +1,9 @@
<section class="agreements">
<section class="employee-overlook">
<h3>Employee Overlook</h3>
+ {% for user in by_user %}
+ {{ user }}
+ {% endfor %}
<div class="usercard">
<h4>{{ user.fname }} {{ user.mname }}. {{ user.lname }}</h3>
<table>
diff --git a/app/templates/admin/reports/bound_timedata_report.html b/app/templates/admin/reports/bound_timedata_report.html
@@ -0,0 +1,14 @@
+{% extends 'base.html' %}
+
+{% block title %}Employee Reports{% endblock %}
+
+{% block content %}
+ <section class="admin-grid">
+ <!-- returned values from admin check is array of permissive ACCESS else return 'missing permissions response' -->
+ {%- for x in ['reports','bound_timedata_report','roles','users'] %}
+ {% include 'admin/'~x~'/widget.html' %}
+ {%- else-%}
+ {{ 'You do not have permission to access this page' }}
+ {%- endfor %}
+ </section>
+{% endblock %}
diff --git a/app/templates/admin/reports/employee_report.html b/app/templates/admin/reports/employee_report.html
@@ -5,7 +5,7 @@
{% block content %}
<section class="admin-grid">
<!-- returned values from admin check is array of permissive ACCESS else return 'missing permissions response' -->
- {%- for x in ['reports','total_timedata_report','roles','users'] %}
+ {%- for x in ['reports','employee_report','roles','users'] %}
{% include 'admin/'~x~'/widget.html' %}
{%- else-%}
{{ 'You do not have permission to access this page' }}
diff --git a/app/templates/admin/reports/widget.html b/app/templates/admin/reports/widget.html
@@ -1,6 +1,6 @@
<section class="reportswidget">
<h3>Reports</h3>
- <a href="{{ url_for('pay_period_report') }}"><input type="submit" value="Payperiod"></a>
+ <a href="{{ url_for('select_date_range') }}"><input type="submit" value="Generate"></a>
<a href="{{ url_for('project_report') }}"><input type="submit" value="Agreements"></a>
<a href="{{ url_for('report_employees') }}"><input type="submit" value="Employees"></a>
</section>
diff --git a/app/templates/admin/total_timedata_report/widget.html b/app/templates/admin/total_timedata_report/widget.html
@@ -48,7 +48,12 @@
</div>#}-->
<div class="pagebreak"><h2>Reports by User</h2></div>
<!-- <div class="pagebreak"></div> -->
- {% for user, times in by_user.items() %}
+ {% for user in by_user %}
+ {{ user }}
+ {% endfor %}
+
+ {#
+ {% for user, times in by_user %}
<div class="pagebreak">
<dl class="user-summary">
<dt><h3>{{ user }}</h3></dt>
@@ -71,7 +76,11 @@
<td>{{time['date'].date().isoformat()}}</td>
<td>{{time['project_data'][0]['project_name']}}</td>
<td>{{time['clock_in'][-1].time().isoformat(timespec="minutes")}}</td>
- <td>{{time['clock_out'][-1].time().isoformat(timespec="minutes")}}</td>
+ {% if time['clock_out'] is defined %}
+ <td>{{time['clock_out'][-1].time().isoformat(timespec="minutes")}}</td>
+ {% else %}
+ <td>Clocked In</td>
+ {% endif %}
<td>{{time['lunchCount']}}</td>
<td>{{time['perdiemCount']}}</td>
<td><a href="{{url_for('hours',username=time['modified_by'][0])}}">{{ (time['totalHoursWorked']/(1000*60*60))|round(2) }}</a></td>
@@ -79,11 +88,17 @@
{% endfor %}
</table></div>
{% endfor %}
-
+ #}
+
<!-- </section>
<section class="project-overlook"> -->
<div class="pagebreak"><h2>Reports by Project</h2></div>
<!-- <div class="pagebreak"></div> -->
+ {% for project in by_project %}
+ {{ project }}
+ {% endfor %}
+
+ {#
{% for project, times in by_project.items() %}
<div class="pagebreak">
<dl class="user-summary">
@@ -107,7 +122,11 @@
<td>{{time['date'].date().isoformat()}}</td>
<td>{{time['modified_by'][0]}}</td>
<td>{{time['clock_in'][-1].time().isoformat(timespec="minutes")}}</td>
- <td>{{time['clock_out'][-1].time().isoformat(timespec="minutes")}}</td>
+ {% if time['clock_out'] is defined %}
+ <td>{{time['clock_out'][-1].time().isoformat(timespec="minutes")}}</td>
+ {% else %}
+ <td>Clocked In</td>
+ {% endif %}
<td>{{time['lunchCount']}}</td>
<td>{{time['perdiemCount']}}</td>
<td><a href="{{url_for('hours',username=time['modified_by'][0])}}">{{ (time['totalHoursWorked']/(1000*60*60))|round(2) }}</a></td>
@@ -115,6 +134,7 @@
{% endfor %}
</table></div>
{% endfor %}
+ #}
</section>
</section>
diff --git a/app/templates/dashboard/punchclock/index.html b/app/templates/dashboard/punchclock/index.html
@@ -65,4 +65,8 @@
</table>
</form>
</section>
+ {% for time in tspp %}
+ {{ time }}
+ </br>
+ {% endfor %}
{% endblock %}