Delete unused image, adding profile and minor jumper's page improvement

This commit is contained in:
Trullemans Gregory 2021-11-03 06:19:59 +01:00
parent 63037499df
commit 5070919d44
40 changed files with 313 additions and 188 deletions

View File

@ -30,7 +30,6 @@ ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
@ -42,6 +41,7 @@ INSTALLED_APPS = [
'jumpers',
'followup',
'tools',
'profiles',
]
MIDDLEWARE = [

View File

@ -18,16 +18,23 @@ from django.urls import include, path
import jumpers.urls
import Ultron.views
import profiles.urls
urlpatterns = [
# Profile list
path(r"profile/", include(profiles.urls.profile_urlpatterns)),
# Jumpers management
path(r"jumper/", include(jumpers.urls.jumper_urlpatterns)),
path(r"club/", include(jumpers.urls.club_urlpatterns)),
# path(r"search/", config.views.search, name="global_search"),
# login & logout
# path(r"login/", Ultron.views.login, name="login"),
# Login & logout
path(r"login/", Ultron.views.login, name="login"),
path(r"logout/", Ultron.views.logout, name="logout"),
path(r"", Ultron.views.home, name="home"),
# Administration
path('admin/', admin.site.urls),
]

View File

@ -13,40 +13,41 @@ from django.http import HttpResponseRedirect
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from profiles.models import Profile
# def login(request):
# """
# Formulaire d'authentifictation.
# """
# club_list = Club.objects.all()
# if request.method == "POST":
# username = request.POST["login"]
# password = request.POST["password"]
def login(request):
"""
Formulaire d'authentifictation.
"""
# user = authenticate(username=username, password=password)
if request.method == "POST":
username = request.POST["login"]
password = request.POST["password"]
# if user is not None: # Pq pas "if user:" ??
# if user.is_active:
# auth_login(request, user)
# try:
# profile = Profile.objects.get(user=user)
# request.session["profileid"] = profile.id
# request.session["template"] = profile.template_color
# request.session["sidebar"] = profile.sidebar_color
# request.session["is_sidebar_minified"] = profile.is_sidebar_minified
# except Exception:
# pass
# request.session["clubid"] = request.POST.get("clubid", None)
# return HttpResponseRedirect("/")
# else:
# context = {"message": "Account disabled.", "clubs": club_list}
# else:
# context = {"message": "Wrong login/password.", "clubs": club_list}
# else:
# context = {"clubs": club_list}
user = authenticate(username=username, password=password)
# return render(request, "login.html", context)
if user is not None: # Pq pas "if user:" ??
if user.is_active:
auth_login(request, user)
try:
profile = Profile.objects.get(user=user)
request.session["profileid"] = profile.id
request.session["template"] = profile.template_color
request.session["sidebar"] = profile.sidebar_color
request.session["is_sidebar_minified"] = profile.is_sidebar_minified
except Exception:
pass
request.session["clubid"] = request.POST.get("clubid", None)
return HttpResponseRedirect("/")
else:
context = {"message": "Account disabled."}
else:
context = {"message": "Wrong login/password."}
else:
context = {}
return render(request, "login.html", context)
@login_required

View File

@ -1,3 +1,4 @@
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
# Create your views here.

View File

@ -1,38 +1,36 @@
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from .models import Club, Jumper
from followup.models import Chrono, LearnedSkill
# @login_required
@login_required
@require_http_methods(["GET"])
def club_listing(request):
"""
Liste tous les clubs connus
"""Liste tous les clubs connus
"""
club_list = Club.objects.all()
context = {"club_list": club_list}
return render(request, "clubs/list.html", context)
# @login_required
@login_required
@require_http_methods(["GET"])
def jumper_listing(request):
"""
Liste tous les gymnasts connus
"""Liste tous les gymnasts connus
"""
jumper_list = Jumper.objects.all()
context = {"jumper_list": jumper_list}
return render(request, "jumpers/list.html", context)
# @login_required
@login_required
@require_http_methods(["GET"])
def jumper_details(request, jumperid):
"""
Récupère toutes les informations d'un gymnaste.
"""Récupère toutes les informations d'un gymnaste.
"""
jumper = get_object_or_404(Jumper, pk=jumperid)
learnedskills_list = LearnedSkill.objects.filter(jumper=jumperid).order_by('-date')[:8]

0
profiles/__init__.py Normal file
View File

12
profiles/admin.py Normal file
View File

@ -0,0 +1,12 @@
"""Administration des profils utilisateurs."""
from django.contrib import admin
from .models import Profile
class ProfileAdmin(admin.ModelAdmin):
model = Profile
list_display = ("user", "template_color", "sidebar_color")
admin.site.register(Profile, ProfileAdmin)

6
profiles/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class ProfileConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'profiles'

26
profiles/forms.py Normal file
View File

@ -0,0 +1,26 @@
# coding=UTF-8
from django import forms
from datetime import date
from .models import Profile
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = (
"template_color",
"sidebar_color",
"is_sidebar_minified",
)
widgets = {
"template_color": forms.Select(attrs={"class": "form-control"}),
"sidebar_color": forms.Select(attrs={"class": "form-control"}),
"is_sidebar_minified": forms.CheckboxInput(
attrs={
"class": "bootstrap-switch mt-0",
"data-on-label": "<i class='tim-icons icon-check-2 text-success'></i>",
"data-off-label": "<i class='tim-icons icon-simple-remove text-danger'></i>",
}
),
}

View File

@ -0,0 +1,27 @@
# Generated by Django 3.2.8 on 2021-11-02 15:27
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Profile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('template_color', models.PositiveSmallIntegerField(choices=[(0, 'Dark'), (1, 'Light')], default=0, verbose_name='Template')),
('sidebar_color', models.PositiveSmallIntegerField(choices=[(0, 'Purple'), (1, 'Blue'), (2, 'Green'), (3, 'Orange'), (4, 'Red')], default=0, verbose_name='Sidebar')),
('is_sidebar_minified', models.BooleanField(default=False)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

43
profiles/models.py Normal file
View File

@ -0,0 +1,43 @@
"""Extension et gestion des profils utilisateurs.
Les profils peuvent enregistrer les informations suivantes:
* le type de template,
* la couleur de la barre de navigation
* si la barre de navigation doit être cachée ou non
"""
from django.contrib.auth import get_user_model
from django.db import models
from django.dispatch import receiver
User = get_user_model()
class Profile(models.Model):
"""Classe étendant les informations des utilisateurs/entraineurs de l'application.
References:
* https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html
"""
TEMPLATE_CHOICES = ((0, "Dark"), (1, "Light"))
SIDEBAR_CHOICES = (
(0, "Purple"),
(1, "Blue"),
(2, "Green"),
(3, "Orange"),
(4, "Red"),
)
user = models.OneToOneField(User, on_delete=models.CASCADE)
template_color = models.PositiveSmallIntegerField(
choices=TEMPLATE_CHOICES, verbose_name="Template", default=0
)
sidebar_color = models.PositiveSmallIntegerField(
choices=SIDEBAR_CHOICES, verbose_name="Sidebar", default=0
)
is_sidebar_minified = models.BooleanField(default=False)
def __str__(self):
return "%s %s" % (self.user.first_name, self.user.last_name)

3
profiles/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

10
profiles/urls.py Normal file
View File

@ -0,0 +1,10 @@
"""URLs définissant la gestion des profils utilisateurs."""
from django.urls import path, re_path
from . import views
profile_urlpatterns = [
path(r"edit/", views.profile_update, name="profile_update"),
]

45
profiles/views.py Normal file
View File

@ -0,0 +1,45 @@
"""Vues de gestion des profils utilisateurs."""
from django.contrib.auth.decorators import login_required
from django.contrib.auth import get_user_model
from django.db.models import Q
from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import get_object_or_404, render
from django.views.decorators.http import require_http_methods
from .forms import ProfileForm
from .models import Profile
User = get_user_model()
@login_required
@require_http_methods(["GET", "POST"])
def profile_update(request):
"""Modification du profil de l'utilisateur connecté
"""
try:
profile = request.user.profile
except Exception: # don't do this !
profile = Profile.objects.create(user=request.user)
if request.method == 'POST':
form = ProfileForm(request.POST, instance=profile)
if form.is_valid():
form.save()
request.session['template'] = profile.template_color
request.session['sidebar'] = profile.sidebar_color
request.session['is_sidebar_minified'] = profile.is_sidebar_minified
return HttpResponseRedirect("/")
else:
form = ProfileForm(instance=profile)
context = {
"form": form,
}
return render(request, 'profiles/update.html', context)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

View File

@ -42,8 +42,7 @@
<link href='https://api.mapbox.com/mapbox.js/v3.2.0/mapbox.css' rel='stylesheet' />
</head>
<!-- <body class="sidebar-mini {% if request.session.template == 1 %}white-content{% endif %}"> -->
<body class="sidebar-mini white-content">
<body class="sidebar-mini {% if request.session.template == 1 %}white-content{% endif %}">
<div class="wrapper">
<div class="navbar-minimize-fixed">
<button class="minimize-sidebar btn btn-link btn-just-icon">
@ -100,7 +99,7 @@
</button>
<div class="collapse navbar-collapse" id="navigation">
<ul class="navbar-nav ml-auto">
<li class="search-bar input-group">
<!-- <li class="search-bar input-group">
<button class="btn btn-link" id="search-button" data-toggle="modal" data-target="#searchModal">
<i class="tim-icons icon-zoom-split"></i>
<span class="d-lg-none d-md-block">Search</span>
@ -114,16 +113,19 @@
<ul class="dropdown-menu dropdown-menu-right dropdown-navbar">
</ul>
</li>
</li> -->
<li class="dropdown nav-item">
<a href="#" class="dropdown-toggle nav-link" data-toggle="dropdown">
<div class="photo">
<img src="{% static '/img/mike.jpg' %}" alt="Profile Photo">
<img src="{% static '/img/default-avatar.png' %}" alt="Profile Photo">
</div>
<b class="caret d-none d-lg-block d-xl-block"></b>
<p class="d-lg-none">Log out</p>
</a>
<ul class="dropdown-menu dropdown-navbar">
<li class="nav-link">
<a href="{% url 'profile_update' %}" class="nav-item dropdown-item">Profile</a>
</li>
<li class="dropdown-divider"></li>
<li class="nav-link">
<a href="{% url 'logout' %}" class="nav-item dropdown-item">Log out</a>
@ -261,7 +263,7 @@
sidebar_mini_active = false;
{% endif %}
{% if request.session.template == 0 %}
white_color = true;
white_color = false;
{% else %}
white_color = true;
{% endif %}
@ -279,7 +281,7 @@
{% elif request.session.sidebar == 4 %}
color = 'red';
{% else %}
color = 'red'
color = 'purple'
{% endif %}
$sidebar.attr('data', color);
$main_panel.attr('data', color);

View File

@ -10,23 +10,10 @@
<div class="col-md-9">
<div class="card">
<div class="card-header">
<h4>Hi {{ user.first_name }} !</h4>
<h4>Hi {{ user.username }} !</h4>
</div>
<div class="card-body">
{% if error %}
<p class="text-danger">{{ error }}</p>
{% endif %}
{% if number_of_courses %}
We are in the <b>{{ week_number }}th week</b> of the season and you've {{ number_of_courses }} courses the next two days : <br />
<ul>
{% for course in courses_list %}
<li><a href="/course/{{ course.id }}/(date)">{{ course.get_iso_day_number_display }} from {{ course.hour_begin|time:"H:i" }} to {{ course.hour_end|time:"H:i" }} ({{ course.club.acronym }})</a></li >
{% endfor %}
</ul>
{% endif %}
{% if number_unreaded_message %}
You have <a href="{% url 'received_messages' %}">{{ number_unreaded_message }}</a> unread messsage{% if number_unreaded_message > 1 %}s{% endif %}.
{% endif %}
</div>
</div>
</div>
@ -34,7 +21,7 @@
<div class="card mb-2">
<div class="card-body">
<div class="w-lg m-x-auto">
<canvas id="chartjs_year_completude" style="width: 238px; height: 238px"></canvas>
(line 1 cadre 2)
</div>
</div>
</div>
@ -45,28 +32,10 @@
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h4 class="">Next events</h4>
<h4 class="">(line 2 cadre 1)</h4>
</div>
<div class="card-body">
{% if event_list %}
<table class="table table-condensed table-sm table-striped">
<tbody>
{% for event in event_list %}
<tr>
<td class="text-left">
<a href="{% url 'event_details' event.id %}">{{ event.name }}</a>
</td>
<td>
{{ event.datebegin | date:"j M"}}
</td>
<td class="text-right"><span class="text-{% if event.number_of_week_from_today > 12 %}success{% elif event.number_of_week_from_today > 9 %}info{% elif event.number_of_week_from_today > 6 %}warning{% else %}danger{% endif %}">{{event.number_of_week_from_today}}w</span></td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
No planified event.
{% endif %}
</div>
</div>
</div>
@ -74,29 +43,10 @@
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h4 class="">Next Unavailability</h4>
<h4 class="">(line 2 celle 2)</h4>
</div>
<div class="card-body">
{% if unavailable_list %}
<table class="table table-condensed table-sm table-striped">
<tbody>
{% for unavailable in unavailable_list %}
<tr>
<td class="text-left">
<a class="list-group-item" href="/unavailability/{{unavailable.id}}">
{% if unavailable.datebegin == unavailable.dateend %}
{{ unavailable.datebegin | date:"j M" }}
{% else %}
{{ unavailable.datebegin | date:"j M"}} - {{ unavailable.dateend | date:"j M"}}
{% endif %}
</a>
</td>
{% endfor %}
</tbody>
</table>
{% else %}
No unavailability planified.
{% endif %}
</div>
</div>
</div>
@ -104,74 +54,17 @@
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h4 class="">Next birthdays</h4>
<h4 class="">(line 2 cadre 3)</h4>
</div>
<div class="card-body">
<table class="table table-condensed table-sm table-striped">
<tbody>
{% for gymnast in birthday_list %}
<tr>
<td class="text-left"><a href="{% url 'gymnast_details' gymnast.id %}">{{ gymnast.firstname }}</a></td>
<td class="">{{ gymnast.birthdate | date:"j M"}}</td>
<td class="text-right">{{ gymnast.nextAge }} years</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- <div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h4>Courses</h4>
</div>
<div class="card-body">
{% for course in course_list %}
<a class="list-group-item" href="#">
<span class="list-group-progress" style="width: {% widthratio course.2 course.1 100 %}%;"></span>
<span class="pull-right text-muted">{{ course.2 }}/{{ course.1 }}</span>
{{ course.0.get_diso_day_number_display }} <span class="text-muted">({{ course.0.hour_begin}} - {{ course.0.hour_end }})</span>
</a>
{% endfor %}
</div>
</div>
</div>
</div> -->
{% endblock %}
{% block footerscript %}
<script type="text/javascript">
new Chart(document.getElementById("chartjs_year_completude"),{
type: "doughnut",
data: {
datasets:[
{
borderWidth: 1,
data:[
{{ donecourses }},
{{ courses_left }}
],
backgroundColor:[
"#1bc98e", /*"#1ca8dd",*/
"#FF2F92" /*"#1bc98e"*/
]
}
],
labels: [
'Given courses ',
'Total courses '
],
},
options: {
legend: {
display: false,
},
cutoutPercentage: 55,
maintainAspectRatio: false,
},
});
</script>
{% endblock%}

View File

@ -35,17 +35,17 @@
<div class="col-12 col-sm-4 col-md-4 col-lg-4">
<div class="table-responsive pb-0"></div>
{% if learnedskills_list %}
<table class="table tablesorter table-striped table-condensed" data-sort="table" id="maintable">
<table class="table tablesorter table-striped table-condensed" data-sort="table" id="skilltable">
<thead>
<tr>
<th class="header text-left" style="width: 45%">Date</th>
<th class="header text-left" style="width: 65%">Skill</th>
<th class="header text-left" style="width: 40%">Date</th>
<th class="header text-left" style="width: 60%">Skill</th>
</tr>
</thead>
<tbody>
{% for skill in learnedskills_list %}
<tr>
<td class="text-left">{{ skill.date | date:"d F Y" }}</a></td>
<td class="text-left">{{ skill.date | date:"d-m-Y" }}</a></td>
<td class="text-left">{{ skill.skill }}</a></td>
</tr>
{% endfor %}
@ -63,18 +63,18 @@
<div class="col-12 col-sm-4 col-md-4 card">
<div class="table-responsive pb-0">
{% if chronos_list %}
<table class="table tablesorter table-striped table-condensed" data-sort="table" id="maintable">
<table class="table tablesorter table-striped table-condensed" data-sort="table" id="chronotable">
<thead>
<tr>
<th class="header text-left" style="width: 45%">Date</th>
<th class="header text-left" style="width: 20%">Type</th>
<th class="header text-left" style="width: 35%">Tof</th>
<th class="header text-left" style="width: 35%">Date</th>
<th class="header text-left" style="width: 35%">Type</th>
<th class="header text-left" style="width: 30%">Tof</th>
</tr>
</thead>
<tbody>
{% for chrono in chronos_list %}
<tr>
<td class="text-left">{{ chrono.date | date:"d F Y" }}</a></td>
<td class="text-left">{{ chrono.date | date:"d-m-Y" }}</a></td>
<td class="text-left">{{ chrono.get_type_display }}</a></td>
<td class="text-right">{{ chrono.tof }}</a></td>
</tr>
@ -84,7 +84,7 @@
{% else %}
<table class="table">
<tr>
<td>There are no places corresponding to your criterias</td>
<td>No information found</td>
</tr>
</table>
{% endif %}
@ -99,8 +99,20 @@
</div>
</div>
</div>
{% endblock %}
{% block footerscript %}
<script type="text/javascript">
$(function(){
$('#skilltable').tablesorter({
dateFormat: "uk",
})
$('#chronotable').tablesorter({
dateFormat: "uk",
})
});
new Chart(document.getElementById("chartjs_routine"),{
type: 'line',
data:{

View File

@ -39,6 +39,7 @@
<div class="container">
<div class="col-lg-4 col-md-6 ml-auto mr-auto">
<form class="form" action="/login/" method="post" if="formulaire">
{% csrf_token %}
<div class="card card-login card-white">
<div class="card-header">
<img src="{% static "img/card-danger.png" %}" alt="">
@ -61,18 +62,6 @@
</div>
<input type="password" name="password" class="form-control" id="password" placeholder="Password">
</div>
<!-- {% if clubs.count > 0 %}
<div class="form-group">
<label for="password" class="col-sm-2 control-label">Club</label>
<div class="col-sm-6">
<select name="clubid" class="custom-select custom-select-sm">
{% for club in clubs %}
<option value="{{club.id}}">{{club}}</option>
{% endfor %}
</select>
</div>
</div>
{% endif %} -->
{% if message %}
<p class="text-danger"><b>{{message}}</b></p>
{% endif %}

View File

@ -0,0 +1,50 @@
{% extends "base.html" %}
{% block content %}
<div class="row justify-content-center">
<div class="col-12 col-sm-10 col-md-7 col-lg-6 col-xl-5">
<div class="card">
<div class="card-header">
<h4 class="card-title">Edit User Profile</h4>
</div>
<div class="card-body">
<form action="{% url 'profile_update' %}" method="post" class="form-horizontal" id="formulaire" name="formulaire">
{% csrf_token %}
<div class="form-group row">
<label for="id_template_color" class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4 col-form-label">Template Black</label>
<div class="col-8 col-sm-7 col-md-6 col-lg-5 col-xl-4 {% if form.template_color.errors %}has-danger{% endif %}">
{{ form.template_color }}
{% if form.template_color.errors %}&nbsp;<span class="btn btn-sm btn-danger-outline">{% for error in form.template_color.errors %}{{error}}{% endfor %}</span>{% endif %}
</div>
</div>
<div class="form-group row">
<label for="id_is_sidebar_minified" class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4 col-form-label">Sidebar active</label>
<div class="col-8 col-sm-2 col-md-2 col-lg-2 col-xl-2 {% if form.is_sidebar_minified.errors %}has-danger{% endif %}">
{{ form.is_sidebar_minified }}
{% if form.is_sidebar_minified.errors %}&nbsp;<span class="btn btn-sm btn-danger-outline">{% for error in form.is_sidebar_minified.errors %}{{error}}{% endfor %}</span>{% endif %}
</div>
</div>
<div class="form-group row">
<label for="id_sidebar_color" class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4 col-form-label">Sidebar Color</label>
<div class="col-8 col-sm-7 col-md-6 col-lg-5 col-xl-4 {% if form.sidebar_color.errors %}has-danger{% endif %}">
{{ form.sidebar_color }}
{% if form.sidebar_color.errors %}&nbsp;<span class="btn btn-sm btn-danger-outline">{% for error in form.sidebar_color.errors %}{{error}}{% endfor %}</span>{% endif %}
</div>
</div>
<br />
<div class="form-group text-center">
<input type="submit" value="Save" class="btn btn-warning" />
</div>
</form>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$(function() {
blackDashboard.initDateTimePicker();
});
</script>
{% endblock %}