add initial events management

This includes a copy of django-datetime-widget, it should be replaced by our
own datetime widget, based on a recent version of the bootstrap datetimepicker,
packaged as a xstatic.
This commit is contained in:
Frédéric Péters 2016-02-13 15:31:28 +01:00
parent 101c462157
commit 349d4baaed
13 changed files with 2701 additions and 4 deletions

View File

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('agendas', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Event',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('start_datetime', models.DateTimeField(verbose_name='Date/time')),
('places', models.PositiveIntegerField(verbose_name='Places')),
('agenda', models.ForeignKey(to='agendas.Agenda')),
],
options={
'ordering': ['agenda', 'start_datetime'],
},
bases=(models.Model,),
),
]

View File

@ -44,3 +44,12 @@ class Agenda(models.Model):
def get_absolute_url(self):
return reverse('chrono-manager-agenda-view', kwargs={'pk': self.id})
class Event(models.Model):
agenda = models.ForeignKey(Agenda)
start_datetime = models.DateTimeField(_('Date/time'))
places = models.PositiveIntegerField(_('Places'))
class Meta:
ordering = ['agenda', 'start_datetime']

42
chrono/manager/forms.py Normal file
View File

@ -0,0 +1,42 @@
# chrono - agendas system
# Copyright (C) 2016 Entr'ouvert
#
# 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.
#
# 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/>.
from django import forms
from . import widgets
from chrono.agendas.models import Event
DATETIME_OPTIONS = {
'weekStart': '1',
'autoclose': 'true',
}
class DateTimeWidget(widgets.DateTimeWidget):
def __init__(self, *args, **kwargs):
super(DateTimeWidget, self).__init__(*args, options=DATETIME_OPTIONS, **kwargs)
class EventForm(forms.ModelForm):
class Meta:
model = Event
widgets = {
'agenda': forms.HiddenInput(),
'start_datetime': DateTimeWidget(),
}
exclude = []

View File

@ -0,0 +1,372 @@
/*!
* 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: "→";
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
/**
* 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

@ -5,14 +5,39 @@
<h2>{% trans 'Agenda' %} - {{ object.label }}</h2>
<a rel="popup" href="{% url 'chrono-manager-agenda-delete' pk=object.id %}">{% trans 'Delete' %}</a>
<a rel="popup" href="{% url 'chrono-manager-agenda-edit' pk=object.id %}">{% trans 'Rename' %}</a>
<a rel="popup" href="{% url 'chrono-manager-agenda-add-event' pk=object.id %}">{% trans 'New Event' %}</a>
{% endblock %}
{% block breadcrumb %}
{{ block.super }}
{% if object %}
{% if agenda %}
<a href="{% url 'chrono-manager-agenda-view' pk=agenda.id %}">{{agenda.label}}</a>
{% elif object %}
<a href="{% url 'chrono-manager-agenda-view' pk=object.id %}">{{object.label}}</a>
{% endif %}
{% endblock %}
{% block content %}
{% if object.event_set.count %}
<div>
<ul class="objects-list single-links">
{% for event in object.event_set.all %}
<li><a rel="popup" href="{% url 'chrono-manager-event-edit' pk=event.id %}">
{% blocktrans with start=event.start_datetime places=event.places%}
{{ start }} ({{ places }} places)
{% endblocktrans %}</a>
</li>
{% endfor %}
</ul>
</div>
{% else %}
<div class="big-msg-info">
{% blocktrans %}
This agenda doesn't have any event yet. Click on the "New Event" button in
the top right of the page to add a first one.
{% endblocktrans %}
</div>
{% endif %}
{% endblock %}

View File

@ -1,6 +1,13 @@
{% extends "gadjo/base.html" %}
{% load staticfiles i18n %}
{% block extrascripts %}
{{ block.super }}
<link rel="stylesheet" type="text/css" media="all" href="{% static 'css/datetimepicker.css' %}" />
<script src="{% static 'js/bootstrap-datetimepicker.js' %}"></script>
<script src="{% static 'js/locales/bootstrap-datetimepicker.fr.js' %}"></script>
{% endblock %}
{% block page-title %}
{% trans 'Agendas' as default_site_title %}
{% firstof site_title default_site_title %}

View File

@ -0,0 +1,27 @@
{% extends "chrono/manager_agenda_view.html" %}
{% load i18n %}
{% block extrascripts %}
{{ block.super }}
{{ form.media }}
{% endblock %}
{% block appbar %}
{% if object.id %}
<h2>{% trans "Edit Event" %}</h2>
{% else %}
<h2>{% trans "New Event" %}</h2>
{% endif %}
{% endblock %}
{% block content %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<div class="buttons">
<button>{% trans "Save" %}</button>
<a class="cancel" href="{% url 'chrono-manager-agenda-view' pk=agenda.id %}">{% trans 'Cancel' %}</a>
</div>
</form>
{% endblock %}

View File

@ -28,5 +28,9 @@ urlpatterns = patterns('chrono.views',
name='chrono-manager-agenda-edit'),
url(r'^agendas/(?P<pk>\w+)/delete$', views.agenda_delete,
name='chrono-manager-agenda-delete'),
url(r'^agendas/(?P<pk>\w+)/add-event$', views.agenda_add_event,
name='chrono-manager-agenda-add-event'),
url(r'^events/(?P<pk>\w+)/$', views.event_edit,
name='chrono-manager-event-edit'),
url(r'^menu.json$', views.menu_json),
)

View File

@ -16,14 +16,16 @@
import json
from django.core.urlresolvers import reverse_lazy, reverse
from django.core.urlresolvers import reverse, reverse_lazy
from django.http import HttpResponse
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_text
from django.views.generic import (DetailView, CreateView, UpdateView,
ListView, DeleteView)
from chrono.agendas.models import Agenda
from chrono.agendas.models import Agenda, Event
from .forms import EventForm
class HomepageView(ListView):
@ -64,6 +66,43 @@ class AgendaView(DetailView):
agenda_view = AgendaView.as_view()
class AgendaAddEventView(CreateView):
template_name = 'chrono/manager_event_form.html'
model = Event
form_class = EventForm
def get_success_url(self):
return reverse('chrono-manager-agenda-view', kwargs={'pk': self.kwargs.get('pk')})
def get_initial(self):
initial = super(AgendaAddEventView, self).get_initial()
initial['agenda'] = self.kwargs.get('pk')
return initial
def get_context_data(self, **kwargs):
context = super(AgendaAddEventView, self).get_context_data(**kwargs)
context['agenda'] = Agenda.objects.get(id=self.kwargs.get('pk'))
return context
agenda_add_event = AgendaAddEventView.as_view()
class EventEditView(UpdateView):
template_name = 'chrono/manager_event_form.html'
model = Event
form_class = EventForm
def get_context_data(self, **kwargs):
context = super(EventEditView, self).get_context_data(**kwargs)
context['agenda'] = self.object.agenda
return context
def get_success_url(self):
return reverse('chrono-manager-agenda-view', kwargs={'pk': self.object.agenda_id})
event_edit = EventEditView.as_view()
def menu_json(request):
response = HttpResponse(content_type='application/json')
label = _('Agendas')

340
chrono/manager/widgets.py Normal file
View File

@ -0,0 +1,340 @@
# 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 both Bootstrap 3 and Bootstrap 2
#
# https://github.com/asaglimbeni/django-datetime-widget
#
# License: BSD
__author__ = 'Alfredo Saglimbeni'
from datetime import datetime
import re
import uuid
from django.forms import forms, widgets
from django.forms.widgets import MultiWidget, DateTimeInput, DateInput, TimeInput
from django.utils.formats import get_format, get_language
from django.utils.safestring import mark_safe
from django.utils.six import string_types
try:
from django.forms.widgets import to_current_timezone
except ImportError:
to_current_timezone = lambda obj: obj # passthrough, no tz support
# This should be updated as more .po files are added to the datetime picker javascript code
supported_languages = set([
'ar',
'bg',
'ca', 'cs',
'da', 'de',
'ee', 'el', 'es',
'fi', 'fr',
'he', 'hr', 'hu',
'id', 'is', 'it',
'ja',
'ko', 'kr',
'lt', 'lv',
'ms',
'nb', 'nl', 'no',
'pl', 'pt-BR', 'pt',
'ro', 'rs', 'rs-latin', 'ru',
'sk', 'sl', 'sv', 'sw',
'th', 'tr',
'ua', 'uk',
'zh-CN', 'zh-TW',
])
def get_supported_language(language_country_code):
"""Helps us get from django's 'language-countryCode' to the datepicker's 'language' if we
possibly can.
If we pass the django 'language_countryCode' through untouched then it might not
match an exact language string supported by the datepicker and would default to English which
would be worse than trying to match the language part.
"""
# Catch empty strings in case one sneeks in
if not language_country_code:
return 'en'
# Check full language & country code against the supported languages as there are dual entries
# in the list eg. zh-CN (assuming that is a language country code)
if language_country_code in supported_languages:
return language_country_code
# Grab just the language part and try that
language = language_country_code.split('-')[0]
if language in supported_languages:
return language
# Otherwise return English as the default
return 'en'
dateConversiontoPython = {
'P': '%p',
'ss': '%S',
'ii': '%M',
'hh': '%H',
'HH': '%I',
'dd': '%d',
'mm': '%m',
'yy': '%y',
'yyyy': '%Y',
}
toPython_re = re.compile(r'\b(' + '|'.join(dateConversiontoPython.keys()) + r')\b')
dateConversiontoJavascript = {
'%M': 'ii',
'%m': 'mm',
'%I': 'HH',
'%H': 'hh',
'%d': 'dd',
'%Y': 'yyyy',
'%y': 'yy',
'%p': 'P',
'%S': 'ss'
}
toJavascript_re = re.compile(r'(?<!\w)(' + '|'.join(dateConversiontoJavascript.keys()) + r')\b')
BOOTSTRAP_INPUT_TEMPLATE = {
2: """
<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>
""",
3: """
<div id="%(id)s" class="input-group date">
%(rendered_widget)s
%(clear_button)s
<span class="input-group-addon"><span class="glyphicon %(glyphicon)s"></span></span>
</div>
<script type="text/javascript">
$("#%(id)s").datetimepicker({%(options)s}).find('input').addClass("form-control");
</script>
"""
}
CLEAR_BTN_TEMPLATE = {2: """<span class="add-on"><i class="icon-remove"></i></span>""",
3: """<span class="input-group-addon"><span class="glyphicon glyphicon-remove"></span></span>"""}
quoted_options = set([
'format',
'startDate',
'endDate',
'startView',
'minView',
'maxView',
'todayBtn',
'language',
'pickerPosition',
'viewSelect',
'initialDate',
'weekStart',
'minuteStep'
'daysOfWeekDisabled',
])
# to traslate boolean object to javascript
quoted_bool_options = set([
'autoclose',
'todayHighlight',
'showMeridian',
'clearBtn',
])
def quote(key, value):
"""Certain options support string values. We want clients to be able to pass Python strings in
but we need them to be quoted in the output. Unfortunately some of those options also allow
numbers so we type check the value before wrapping it in quotes.
"""
if key in quoted_options and isinstance(value, string_types):
return "'%s'" % value
if key in quoted_bool_options and isinstance(value, bool):
return {True:'true',False:'false'}[value]
return value
class PickerWidgetMixin(object):
format_name = None
glyphicon = None
def __init__(self, attrs=None, options=None, usel10n=None, bootstrap_version=None):
if bootstrap_version in [2,3]:
self.bootstrap_version = bootstrap_version
else:
# default 2 to mantain support to old implemetation of django-datetime-widget
self.bootstrap_version = 2
if attrs is None:
attrs = {'readonly': ''}
self.options = options
self.is_localized = False
self.format = None
# We want to have a Javascript style date format specifier in the options dictionary and we
# want a Python style date format specifier as a member variable for parsing the date string
# from the form data
if usel10n is True:
# If we're doing localisation, get the local Python date format and convert it to
# Javascript data format for the options dictionary
self.is_localized = True
# Get format from django format system
self.format = get_format(self.format_name)[0]
# Convert Python format specifier to Javascript format specifier
self.options['format'] = toJavascript_re.sub(
lambda x: dateConversiontoJavascript[x.group()],
self.format
)
# Set the local language
self.options['language'] = get_supported_language(get_language())
else:
# If 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
format = self.options['format']
self.format = toPython_re.sub(
lambda x: dateConversiontoPython[x.group()],
format
)
super(PickerWidgetMixin, self).__init__(attrs, format=self.format)
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs)
rendered_widget = super(PickerWidgetMixin, self).render(name, value, final_attrs)
#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, quote(key, 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)
clearBtn = quote('clearBtn', self.options.get('clearBtn', 'true')) == 'true'
return mark_safe(
BOOTSTRAP_INPUT_TEMPLATE[self.bootstrap_version]
% dict(
id=id,
rendered_widget=rendered_widget,
clear_button=CLEAR_BTN_TEMPLATE[self.bootstrap_version] if clearBtn else "",
glyphicon=self.glyphicon,
options=js_options
)
)
def _media(self):
js = ["js/bootstrap-datetimepicker.js"]
language = self.options.get('language', 'en')
if language != 'en':
js.append("js/locales/bootstrap-datetimepicker.%s.js" % language)
return widgets.Media(
css={
'all': ('css/datetimepicker.css',)
},
js=js
)
media = property(_media)
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, bootstrap_version=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, bootstrap_version)
class DateWidget(PickerWidgetMixin, DateInput):
"""
DateWidget is the corresponding widget for Date field, it renders only the date section of
datetime picker.
"""
format_name = 'DATE_INPUT_FORMATS'
glyphicon = 'glyphicon-calendar'
def __init__(self, attrs=None, options=None, usel10n=None, bootstrap_version=None):
if options is None:
options = {}
# Set the default options to show only the datepicker object
options['startView'] = options.get('startView', 2)
options['minView'] = options.get('minView', 2)
options['format'] = options.get('format', 'dd/mm/yyyy')
super(DateWidget, self).__init__(attrs, options, usel10n, bootstrap_version)
class TimeWidget(PickerWidgetMixin, TimeInput):
"""
TimeWidget is the corresponding widget for Time field, it renders only the time section of
datetime picker.
"""
format_name = 'TIME_INPUT_FORMATS'
glyphicon = 'glyphicon-time'
def __init__(self, attrs=None, options=None, usel10n=None, bootstrap_version=None):
if options is None:
options = {}
# Set the default options to show only the timepicker object
options['startView'] = options.get('startView', 1)
options['minView'] = options.get('minView', 0)
options['maxView'] = options.get('maxView', 1)
options['format'] = options.get('format', 'hh:ii')
super(TimeWidget, self).__init__(attrs, options, usel10n, bootstrap_version)

View File

@ -1,10 +1,11 @@
from django.contrib.auth.models import User
import datetime
import pytest
from webtest import TestApp
from chrono.wsgi import application
from chrono.agendas.models import Agenda
from chrono.agendas.models import Agenda, Event
pytestmark = pytest.mark.django_db
@ -84,3 +85,37 @@ def test_delete_agenda(admin_user):
assert resp.location == 'http://localhost:80/manage/'
resp = resp.follow()
assert not 'Foo bar' in resp.body
def test_add_event(admin_user):
agenda = Agenda(label=u'Foo bar')
agenda.save()
app = login(TestApp(application))
resp = app.get('/manage/agendas/1/', status=200)
assert "This agenda doesn't have any event yet." in resp.body
resp = resp.click('New Event')
resp.form['start_datetime'] = '2016-02-15 17:00'
resp.form['places'] = 10
resp = resp.form.submit()
resp = resp.follow()
assert not "This agenda doesn't have any event yet." in resp.body
assert '/manage/events/1/' in resp.body
assert 'Feb. 15, 2016, 5 p.m.' in resp.body
assert '10 places' in resp.body
def test_edit_event(admin_user):
agenda = Agenda(label=u'Foo bar')
agenda.save()
event = Event(start_datetime=datetime.datetime(2016, 2, 15, 17, 0),
places=20, agenda=agenda)
event.save()
app = login(TestApp(application))
resp = app.get('/manage/agendas/1/', 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'
resp.form['places'] = 20
resp = resp.form.submit()
resp = resp.follow()
assert '/manage/events/1/' in resp.body
assert 'Feb. 16, 2016, 5 p.m.' in resp.body
assert '20 places' in resp.body