manager: use independent date/time inputs for datetime input (#27013)
This commit is contained in:
parent
eda5a25b56
commit
2bf1f8672a
|
@ -38,17 +38,7 @@ from chrono.agendas.models import (
|
|||
)
|
||||
|
||||
from . import widgets
|
||||
|
||||
|
||||
DATETIME_OPTIONS = {
|
||||
'weekStart': 1,
|
||||
'autoclose': True,
|
||||
}
|
||||
|
||||
|
||||
class DateTimeWidget(widgets.DateTimeWidget):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DateTimeWidget, self).__init__(*args, options=DATETIME_OPTIONS, **kwargs)
|
||||
from .widgets import DateTimeWidget
|
||||
|
||||
|
||||
class AgendaAddForm(forms.ModelForm):
|
||||
|
|
|
@ -1,372 +0,0 @@
|
|||
/*!
|
||||
* Datetimepicker for Bootstrap
|
||||
*
|
||||
* Copyright 2012 Stefan Petre
|
||||
* Improvements by Andrew Rowls
|
||||
* Licensed under the Apache License v2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*/
|
||||
.datetimepicker {
|
||||
padding: 4px;
|
||||
margin-top: 1px;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
direction: ltr;
|
||||
/*.dow {
|
||||
border-top: 1px solid #ddd !important;
|
||||
}*/
|
||||
|
||||
}
|
||||
.datetimepicker-inline {
|
||||
width: 220px;
|
||||
}
|
||||
.datetimepicker.datetimepicker-rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
.datetimepicker.datetimepicker-rtl table tr td span {
|
||||
float: right;
|
||||
}
|
||||
.datetimepicker-dropdown, .datetimepicker-dropdown-left {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.datetimepicker-dropdown:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
border-left: 7px solid transparent;
|
||||
border-right: 7px solid transparent;
|
||||
border-bottom: 7px solid #ccc;
|
||||
border-bottom-color: rgba(0, 0, 0, 0.2);
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 6px;
|
||||
}
|
||||
.datetimepicker-dropdown:after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid #ffffff;
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: 7px;
|
||||
}
|
||||
.datetimepicker-dropdown-left:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
border-left: 7px solid transparent;
|
||||
border-right: 7px solid transparent;
|
||||
border-bottom: 7px solid #ccc;
|
||||
border-bottom-color: rgba(0, 0, 0, 0.2);
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
right: 6px;
|
||||
}
|
||||
.datetimepicker-dropdown-left:after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid #ffffff;
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: 7px;
|
||||
}
|
||||
.datetimepicker > div {
|
||||
display: none;
|
||||
}
|
||||
.datetimepicker.minutes div.datetimepicker-minutes {
|
||||
display: block;
|
||||
}
|
||||
.datetimepicker.hours div.datetimepicker-hours {
|
||||
display: block;
|
||||
}
|
||||
.datetimepicker.days div.datetimepicker-days {
|
||||
display: block;
|
||||
}
|
||||
.datetimepicker.months div.datetimepicker-months {
|
||||
display: block;
|
||||
}
|
||||
.datetimepicker.years div.datetimepicker-years {
|
||||
display: block;
|
||||
}
|
||||
.datetimepicker table {
|
||||
margin: 0;
|
||||
}
|
||||
.datetimepicker td,
|
||||
.datetimepicker th {
|
||||
text-align: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
}
|
||||
.table-striped .datetimepicker table tr td,
|
||||
.table-striped .datetimepicker table tr th {
|
||||
background-color: transparent;
|
||||
}
|
||||
.datetimepicker table tr td.minute:hover {
|
||||
background: #eeeeee;
|
||||
cursor: pointer;
|
||||
}
|
||||
.datetimepicker table tr td.hour:hover {
|
||||
background: #eeeeee;
|
||||
cursor: pointer;
|
||||
}
|
||||
.datetimepicker table tr td.day:hover {
|
||||
background: #eeeeee;
|
||||
cursor: pointer;
|
||||
}
|
||||
.datetimepicker table tr td.old,
|
||||
.datetimepicker table tr td.new {
|
||||
color: #999999;
|
||||
}
|
||||
.datetimepicker table tr td.disabled,
|
||||
.datetimepicker table tr td.disabled:hover {
|
||||
background: none;
|
||||
color: #999999;
|
||||
cursor: default;
|
||||
}
|
||||
.datetimepicker table tr td.today,
|
||||
.datetimepicker table tr td.today:hover,
|
||||
.datetimepicker table tr td.today.disabled,
|
||||
.datetimepicker table tr td.today.disabled:hover {
|
||||
background-color: #fde19a;
|
||||
background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a);
|
||||
background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a));
|
||||
background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a);
|
||||
background-image: -o-linear-gradient(top, #fdd49a, #fdf59a);
|
||||
background-image: linear-gradient(top, #fdd49a, #fdf59a);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);
|
||||
border-color: #fdf59a #fdf59a #fbed50;
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
||||
}
|
||||
.datetimepicker table tr td.today:hover,
|
||||
.datetimepicker table tr td.today:hover:hover,
|
||||
.datetimepicker table tr td.today.disabled:hover,
|
||||
.datetimepicker table tr td.today.disabled:hover:hover,
|
||||
.datetimepicker table tr td.today:active,
|
||||
.datetimepicker table tr td.today:hover:active,
|
||||
.datetimepicker table tr td.today.disabled:active,
|
||||
.datetimepicker table tr td.today.disabled:hover:active,
|
||||
.datetimepicker table tr td.today.active,
|
||||
.datetimepicker table tr td.today:hover.active,
|
||||
.datetimepicker table tr td.today.disabled.active,
|
||||
.datetimepicker table tr td.today.disabled:hover.active,
|
||||
.datetimepicker table tr td.today.disabled,
|
||||
.datetimepicker table tr td.today:hover.disabled,
|
||||
.datetimepicker table tr td.today.disabled.disabled,
|
||||
.datetimepicker table tr td.today.disabled:hover.disabled,
|
||||
.datetimepicker table tr td.today[disabled],
|
||||
.datetimepicker table tr td.today:hover[disabled],
|
||||
.datetimepicker table tr td.today.disabled[disabled],
|
||||
.datetimepicker table tr td.today.disabled:hover[disabled] {
|
||||
background-color: #fdf59a;
|
||||
}
|
||||
.datetimepicker table tr td.today:active,
|
||||
.datetimepicker table tr td.today:hover:active,
|
||||
.datetimepicker table tr td.today.disabled:active,
|
||||
.datetimepicker table tr td.today.disabled:hover:active,
|
||||
.datetimepicker table tr td.today.active,
|
||||
.datetimepicker table tr td.today:hover.active,
|
||||
.datetimepicker table tr td.today.disabled.active,
|
||||
.datetimepicker table tr td.today.disabled:hover.active {
|
||||
background-color: #fbf069 \9;
|
||||
}
|
||||
.datetimepicker table tr td.active,
|
||||
.datetimepicker table tr td.active:hover,
|
||||
.datetimepicker table tr td.active.disabled,
|
||||
.datetimepicker table tr td.active.disabled:hover {
|
||||
background-color: #006dcc;
|
||||
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
|
||||
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: linear-gradient(top, #0088cc, #0044cc);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
|
||||
border-color: #0044cc #0044cc #002a80;
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
||||
color: #fff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.datetimepicker table tr td.active:hover,
|
||||
.datetimepicker table tr td.active:hover:hover,
|
||||
.datetimepicker table tr td.active.disabled:hover,
|
||||
.datetimepicker table tr td.active.disabled:hover:hover,
|
||||
.datetimepicker table tr td.active:active,
|
||||
.datetimepicker table tr td.active:hover:active,
|
||||
.datetimepicker table tr td.active.disabled:active,
|
||||
.datetimepicker table tr td.active.disabled:hover:active,
|
||||
.datetimepicker table tr td.active.active,
|
||||
.datetimepicker table tr td.active:hover.active,
|
||||
.datetimepicker table tr td.active.disabled.active,
|
||||
.datetimepicker table tr td.active.disabled:hover.active,
|
||||
.datetimepicker table tr td.active.disabled,
|
||||
.datetimepicker table tr td.active:hover.disabled,
|
||||
.datetimepicker table tr td.active.disabled.disabled,
|
||||
.datetimepicker table tr td.active.disabled:hover.disabled,
|
||||
.datetimepicker table tr td.active[disabled],
|
||||
.datetimepicker table tr td.active:hover[disabled],
|
||||
.datetimepicker table tr td.active.disabled[disabled],
|
||||
.datetimepicker table tr td.active.disabled:hover[disabled] {
|
||||
background-color: #0044cc;
|
||||
}
|
||||
.datetimepicker table tr td.active:active,
|
||||
.datetimepicker table tr td.active:hover:active,
|
||||
.datetimepicker table tr td.active.disabled:active,
|
||||
.datetimepicker table tr td.active.disabled:hover:active,
|
||||
.datetimepicker table tr td.active.active,
|
||||
.datetimepicker table tr td.active:hover.active,
|
||||
.datetimepicker table tr td.active.disabled.active,
|
||||
.datetimepicker table tr td.active.disabled:hover.active {
|
||||
background-color: #003399 \9;
|
||||
}
|
||||
.datetimepicker table tr td span {
|
||||
display: block;
|
||||
width: 23%;
|
||||
height: 54px;
|
||||
line-height: 54px;
|
||||
float: left;
|
||||
margin: 1%;
|
||||
cursor: pointer;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.datetimepicker .datetimepicker-hours span {
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
}
|
||||
.datetimepicker .datetimepicker-hours table tr td span.hour_am,
|
||||
.datetimepicker .datetimepicker-hours table tr td span.hour_pm {
|
||||
width: 14.6%;
|
||||
}
|
||||
.datetimepicker .datetimepicker-hours fieldset legend,
|
||||
.datetimepicker .datetimepicker-minutes fieldset legend {
|
||||
margin-bottom: inherit;
|
||||
line-height: 30px;
|
||||
}
|
||||
.datetimepicker .datetimepicker-minutes span {
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
}
|
||||
.datetimepicker table tr td span:hover {
|
||||
background: #eeeeee;
|
||||
}
|
||||
.datetimepicker table tr td span.disabled,
|
||||
.datetimepicker table tr td span.disabled:hover {
|
||||
background: none;
|
||||
color: #999999;
|
||||
cursor: default;
|
||||
}
|
||||
.datetimepicker table tr td span.active,
|
||||
.datetimepicker table tr td span.active:hover,
|
||||
.datetimepicker table tr td span.active.disabled,
|
||||
.datetimepicker table tr td span.active.disabled:hover {
|
||||
background-color: #006dcc;
|
||||
background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
|
||||
background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: -o-linear-gradient(top, #0088cc, #0044cc);
|
||||
background-image: linear-gradient(top, #0088cc, #0044cc);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
|
||||
border-color: #0044cc #0044cc #002a80;
|
||||
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
|
||||
color: #fff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.datetimepicker table tr td span.active:hover,
|
||||
.datetimepicker table tr td span.active:hover:hover,
|
||||
.datetimepicker table tr td span.active.disabled:hover,
|
||||
.datetimepicker table tr td span.active.disabled:hover:hover,
|
||||
.datetimepicker table tr td span.active:active,
|
||||
.datetimepicker table tr td span.active:hover:active,
|
||||
.datetimepicker table tr td span.active.disabled:active,
|
||||
.datetimepicker table tr td span.active.disabled:hover:active,
|
||||
.datetimepicker table tr td span.active.active,
|
||||
.datetimepicker table tr td span.active:hover.active,
|
||||
.datetimepicker table tr td span.active.disabled.active,
|
||||
.datetimepicker table tr td span.active.disabled:hover.active,
|
||||
.datetimepicker table tr td span.active.disabled,
|
||||
.datetimepicker table tr td span.active:hover.disabled,
|
||||
.datetimepicker table tr td span.active.disabled.disabled,
|
||||
.datetimepicker table tr td span.active.disabled:hover.disabled,
|
||||
.datetimepicker table tr td span.active[disabled],
|
||||
.datetimepicker table tr td span.active:hover[disabled],
|
||||
.datetimepicker table tr td span.active.disabled[disabled],
|
||||
.datetimepicker table tr td span.active.disabled:hover[disabled] {
|
||||
background-color: #0044cc;
|
||||
}
|
||||
.datetimepicker table tr td span.active:active,
|
||||
.datetimepicker table tr td span.active:hover:active,
|
||||
.datetimepicker table tr td span.active.disabled:active,
|
||||
.datetimepicker table tr td span.active.disabled:hover:active,
|
||||
.datetimepicker table tr td span.active.active,
|
||||
.datetimepicker table tr td span.active:hover.active,
|
||||
.datetimepicker table tr td span.active.disabled.active,
|
||||
.datetimepicker table tr td span.active.disabled:hover.active {
|
||||
background-color: #003399 \9;
|
||||
}
|
||||
.datetimepicker table tr td span.old {
|
||||
color: #999999;
|
||||
}
|
||||
.datetimepicker th.switch {
|
||||
width: 145px;
|
||||
}
|
||||
.datetimepicker thead tr:first-child th,
|
||||
.datetimepicker tfoot tr:first-child th {
|
||||
cursor: pointer;
|
||||
}
|
||||
.datetimepicker thead tr:first-child th:hover,
|
||||
.datetimepicker tfoot tr:first-child th:hover {
|
||||
background: #eeeeee;
|
||||
}
|
||||
.input-append.date .add-on i,
|
||||
.input-prepend.date .add-on i {
|
||||
cursor: pointer;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
float: left;
|
||||
min-width: 160px;
|
||||
padding: 5px 0;
|
||||
margin: 2px 0 0;
|
||||
list-style: none;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #cccccc;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.icon-arrow-left:before {
|
||||
content: "←";
|
||||
}
|
||||
|
||||
.icon-arrow-right:before {
|
||||
content: "→";
|
||||
}
|
|
@ -273,3 +273,7 @@ ul.objects-list.single-links li a.link-action-icon.refresh {
|
|||
content: "\f021"; /* refresh */
|
||||
}
|
||||
}
|
||||
|
||||
div.ui-dialog form p span.datetime input {
|
||||
width: auto;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,18 +0,0 @@
|
|||
/**
|
||||
* French translation for bootstrap-datetimepicker
|
||||
* Nico Mollet <nico.mollet@gmail.com>
|
||||
*/
|
||||
;(function($){
|
||||
$.fn.datetimepicker.dates['fr'] = {
|
||||
days: ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"],
|
||||
daysShort: ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"],
|
||||
daysMin: ["D", "L", "Ma", "Me", "J", "V", "S", "D"],
|
||||
months: ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
|
||||
monthsShort: ["Jan", "Fev", "Mar", "Avr", "Mai", "Jui", "Jul", "Aou", "Sep", "Oct", "Nov", "Dec"],
|
||||
today: "Aujourd'hui",
|
||||
suffix: [],
|
||||
meridiem: ["am", "pm"],
|
||||
weekStart: 1,
|
||||
format: "dd/mm/yyyy"
|
||||
};
|
||||
}(jQuery));
|
|
@ -3,10 +3,7 @@
|
|||
|
||||
{% block extrascripts %}
|
||||
{{ block.super }}
|
||||
<link rel="stylesheet" type="text/css" media="all" href="{% static 'css/datetimepicker.css' %}" />
|
||||
<script src="{% static 'js/chrono.manager.js' %}"></script>
|
||||
<script src="{% static 'js/bootstrap-datetimepicker.js' %}"></script>
|
||||
<script src="{% static 'js/locales/bootstrap-datetimepicker.fr.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block page-title %}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<span class="datetime">
|
||||
<input type="date" name="{{ widget.name }}$date" {% if widget.value.date != None %} value="{{ widget.value.date }}"{% endif %}{% include "django/forms/widgets/attrs.html" %} />
|
||||
<input type="time" name="{{ widget.name }}$time" pattern="[0-9]{2}:[0-9]{2}" step="300" {% if widget.value.time != None %} value="{{ widget.value.time }}"{% endif %}{% include "django/forms/widgets/attrs.html" %} />
|
||||
</span>
|
|
@ -1,134 +1,49 @@
|
|||
# Bootstrap django-datetime-widget is a simple and clean widget for DateField,
|
||||
# Timefiled and DateTimeField in Django framework. It is based on Bootstrap
|
||||
# datetime picker, supports Bootstrap 2
|
||||
# chrono - agendas system
|
||||
# Copyright (C) 2016-2019 Entr'ouvert
|
||||
#
|
||||
# https://github.com/asaglimbeni/django-datetime-widget
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# License: BSD
|
||||
# Initial Author: Alfredo Saglimbeni
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import json
|
||||
import re
|
||||
import uuid
|
||||
|
||||
import datetime
|
||||
|
||||
from django.forms.widgets import DateTimeInput, TimeInput, SelectMultiple
|
||||
from django.utils.formats import get_language
|
||||
from django.utils import dateparse
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
DATE_FORMAT_JS_PY_MAPPING = {
|
||||
'P': '%p',
|
||||
'ss': '%S',
|
||||
'ii': '%M',
|
||||
'hh': '%H',
|
||||
'HH': '%I',
|
||||
'dd': '%d',
|
||||
'mm': '%m',
|
||||
'yy': '%y',
|
||||
'yyyy': '%Y',
|
||||
}
|
||||
|
||||
DATE_FORMAT_TO_PYTHON_REGEX = re.compile(r'\b(' + '|'.join(DATE_FORMAT_JS_PY_MAPPING.keys()) + r')\b')
|
||||
class DateTimeWidget(DateTimeInput):
|
||||
template_name = 'chrono/widget_datetime.html'
|
||||
|
||||
def format_value(self, value):
|
||||
if value:
|
||||
x = {
|
||||
'date': value.date().strftime('%Y-%m-%d'),
|
||||
'time': value.time().strftime('%H:%M'),
|
||||
}
|
||||
return x
|
||||
return None
|
||||
|
||||
DATE_FORMAT_PY_JS_MAPPING = {
|
||||
'%M': 'ii',
|
||||
'%m': 'mm',
|
||||
'%I': 'HH',
|
||||
'%H': 'hh',
|
||||
'%d': 'dd',
|
||||
'%Y': 'yyyy',
|
||||
'%y': 'yy',
|
||||
'%p': 'P',
|
||||
'%S': 'ss',
|
||||
}
|
||||
|
||||
DATE_FORMAT_TO_JS_REGEX = re.compile(r'(?<!\w)(' + '|'.join(DATE_FORMAT_PY_JS_MAPPING.keys()) + r')\b')
|
||||
|
||||
|
||||
BOOTSTRAP_INPUT_TEMPLATE = """
|
||||
<div id="%(id)s" class="controls input-append date">
|
||||
%(rendered_widget)s
|
||||
%(clear_button)s
|
||||
<span class="add-on"><i class="icon-th"></i></span>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$("#%(id)s").datetimepicker({%(options)s});
|
||||
</script>
|
||||
"""
|
||||
|
||||
CLEAR_BTN_TEMPLATE = """<span class="add-on"><i class="icon-remove"></i></span>"""
|
||||
|
||||
|
||||
class PickerWidgetMixin(object):
|
||||
|
||||
format_name = None
|
||||
glyphicon = None
|
||||
|
||||
def __init__(self, attrs=None, options=None, usel10n=None):
|
||||
|
||||
if attrs is None:
|
||||
attrs = {'readonly': ''}
|
||||
|
||||
self.options = options
|
||||
if get_language():
|
||||
self.options['language'] = get_language().split('-')[0]
|
||||
|
||||
# We're not doing localisation, get the Javascript date format provided by the user,
|
||||
# with a default, and convert it to a Python data format for later string parsing
|
||||
date_format = self.options['format']
|
||||
self.format = DATE_FORMAT_TO_PYTHON_REGEX.sub(
|
||||
lambda x: DATE_FORMAT_JS_PY_MAPPING[x.group()], date_format
|
||||
)
|
||||
|
||||
super(PickerWidgetMixin, self).__init__(attrs, format=self.format)
|
||||
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
final_attrs = self.build_attrs(attrs)
|
||||
rendered_widget = super(PickerWidgetMixin, self).render(name, value, final_attrs, renderer=renderer)
|
||||
|
||||
# if not set, autoclose have to be true.
|
||||
self.options.setdefault('autoclose', True)
|
||||
|
||||
# Build javascript options out of python dictionary
|
||||
options_list = []
|
||||
for key, value in iter(self.options.items()):
|
||||
options_list.append("%s: %s" % (key, json.dumps(value)))
|
||||
|
||||
js_options = ",\n".join(options_list)
|
||||
|
||||
# Use provided id or generate hex to avoid collisions in document
|
||||
id = final_attrs.get('id', uuid.uuid4().hex)
|
||||
|
||||
return mark_safe(
|
||||
BOOTSTRAP_INPUT_TEMPLATE
|
||||
% dict(
|
||||
id=id,
|
||||
rendered_widget=rendered_widget,
|
||||
clear_button=CLEAR_BTN_TEMPLATE if self.options.get('clearBtn') else '',
|
||||
glyphicon=self.glyphicon,
|
||||
options=js_options,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class DateTimeWidget(PickerWidgetMixin, DateTimeInput):
|
||||
"""
|
||||
DateTimeWidget is the corresponding widget for Datetime field, it renders both the date and time
|
||||
sections of the datetime picker.
|
||||
"""
|
||||
|
||||
format_name = 'DATETIME_INPUT_FORMATS'
|
||||
glyphicon = 'glyphicon-th'
|
||||
|
||||
def __init__(self, attrs=None, options=None, usel10n=None):
|
||||
|
||||
if options is None:
|
||||
options = {}
|
||||
|
||||
# Set the default options to show only the datepicker object
|
||||
options['format'] = options.get('format', 'dd/mm/yyyy hh:ii')
|
||||
|
||||
super(DateTimeWidget, self).__init__(attrs, options, usel10n)
|
||||
def value_from_datadict(self, data, files, name):
|
||||
date_string = data.get(name + '$date')
|
||||
time_string = data.get(name + '$time')
|
||||
if not date_string or not time_string:
|
||||
return None
|
||||
date_value = dateparse.parse_date(date_string)
|
||||
time_value = dateparse.parse_time(time_string)
|
||||
if not date_value or not time_value:
|
||||
return None
|
||||
return datetime.datetime.combine(date_value, time_value)
|
||||
|
||||
|
||||
class TimeWidget(TimeInput):
|
||||
|
|
|
@ -351,7 +351,8 @@ def test_add_event(app, admin_user):
|
|||
assert "This agenda doesn't have any event yet." in resp.text
|
||||
year = now().year + 1
|
||||
resp = resp.click('New Event')
|
||||
resp.form['start_datetime'] = '%s-02-15 17:00' % year
|
||||
resp.form['start_datetime$date'] = '%s-02-15' % year
|
||||
resp.form['start_datetime$time'] = '17:00'
|
||||
resp.form['places'] = 10
|
||||
resp = resp.form.submit()
|
||||
resp = resp.follow()
|
||||
|
@ -369,7 +370,8 @@ def test_add_event(app, admin_user):
|
|||
# add with a description
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
|
||||
resp = resp.click('New Event')
|
||||
resp.form['start_datetime'] = '%s-02-15 18:00' % year
|
||||
resp.form['start_datetime$date'] = '%s-02-15' % year
|
||||
resp.form['start_datetime$time'] = '18:00'
|
||||
resp.form['places'] = 11
|
||||
resp.form['description'] = 'A description'
|
||||
resp = resp.form.submit()
|
||||
|
@ -377,6 +379,22 @@ def test_add_event(app, admin_user):
|
|||
event = Event.objects.get(places=11)
|
||||
assert event.description == 'A description'
|
||||
|
||||
# add with errors in datetime parts
|
||||
for parts in (
|
||||
('', ''),
|
||||
('invalid', ''),
|
||||
('', 'invalid'),
|
||||
('2019-02-24', 'invalid'),
|
||||
('invalid', '17:00'),
|
||||
):
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
|
||||
resp = resp.click('New Event')
|
||||
resp.form['start_datetime$date'] = parts[0]
|
||||
resp.form['start_datetime$time'] = parts[1]
|
||||
resp.form['places'] = 10
|
||||
resp = resp.form.submit()
|
||||
assert resp.text.count('This field is required.') == 1
|
||||
|
||||
|
||||
def test_add_event_on_missing_agenda(app, admin_user):
|
||||
app = login(app)
|
||||
|
@ -398,7 +416,8 @@ def test_add_event_as_manager(app, manager_user):
|
|||
resp = resp.click('Settings')
|
||||
assert '<h2>Settings' in resp.text
|
||||
resp = resp.click('New Event')
|
||||
resp.form['start_datetime'] = '2016-02-15 17:00'
|
||||
resp.form['start_datetime$date'] = '2016-02-15'
|
||||
resp.form['start_datetime$time'] = '17:00'
|
||||
resp.form['places'] = 10
|
||||
resp = resp.form.submit()
|
||||
resp = resp.follow()
|
||||
|
@ -417,8 +436,10 @@ def test_edit_event(app, admin_user):
|
|||
app = login(app)
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
|
||||
resp = resp.click('Feb. 15, 2016, 5 p.m.')
|
||||
assert resp.form['start_datetime'].value == '15/02/2016 17:00'
|
||||
resp.form['start_datetime'] = '2016-02-16 17:00'
|
||||
assert resp.form['start_datetime$date'].value == '2016-02-15'
|
||||
assert resp.form['start_datetime$time'].value == '17:00'
|
||||
resp.form['start_datetime$date'] = '2016-02-16'
|
||||
resp.form['start_datetime$time'] = '17:00'
|
||||
resp.form['places'] = 20
|
||||
resp = resp.form.submit()
|
||||
resp = resp.follow()
|
||||
|
@ -445,8 +466,10 @@ def test_edit_event_as_manager(app, manager_user):
|
|||
agenda.save()
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
|
||||
resp = resp.click('Feb. 15, 2016, 5 p.m.')
|
||||
assert resp.form['start_datetime'].value == '15/02/2016 17:00'
|
||||
resp.form['start_datetime'] = '2016-02-16 17:00'
|
||||
assert resp.form['start_datetime$date'].value == '2016-02-15'
|
||||
assert resp.form['start_datetime$time'].value == '17:00'
|
||||
resp.form['start_datetime$date'] = '2016-02-16'
|
||||
resp.form['start_datetime$time'] = '17:00'
|
||||
resp.form['places'] = 20
|
||||
resp = resp.form.submit()
|
||||
resp = resp.follow()
|
||||
|
@ -954,8 +977,10 @@ def test_meetings_agenda_add_time_period_exception(app, admin_user):
|
|||
tomorrow = make_aware(today + datetime.timedelta(days=1))
|
||||
dt_format = '%Y-%m-%d %H:%M'
|
||||
resp.form['label'] = 'Exception 1'
|
||||
resp.form['start_datetime'] = tomorrow.replace(hour=8).strftime(dt_format)
|
||||
resp.form['end_datetime'] = tomorrow.replace(hour=16).strftime(dt_format)
|
||||
resp.form['start_datetime$date'] = tomorrow.strftime('%Y-%m-%d')
|
||||
resp.form['start_datetime$time'] = '08:00'
|
||||
resp.form['end_datetime$date'] = tomorrow.strftime('%Y-%m-%d')
|
||||
resp.form['end_datetime$time'] = '16:00'
|
||||
resp = resp.form.submit().follow()
|
||||
assert TimePeriodException.objects.count() == 1
|
||||
time_period_exception = TimePeriodException.objects.first()
|
||||
|
@ -969,8 +994,10 @@ def test_meetings_agenda_add_time_period_exception(app, admin_user):
|
|||
resp = resp.click('Add a time period exception', index=1)
|
||||
future = tomorrow + datetime.timedelta(days=15)
|
||||
resp.form['label'] = 'Exception 2'
|
||||
resp.form['start_datetime'] = future.replace(hour=0, minute=0).strftime(dt_format)
|
||||
resp.form['end_datetime'] = future.replace(hour=16).strftime(dt_format)
|
||||
resp.form['start_datetime$date'] = future.strftime('%Y-%m-%d')
|
||||
resp.form['start_datetime$time'] = '00:00'
|
||||
resp.form['end_datetime$date'] = future.strftime('%Y-%m-%d')
|
||||
resp.form['end_datetime$time'] = '16:00'
|
||||
resp = resp.form.submit().follow()
|
||||
assert TimePeriodException.objects.count() == 2
|
||||
assert 'Exception 1' in resp.text
|
||||
|
@ -1001,8 +1028,10 @@ def test_meetings_agenda_add_time_period_exception_when_booking_exists(app, admi
|
|||
# fields should be marked with errors
|
||||
assert resp.text.count('This field is required.') == 2
|
||||
# try again with data in fields
|
||||
resp.form['start_datetime'] = '2017-05-22 08:00'
|
||||
resp.form['end_datetime'] = '2017-05-26 17:30'
|
||||
resp.form['start_datetime$date'] = '2017-05-22'
|
||||
resp.form['start_datetime$time'] = '08:00'
|
||||
resp.form['end_datetime$date'] = '2017-05-26'
|
||||
resp.form['end_datetime$time'] = '17:30'
|
||||
resp = resp.form.submit()
|
||||
assert 'One or several bookings exists within this time slot.' in resp.text
|
||||
assert TimePeriodException.objects.count() == 0
|
||||
|
@ -1015,8 +1044,10 @@ def test_meetings_agenda_add_time_period_exception_when_booking_exists(app, admi
|
|||
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
|
||||
resp = resp.click('Settings')
|
||||
resp = resp.click('Add a time period exception', href='desk/%s/' % desk.id)
|
||||
resp.form['start_datetime'] = '2017-05-22 08:00'
|
||||
resp.form['end_datetime'] = '2017-05-26 17:30'
|
||||
resp.form['start_datetime$date'] = '2017-05-22'
|
||||
resp.form['start_datetime$time'] = '08:00'
|
||||
resp.form['end_datetime$date'] = '2017-05-26'
|
||||
resp.form['end_datetime$time'] = '17:30'
|
||||
resp = resp.form.submit()
|
||||
assert TimePeriodException.objects.count() == 1
|
||||
|
||||
|
@ -1038,8 +1069,10 @@ def test_meetings_agenda_add_time_period_exception_when_cancelled_booking_exists
|
|||
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
|
||||
resp = resp.click('Settings')
|
||||
resp = resp.click('Add a time period exception')
|
||||
resp.form['start_datetime'] = '2017-05-22 08:00'
|
||||
resp.form['end_datetime'] = '2017-05-26 17:30'
|
||||
resp.form['start_datetime$date'] = '2017-05-22'
|
||||
resp.form['start_datetime$time'] = '08:00'
|
||||
resp.form['end_datetime$date'] = '2017-05-26'
|
||||
resp.form['end_datetime$time'] = '17:30'
|
||||
resp = resp.form.submit()
|
||||
assert 'One or several bookings exists within this time slot.' not in resp.text
|
||||
assert TimePeriodException.objects.count() == 1
|
||||
|
@ -1056,8 +1089,10 @@ def test_meetings_agenda_add_invalid_time_period_exception(app, admin_user):
|
|||
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
|
||||
resp = resp.click('Settings')
|
||||
resp = resp.click('Add a time period exception')
|
||||
resp.form['start_datetime'] = '2017-05-26 17:30'
|
||||
resp.form['end_datetime'] = '2017-05-22 08:00'
|
||||
resp.form['start_datetime$date'] = '2017-05-26'
|
||||
resp.form['start_datetime$time'] = '17:30'
|
||||
resp.form['end_datetime$date'] = '2017-05-22'
|
||||
resp.form['end_datetime$time'] = '08:00'
|
||||
resp = resp.form.submit()
|
||||
assert 'End datetime must be greater than start datetime.' in resp.text
|
||||
|
||||
|
@ -1077,8 +1112,10 @@ def test_meetings_agenda_delete_time_period_exception(app, admin_user):
|
|||
tomorrow = make_aware(today + datetime.timedelta(days=15))
|
||||
dt_format = '%Y-%m-%d %H:%M'
|
||||
resp.form['label'] = 'Exception 1'
|
||||
resp.form['start_datetime'] = tomorrow.replace(hour=8).strftime(dt_format)
|
||||
resp.form['end_datetime'] = tomorrow.replace(hour=16).strftime(dt_format)
|
||||
resp.form['start_datetime$date'] = tomorrow.strftime('%Y-%m-%d')
|
||||
resp.form['start_datetime$time'] = '08:00'
|
||||
resp.form['end_datetime$date'] = tomorrow.strftime('%Y-%m-%d')
|
||||
resp.form['end_datetime$time'] = '16:00'
|
||||
resp = resp.form.submit().follow()
|
||||
assert TimePeriodException.objects.count() == 1
|
||||
time_period_exception = TimePeriodException.objects.first()
|
||||
|
@ -1946,7 +1983,8 @@ def test_agenda_view_edit_event(app, manager_user):
|
|||
resp = app.get(event_url)
|
||||
assert 'Options' in resp.text
|
||||
resp = resp.click('Options')
|
||||
resp.form['start_datetime'] = agenda.event_set.first().start_datetime.strftime('%Y-%m-%d %H:%M')
|
||||
resp.form['start_datetime$date'] = agenda.event_set.first().start_datetime.strftime('%Y-%m-%d')
|
||||
resp.form['start_datetime$time'] = agenda.event_set.first().start_datetime.strftime('%H:%M')
|
||||
resp = resp.form.submit(status=302).follow()
|
||||
assert event_url == resp.request.url
|
||||
|
||||
|
|
Loading…
Reference in New Issue