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:
parent
101c462157
commit
349d4baaed
|
@ -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,),
|
||||
),
|
||||
]
|
|
@ -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']
|
||||
|
|
|
@ -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 = []
|
|
@ -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
|
@ -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));
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue