manager: use independent date/time inputs for datetime input (#27013)

This commit is contained in:
Frédéric Péters 2019-12-24 16:34:01 +01:00
parent eda5a25b56
commit 2bf1f8672a
9 changed files with 105 additions and 2303 deletions

View File

@ -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):

View File

@ -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: "→";
}

View File

@ -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

View File

@ -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));

View File

@ -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 %}

View File

@ -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>

View File

@ -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):

View File

@ -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