forms: add full date/time support for publication dates (#42252)

This commit is contained in:
Frédéric Péters 2020-05-02 10:06:14 +02:00
parent 07617b5f1b
commit 0226e43616
8 changed files with 127 additions and 21 deletions

View File

@ -359,7 +359,7 @@ def test_forms_edit(pub):
resp = resp.click('Online Status')
resp.forms[0]['disabled'].checked = False
resp.forms[0]['expiration_date'] = '2000-01-01' # this is past(tm)
resp.forms[0]['expiration_date$date'] = '2000-01-01' # this is past(tm)
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/backoffice/forms/1/'
resp = resp.follow()
@ -437,6 +437,41 @@ def test_form_title_change(pub):
assert FormDef.get(formdef2.id).url_name == 'foobar'
def test_forms_edit_publication_date(pub):
create_superuser(pub)
create_role()
FormDef.wipe()
formdef = FormDef()
formdef.name = 'form title'
formdef.fields = []
formdef.store()
app = login(get_app(pub))
resp = app.get('/backoffice/forms/1/options/online_status')
resp.form['publication_date$date'] = '2020-01-01'
resp = resp.form.submit()
assert FormDef.get(formdef.id).publication_date == '2020-01-01 00:00'
resp = app.get('/backoffice/forms/1/options/online_status')
assert resp.form['publication_date$date'].value == '2020-01-01'
resp.form['publication_date$time'] = '12:00'
resp = resp.form.submit()
assert FormDef.get(formdef.id).publication_date == '2020-01-01 12:00'
resp = app.get('/backoffice/forms/1/options/online_status')
assert resp.form['publication_date$date'].value == '2020-01-01'
assert resp.form['publication_date$time'].value == '12:00'
formdef.publication_date = None
formdef.store()
resp = app.get('/backoffice/forms/1/options/online_status')
resp.form['publication_date$time'] = '12:00'
resp = resp.form.submit()
assert 'invalid value' in resp
def test_form_category(pub):
create_superuser(pub)
create_role()

View File

@ -20,6 +20,7 @@ import glob
import itertools
import pickle
import sys
import time
import types
import json
import xml.etree.ElementTree as ET

View File

@ -24,7 +24,6 @@ import re
import socket
import sys
import tempfile
import time
import random
import datetime
import itertools
@ -175,6 +174,15 @@ def render(self):
template_names = get_template_names(self)
return htmltext(render_template(template_names, context))
def render_widget_content(self):
# widget content (without label, hint, etc.) is reused on status page;
# render the appropriate block.
self.add_media()
template_names = get_template_names(self)
context = {'widget': self}
return htmltext(force_str(render_block_to_string(template_names, 'widget-content', context)))
Widget.get_error = get_i18n_error
Widget.render = render
Widget.cleanup = None
@ -182,6 +190,7 @@ Widget.render_error = render_error
Widget.render_hint = render_hint
Widget.render_title = render_title
Widget.is_prefilled = is_prefilled
Widget.render_widget_content = render_widget_content
def string_render_content(self):
@ -1126,17 +1135,55 @@ class DateWidget(StringWidget):
'hh', '00').replace('ii', '00').replace('ss', '00')
class DateTimeWidget(DateWidget):
'''StringWidget which checks the value entered is a correct date/time'''
content_extra_css_class = 'date'
class TimeWidget(DateWidget):
template_name = 'qommon/forms/widgets/time.html'
def _parse(self, request):
StringWidget._parse(self, request)
if self.value is not None:
try:
value = datetime.datetime.strptime(self.value, self.get_format_string())
self.value = strftime(self.get_format_string(), value)
except ValueError:
self.error = _('invalid time')
self.value = None
return
@classmethod
def get_format_string(cls):
return '%H:%M'
class DateTimeWidget(CompositeWidget):
def __init__(self, name, value=None, **kwargs):
DateWidget.__init__(self, name, value=value, **kwargs)
self.attrs['size'] = '16'
self.attrs['maxlength'] = '16'
super(DateTimeWidget, self).__init__(name, value=value, **kwargs)
date_value = None
time_value = None
if value:
date_value = misc.get_as_datetime(value).strftime(DateWidget.get_format_string())
time_value = misc.get_as_datetime(value).strftime(TimeWidget.get_format_string())
self.add(DateWidget, 'date', value=date_value, render_br=False)
self.add(TimeWidget, 'time', value=time_value, render_br=False)
def get_format_string(self):
return misc.datetime_format()
def render_content(self):
r = TemplateIO(html=True)
for widget in self.get_widgets():
r += widget.render_widget_content()
return r.getvalue()
def _parse(self, request):
date = self.get('date')
time = self.get('time')
if not date and not time:
self.value = None
return self.value
time = time or '00:00'
try:
misc.get_as_datetime('%s %s' % (date, time))
except ValueError:
self.error = _('invalid value')
self.value = '%s %s' % (date, time)
return self.value
class RegexStringWidget(StringWidget):
@ -2248,14 +2295,6 @@ class MapWidget(CompositeWidget):
def add_media(self):
get_response().add_javascript(['qommon.map.js'])
def render_widget_content(self):
# widget content (without label, hint, etc.) is reused on status page;
# render the appropriate block.
self.add_media()
template_names = get_template_names(self)
context = {'widget': self}
return htmltext(force_str(render_block_to_string(template_names, 'widget-content', context)))
def _parse(self, request):
CompositeWidget._parse(self, request)
self.value = self.get('latlng')

View File

@ -659,6 +659,10 @@ div.SingleSelectHintWidget select {
text-overflow: ellipsis;
}
div.DateTimeWidget .content .content {
display: inline-block;
}
aside#sidebar input.inline-input {
margin-right: 1em;
}

View File

@ -31,7 +31,9 @@ $(function() {
});
}
$('.date-pick').each(function() {
if (this.type == "date") return; // prefer native date widget
if (this.type == "date" || this.type == "time") {
return; // prefer native date/time widgets
}
var $date_input = $(this);
$date_input.attr('type', 'text');
if ($date_input.data('formatted-value')) {
@ -42,6 +44,7 @@ $(function() {
options.weekStart = 1;
options.format = $date_input.data('date-format');
options.minView = $date_input.data('min-view');
options.maxView = $date_input.data('max-view');
options.startView = $date_input.data('start-view');
if ($date_input.data('start-date')) options.startDate = $date_input.data('start-date');
if ($date_input.data('end-date')) options.endDate = $date_input.data('end-date');

View File

@ -3,13 +3,13 @@
{% block widget-control %}
<input id="form_{{widget.name}}" name="{{widget.name}}"
type="{% if "readonly" in widget.attrs %}text{% else %}date{% endif %}"
{% for attr in widget.attrs.items %}{{attr.0}}="{{attr.1}}"{% endfor %}
{% for attr in widget.attrs.items %}{{attr.0}}="{{attr.1}}" {% endfor %}
{% if widget.required %}aria-required="true"{% endif %}
{% if "readonly" in widget.attrs %}
{% if widget.value %}value="{{ widget.value }}"{% endif %}
{% else %}
{% if widget.value %}value="{{ widget.value|date:"Y-m-d" }}" data-formatted-value="{{ widget.value }}"{% endif %}
{% if widget.value %}value="{{ widget.value|date:"Y-m-d" }}" data-formatted-value="{{ widget.value|default:"" }}"{% endif %}
class="date-pick"
data-date-format="{{widget.date_format}}"

View File

@ -0,0 +1,23 @@
{% extends "qommon/forms/widget.html" %}
{% block widget-control %}
<input id="form_{{widget.name}}" name="{{widget.name}}"
type="{% if "readonly" in widget.attrs %}text{% else %}time{% endif %}"
{% for attr in widget.attrs.items %}{{attr.0}}="{{attr.1}}" {% endfor %}
{% if widget.required %}aria-required="true"{% endif %}
{% if "readonly" in widget.attrs %}
{% if widget.value %}value="{{ widget.value }}"{% endif %}
{% else %}
value="{{ widget.value|default:"" }}" data-formatted-value="{{ widget.value|default:"" }}"
class="date-pick"
data-date-format="hh:ii"
data-min-view="0"
data-max-view="0"
data-start-view="0"
{% endif %}
>
{% endblock %}

View File

@ -17,6 +17,7 @@
import itertools
import json
import os
import time
from django.utils import six