mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-07-10 19:34:27 -04:00
Merge pull request #734 from alphagov/refactor-dashboard-extensive
Refactor markup behind the AJAX bits of the page
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from datetime import datetime, date, timedelta
|
||||
from collections import namedtuple
|
||||
from itertools import groupby
|
||||
import json
|
||||
|
||||
from flask import (
|
||||
render_template,
|
||||
@@ -48,8 +49,8 @@ def service_dashboard(service_id):
|
||||
|
||||
return render_template(
|
||||
'views/dashboard/dashboard.html',
|
||||
templates=service_api_client.get_service_templates(service_id)['data'],
|
||||
**get_dashboard_statistics_for_service(service_id)
|
||||
updates_url=url_for(".service_dashboard_updates", service_id=service_id),
|
||||
partials=get_dashboard_partials(service_id)
|
||||
)
|
||||
|
||||
|
||||
@@ -57,12 +58,7 @@ def service_dashboard(service_id):
|
||||
@login_required
|
||||
@user_has_permissions('view_activity', admin_override=True)
|
||||
def service_dashboard_updates(service_id):
|
||||
return jsonify(**{
|
||||
'today': render_template(
|
||||
'views/dashboard/today.html',
|
||||
**get_dashboard_statistics_for_service(service_id)
|
||||
)
|
||||
})
|
||||
return jsonify(**get_dashboard_partials(service_id))
|
||||
|
||||
|
||||
@main.route("/services/<service_id>/template-activity")
|
||||
@@ -87,7 +83,7 @@ def template_history(service_id):
|
||||
def usage(service_id):
|
||||
return render_template(
|
||||
'views/usage.html',
|
||||
**get_dashboard_statistics_for_service(service_id)
|
||||
**calculate_usage(service_api_client.get_service_usage(service_id)['data'])
|
||||
)
|
||||
|
||||
|
||||
@@ -143,35 +139,54 @@ def aggregate_usage(template_statistics):
|
||||
)
|
||||
|
||||
|
||||
def get_dashboard_statistics_for_service(service_id):
|
||||
|
||||
usage = service_api_client.get_service_usage(service_id)
|
||||
sms_free_allowance = 250000
|
||||
sms_rate = 0.018
|
||||
|
||||
sms_sent = usage['data'].get('sms_count', 0)
|
||||
emails_sent = usage['data'].get('email_count', 0)
|
||||
def get_dashboard_partials(service_id):
|
||||
|
||||
template_statistics = aggregate_usage(
|
||||
template_statistics_client.get_template_statistics_for_service(service_id, limit_days=7)
|
||||
)
|
||||
|
||||
jobs = add_rate_to_jobs(filter(
|
||||
lambda job: job['original_file_name'] != current_app.config['TEST_MESSAGE_FILENAME'],
|
||||
job_api_client.get_job(service_id, limit_days=7)['data']
|
||||
))
|
||||
|
||||
return {
|
||||
'statistics': add_rates_to(sum_of_statistics(
|
||||
statistics_api_client.get_statistics_for_service(service_id, limit_days=7)['data']
|
||||
)),
|
||||
'template_statistics': template_statistics,
|
||||
'most_used_template_count': max(
|
||||
[row['usage_count'] for row in template_statistics] or [0]
|
||||
'totals': render_template(
|
||||
'views/dashboard/_totals.html',
|
||||
statistics=add_rates_to(sum_of_statistics(
|
||||
statistics_api_client.get_statistics_for_service(service_id, limit_days=7)['data']
|
||||
))
|
||||
),
|
||||
'template-statistics': render_template(
|
||||
'views/dashboard/template-statistics.html',
|
||||
template_statistics=template_statistics,
|
||||
most_used_template_count=max(
|
||||
[row['usage_count'] for row in template_statistics] or [0]
|
||||
),
|
||||
),
|
||||
'has_template_statistics': bool(template_statistics),
|
||||
'jobs': render_template(
|
||||
'views/dashboard/_jobs.html',
|
||||
jobs=jobs
|
||||
),
|
||||
'has_jobs': bool(jobs),
|
||||
'usage': render_template(
|
||||
'views/dashboard/_usage.html',
|
||||
**calculate_usage(service_api_client.get_service_usage(service_id)['data'])
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def calculate_usage(usage, sms_free_allowance=250000, sms_rate=0.018):
|
||||
|
||||
sms_sent = usage.get('sms_count', 0)
|
||||
emails_sent = usage.get('email_count', 0)
|
||||
|
||||
return {
|
||||
'emails_sent': emails_sent,
|
||||
'sms_free_allowance': sms_free_allowance,
|
||||
'sms_sent': sms_sent,
|
||||
'sms_allowance_remaining': max(0, (sms_free_allowance - sms_sent)),
|
||||
'sms_chargeable': max(0, sms_sent - sms_free_allowance),
|
||||
'sms_rate': sms_rate,
|
||||
'jobs': add_rate_to_jobs(filter(
|
||||
lambda job: job['original_file_name'] != current_app.config['TEST_MESSAGE_FILENAME'],
|
||||
job_api_client.get_job(service_id, limit_days=7)['data']
|
||||
))
|
||||
'sms_rate': sms_rate
|
||||
}
|
||||
|
||||
@@ -70,22 +70,13 @@ def view_jobs(service_id):
|
||||
@login_required
|
||||
@user_has_permissions('view_activity', admin_override=True)
|
||||
def view_job(service_id, job_id):
|
||||
job = job_api_client.get_job(service_id, job_id)['data']
|
||||
template = service_api_client.get_service_template(service_id=service_id,
|
||||
template_id=job['template'],
|
||||
version=job['template_version'])['data']
|
||||
|
||||
job = job_api_client.get_job(service_id, job_id)['data']
|
||||
filter_args = _parse_filter_args(request.args)
|
||||
_set_status_filters(filter_args)
|
||||
notifications = notification_api_client.get_notifications_for_service(
|
||||
service_id, job_id, status=filter_args.get('status'),
|
||||
)
|
||||
finished = job['status'] == 'finished'
|
||||
|
||||
return render_template(
|
||||
'views/jobs/job.html',
|
||||
notifications=notifications['notifications'],
|
||||
job=job,
|
||||
uploaded_at=job['created_at'],
|
||||
finished=job.get('notifications_sent', 0) and ((
|
||||
job.get('notifications_sent', 0) -
|
||||
job.get('notifications_delivered', 0) -
|
||||
@@ -93,11 +84,21 @@ def view_job(service_id, job_id):
|
||||
) == 0),
|
||||
uploaded_file_name=job['original_file_name'],
|
||||
template=Template(
|
||||
template,
|
||||
service_api_client.get_service_template(
|
||||
service_id=service_id,
|
||||
template_id=job['template'],
|
||||
version=job['template_version']
|
||||
)['data'],
|
||||
prefix=current_service['name']
|
||||
),
|
||||
counts=_get_job_counts(job, request.args.get('help', 0)),
|
||||
status=request.args.get('status', '')
|
||||
status=request.args.get('status', ''),
|
||||
updates_url=url_for(
|
||||
".view_job_updates",
|
||||
service_id=service_id,
|
||||
job_id=job['id'],
|
||||
status=request.args.get('status', '')
|
||||
),
|
||||
partials=get_job_partials(job)
|
||||
)
|
||||
|
||||
|
||||
@@ -138,38 +139,9 @@ def view_job_csv(service_id, job_id):
|
||||
@login_required
|
||||
@user_has_permissions('view_activity', admin_override=True)
|
||||
def view_job_updates(service_id, job_id):
|
||||
job = job_api_client.get_job(service_id, job_id)['data']
|
||||
filter_args = _parse_filter_args(request.args)
|
||||
_set_status_filters(filter_args)
|
||||
notifications = notification_api_client.get_notifications_for_service(
|
||||
service_id, job_id, status=filter_args.get('status')
|
||||
)
|
||||
finished = (
|
||||
job.get('notifications_sent', 0) -
|
||||
job.get('notifications_delivered', 0) -
|
||||
job.get('notifications_failed', 0)
|
||||
) == 0
|
||||
return jsonify(**{
|
||||
'counts': render_template(
|
||||
'partials/jobs/count.html',
|
||||
job=job,
|
||||
finished=finished,
|
||||
counts=_get_job_counts(job, request.args.get('help', 0)),
|
||||
status=request.args.get('status', '')
|
||||
),
|
||||
'notifications': render_template(
|
||||
'partials/jobs/notifications.html',
|
||||
job=job,
|
||||
notifications=notifications['notifications'],
|
||||
finished=finished,
|
||||
status=request.args.get('status', '')
|
||||
),
|
||||
'status': render_template(
|
||||
'partials/jobs/status.html',
|
||||
job=job,
|
||||
finished=finished
|
||||
),
|
||||
})
|
||||
return jsonify(**get_job_partials(
|
||||
job_api_client.get_job(service_id, job_id)['data']
|
||||
))
|
||||
|
||||
|
||||
@main.route('/services/<service_id>/notifications/<message_type>')
|
||||
@@ -319,3 +291,30 @@ def _get_job_counts(job, help_argument):
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
def get_job_partials(job):
|
||||
|
||||
filter_args = _parse_filter_args(request.args)
|
||||
_set_status_filters(filter_args)
|
||||
|
||||
return {
|
||||
'counts': render_template(
|
||||
'partials/jobs/count.html',
|
||||
job=job,
|
||||
counts=_get_job_counts(job, request.args.get('help', 0)),
|
||||
status=request.args.get('status', '')
|
||||
),
|
||||
'notifications': render_template(
|
||||
'partials/jobs/notifications.html',
|
||||
job=job,
|
||||
notifications=notification_api_client.get_notifications_for_service(
|
||||
job['service'], job['id'], status=filter_args.get('status')
|
||||
)['notifications'],
|
||||
status=request.args.get('status', '')
|
||||
),
|
||||
'status': render_template(
|
||||
'partials/jobs/status.html',
|
||||
job=job
|
||||
),
|
||||
}
|
||||
|
||||
15
app/templates/components/ajax-block.html
Normal file
15
app/templates/components/ajax-block.html
Normal file
@@ -0,0 +1,15 @@
|
||||
{% macro ajax_block(partials, url, key, interval=2, finished=False) %}
|
||||
{% if not finished %}
|
||||
<div
|
||||
data-module="update-content"
|
||||
data-resource="{{ url }}"
|
||||
data-key="{{ key }}"
|
||||
data-interval-seconds="{{ interval }}"
|
||||
aria-live="polite"
|
||||
>
|
||||
{% endif %}
|
||||
{{ partials[key]|safe }}
|
||||
{% if not finished %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -1,16 +1,5 @@
|
||||
{% from "components/pill.html" import pill %}
|
||||
|
||||
<div
|
||||
{% if not finished %}
|
||||
data-module="update-content"
|
||||
data-resource="{{url_for(".view_job_updates", service_id=current_service.id, job_id=job.id, status=status, help=request.args.get('help', 0))}}"
|
||||
data-key="counts"
|
||||
aria-live="polite"
|
||||
{% endif %}
|
||||
>
|
||||
|
||||
<div class="bottom-gutter">
|
||||
{{ pill('Status', counts, status) }}
|
||||
</div>
|
||||
|
||||
<div class="bottom-gutter">
|
||||
{{ pill('Status', counts, status) }}
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
{% from "components/table.html" import list_table, field, right_aligned_field_heading, date_field, row_heading %}
|
||||
|
||||
<div
|
||||
{% if notifications %}
|
||||
class='dashboard-table'
|
||||
{% endif %}
|
||||
{% if not finished %}
|
||||
data-module="update-content"
|
||||
data-resource="{{url_for(".view_job_updates", service_id=current_service.id, job_id=job.id, status=request.args.get('status'), help=request.args.get('help'))}}"
|
||||
data-key="notifications"
|
||||
aria-live="polite"
|
||||
{% endif %}
|
||||
>
|
||||
{% if notifications %}
|
||||
<div class="dashboard-table">
|
||||
{% endif %}
|
||||
|
||||
{% if notifications %}
|
||||
<p class="bottom-gutter">
|
||||
@@ -46,4 +38,6 @@
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
|
||||
</div>
|
||||
{% if notifications %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
<div
|
||||
{% if not finished %}
|
||||
data-module="update-content"
|
||||
data-resource="{{url_for(".view_job_updates", service_id=current_service.id, job_id=job.id, status=status, help=request.args.get('help', 0))}}"
|
||||
data-key="status"
|
||||
aria-live="polite"
|
||||
{% endif %}
|
||||
>
|
||||
<div>
|
||||
<p class='heading-small bottom-gutter'>
|
||||
Uploaded by {{ job.created_by.name }} on {{ job.created_at|format_datetime_short }}
|
||||
</p>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{% from "components/table.html" import list_table, field, right_aligned_field_heading, row_heading %}
|
||||
{% from "components/big-number.html" import big_number %}
|
||||
{% from "components/big-number.html" import big_number -%}
|
||||
|
||||
<div class='dashboard-table'>
|
||||
{% call(item, row_number) list_table(
|
||||
jobs,
|
||||
caption="Recent batch jobs",
|
||||
caption="Recent files uploaded",
|
||||
caption_visible=False,
|
||||
empty_message='You haven’t sent any batch messages yet',
|
||||
empty_message='You haven’t uploaded any files recently',
|
||||
field_headings=[
|
||||
'File',
|
||||
'Sending',
|
||||
|
||||
27
app/templates/views/dashboard/_totals.html
Normal file
27
app/templates/views/dashboard/_totals.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% from "components/big-number.html" import big_number_with_status %}
|
||||
{% from "components/message-count-label.html" import message_count_label %}
|
||||
|
||||
<div class="grid-row">
|
||||
<div class="column-half">
|
||||
{{ big_number_with_status(
|
||||
statistics.emails_requested,
|
||||
message_count_label(statistics.emails_requested, 'email', suffix=''),
|
||||
statistics.emails_failed,
|
||||
statistics.get('emails_failure_rate', 0.0),
|
||||
statistics.get('emails_failure_rate', 0)|float > 3,
|
||||
failure_link=url_for(".view_notifications", service_id=current_service.id, message_type='email', status='failed'),
|
||||
link=url_for(".view_notifications", service_id=current_service.id, message_type='email', status='sending,delivered,failed')
|
||||
) }}
|
||||
</div>
|
||||
<div class="column-half">
|
||||
{{ big_number_with_status(
|
||||
statistics.sms_requested,
|
||||
message_count_label(statistics.sms_requested, 'sms', suffix=''),
|
||||
statistics.sms_failed,
|
||||
statistics.get('sms_failure_rate', 0.0),
|
||||
statistics.get('sms_failure_rate', 0)|float > 3,
|
||||
failure_link=url_for(".view_notifications", service_id=current_service.id, message_type='sms', status='failed'),
|
||||
link=url_for(".view_notifications", service_id=current_service.id, message_type='sms', status='sending,delivered,failed')
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
23
app/templates/views/dashboard/_usage.html
Normal file
23
app/templates/views/dashboard/_usage.html
Normal file
@@ -0,0 +1,23 @@
|
||||
{% from "components/big-number.html" import big_number %}
|
||||
|
||||
<div class='grid-row'>
|
||||
<div class='column-half'>
|
||||
<div class="keyline-block">
|
||||
{{ big_number("Unlimited", 'free email allowance', smaller=True) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class='column-half'>
|
||||
<div class="keyline-block">
|
||||
{% if sms_chargeable %}
|
||||
{{ big_number(
|
||||
(sms_chargeable * sms_rate),
|
||||
'spent on text messages',
|
||||
currency="£",
|
||||
smaller=True
|
||||
) }}
|
||||
{% else %}
|
||||
{{ big_number(sms_allowance_remaining, 'free text messages left', smaller=True) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,5 +1,11 @@
|
||||
{% extends "withnav_template.html" %}
|
||||
|
||||
{% from "components/big-number.html" import big_number, big_number_with_status %}
|
||||
{% from "components/show-more.html" import show_more %}
|
||||
{% from "components/message-count-label.html" import message_count_label %}
|
||||
{% from "components/table.html" import list_table, field, right_aligned_field_heading, hidden_field_heading %}
|
||||
{% from "components/ajax-block.html" import ajax_block %}
|
||||
|
||||
{% block page_title %}
|
||||
{{ current_service.name }} – GOV.UK Notify
|
||||
{% endblock %}
|
||||
@@ -21,7 +27,37 @@
|
||||
<h2 class="heading-medium">
|
||||
In the last 7 days
|
||||
</h2>
|
||||
{% include 'views/dashboard/today.html' %}
|
||||
|
||||
{{ ajax_block(partials, updates_url, 'totals') }}
|
||||
{{ show_more(
|
||||
url_for('.weekly', service_id=current_service.id),
|
||||
'Compare to previous weeks'
|
||||
) }}
|
||||
|
||||
{% if partials['has_template_statistics'] %}
|
||||
{{ ajax_block(partials, updates_url, 'template-statistics') }}
|
||||
{{ show_more(
|
||||
url_for('.template_history', service_id=current_service.id),
|
||||
'See all templates used this year'
|
||||
) }}
|
||||
{% endif %}
|
||||
|
||||
{% if partials['has_jobs'] %}
|
||||
{{ ajax_block(partials, updates_url, 'jobs') }}
|
||||
{{ show_more(
|
||||
url_for('.view_jobs', service_id=current_service.id),
|
||||
'See all uploaded files'
|
||||
) }}
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.has_permissions(['manage_settings'], admin_override=True) %}
|
||||
<h2 class='heading-medium'>This year</h2>
|
||||
{{ ajax_block(partials, updates_url, 'usage') }}
|
||||
{{ show_more(
|
||||
url_for(".usage", service_id=current_service['id']),
|
||||
'See usage breakdown'
|
||||
) }}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
{% from "components/table.html" import list_table, field, right_aligned_field_heading, hidden_field_heading %}
|
||||
|
||||
{% call(item, row_number) list_table(
|
||||
jobs,
|
||||
caption="Recent batch jobs",
|
||||
empty_message='You haven’t sent any batch messages yet',
|
||||
field_headings=['File', 'Started', right_aligned_field_heading('Rows')]
|
||||
) %}
|
||||
{% call field() %}
|
||||
<a href="{{ url_for('.view_job', service_id=current_service.id, job_id=item.id) }}">{{ item.original_file_name }}</a>
|
||||
{{ item.created_at|format_datetime }}
|
||||
{% endcall %}
|
||||
{% call field() %}
|
||||
|
||||
{% endcall %}
|
||||
{% call field(align='right') %}
|
||||
{{ item.notification_count }}
|
||||
{% endcall %}
|
||||
{% endcall %}
|
||||
{% if more_jobs_to_show %}
|
||||
{% if current_user.has_permissions(['send_texts', 'send_emails', 'send_letters']) %}
|
||||
<p class="table-show-more-link">
|
||||
<a href="{{ url_for('.view_jobs', service_id=current_service.id) }}">See all sent batch messages</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -1,90 +0,0 @@
|
||||
{% from "components/big-number.html" import big_number, big_number_with_status %}
|
||||
{% from "components/show-more.html" import show_more %}
|
||||
{% from "components/message-count-label.html" import message_count_label %}
|
||||
{% from "components/table.html" import list_table, field, right_aligned_field_heading, hidden_field_heading %}
|
||||
|
||||
<div
|
||||
data-module="update-content"
|
||||
data-resource="{{url_for(".service_dashboard_updates", service_id=current_service.id)}}"
|
||||
data-key="today"
|
||||
data-interval-seconds="2"
|
||||
aria-live="polite"
|
||||
>
|
||||
<div class="grid-row">
|
||||
<div class="column-half">
|
||||
{{ big_number_with_status(
|
||||
statistics.emails_requested,
|
||||
message_count_label(statistics.emails_requested, 'email', suffix=''),
|
||||
statistics.emails_failed,
|
||||
statistics.get('emails_failure_rate', 0.0),
|
||||
statistics.get('emails_failure_rate', 0)|float > 3,
|
||||
failure_link=url_for(".view_notifications", service_id=current_service.id, message_type='email', status='failed'),
|
||||
link=url_for(".view_notifications", service_id=current_service.id, message_type='email', status='sending,delivered,failed')
|
||||
) }}
|
||||
</div>
|
||||
<div class="column-half">
|
||||
{{ big_number_with_status(
|
||||
statistics.sms_requested,
|
||||
message_count_label(statistics.sms_requested, 'sms', suffix=''),
|
||||
statistics.sms_failed,
|
||||
statistics.get('sms_failure_rate', 0.0),
|
||||
statistics.get('sms_failure_rate', 0)|float > 3,
|
||||
failure_link=url_for(".view_notifications", service_id=current_service.id, message_type='sms', status='failed'),
|
||||
link=url_for(".view_notifications", service_id=current_service.id, message_type='sms', status='sending,delivered,failed')
|
||||
) }}
|
||||
</div>
|
||||
<div class="column-whole">
|
||||
{{ show_more(
|
||||
url_for('.weekly', service_id=current_service.id),
|
||||
'Compare to previous weeks'
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if template_statistics|length %}
|
||||
{% include 'views/dashboard/template-statistics.html' %}
|
||||
{{ show_more(
|
||||
url_for('.template_history', service_id=current_service.id),
|
||||
'See all templates used this year'
|
||||
) }}
|
||||
{% endif %}
|
||||
|
||||
{% if jobs %}
|
||||
{% include 'views/dashboard/_jobs.html' %}
|
||||
{{ show_more(
|
||||
url_for('.view_jobs', service_id=current_service.id),
|
||||
'See all uploaded files'
|
||||
) }}
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.has_permissions(['manage_settings'], admin_override=True) %}
|
||||
<h2 class='heading-medium'>This year</h2>
|
||||
|
||||
<div class='grid-row'>
|
||||
<div class='column-half'>
|
||||
<div class="keyline-block">
|
||||
{{ big_number("Unlimited", 'free email allowance', smaller=True) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class='column-half'>
|
||||
<div class="keyline-block">
|
||||
{% if sms_chargeable %}
|
||||
{{ big_number(
|
||||
(sms_chargeable * sms_rate),
|
||||
'spent on text messages',
|
||||
currency="£",
|
||||
smaller=True
|
||||
) }}
|
||||
{% else %}
|
||||
{{ big_number(sms_allowance_remaining, 'free text messages left', smaller=True) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ show_more(
|
||||
url_for(".usage", service_id=current_service['id']),
|
||||
'See usage breakdown'
|
||||
) }}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
@@ -2,6 +2,7 @@
|
||||
{% from "components/banner.html" import banner %}
|
||||
{% from "components/sms-message.html" import sms_message %}
|
||||
{% from "components/email-message.html" import email_message %}
|
||||
{% from "components/ajax-block.html" import ajax_block %}
|
||||
|
||||
{% block page_title %}
|
||||
{{ uploaded_file_name }} – GOV.UK Notify
|
||||
@@ -28,10 +29,8 @@
|
||||
)}}
|
||||
{% endif %}
|
||||
|
||||
{% include 'partials/jobs/status.html' %}
|
||||
|
||||
{% include 'partials/jobs/count.html' %}
|
||||
|
||||
{% include 'partials/jobs/notifications.html' %}
|
||||
{{ ajax_block(partials, updates_url, 'status', finished=finished) }}
|
||||
{{ ajax_block(partials, updates_url, 'counts', finished=finished) }}
|
||||
{{ ajax_block(partials, updates_url, 'notifications', finished=finished) }}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user