819 lines
31 KiB
Python
819 lines
31 KiB
Python
# 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/>.
|
|
|
|
import datetime
|
|
import json
|
|
|
|
from django.contrib import messages
|
|
from django.core.exceptions import PermissionDenied
|
|
from django.core.urlresolvers import reverse, reverse_lazy
|
|
from django.db.models import Q
|
|
from django.http import HttpResponse, Http404, HttpResponseRedirect
|
|
from django.shortcuts import get_object_or_404
|
|
from django.utils.dates import MONTHS
|
|
from django.utils.timezone import now, make_aware, make_naive
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from django.utils.translation import ungettext
|
|
from django.utils.encoding import force_text
|
|
from django.views.generic import (DetailView, CreateView, UpdateView,
|
|
ListView, DeleteView, FormView, TemplateView, DayArchiveView,
|
|
MonthArchiveView)
|
|
|
|
from chrono.agendas.models import (Agenda, Event, MeetingType, TimePeriod,
|
|
Booking, Desk, TimePeriodException,
|
|
ICSError, AgendaImportError)
|
|
|
|
from .forms import (AgendaAddForm, AgendaEditForm, NewEventForm, EventForm, NewMeetingTypeForm, MeetingTypeForm,
|
|
TimePeriodForm, ImportEventsForm, NewDeskForm, DeskForm, TimePeriodExceptionForm,
|
|
ExceptionsImportForm, AgendasImportForm, TimePeriodAddForm)
|
|
from .utils import import_site
|
|
|
|
|
|
class HomepageView(ListView):
|
|
template_name = 'chrono/manager_home.html'
|
|
model = Agenda
|
|
|
|
def get_queryset(self):
|
|
queryset = super(HomepageView, self).get_queryset()
|
|
if not self.request.user.is_staff:
|
|
group_ids = [x.id for x in self.request.user.groups.all()]
|
|
queryset = queryset.filter(Q(view_role_id__in=group_ids) | Q(edit_role_id__in=group_ids))
|
|
return queryset
|
|
|
|
homepage = HomepageView.as_view()
|
|
|
|
|
|
class AgendaAddView(CreateView):
|
|
template_name = 'chrono/manager_agenda_form.html'
|
|
model = Agenda
|
|
form_class = AgendaAddForm
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super(AgendaAddView, self).dispatch(request, *args, **kwargs)
|
|
|
|
def form_valid(self, form):
|
|
model_form = super(AgendaAddView, self).form_valid(form)
|
|
if self.object.kind == 'meetings':
|
|
default_desk = Desk(agenda=self.object, label=_('Desk 1'))
|
|
default_desk.save()
|
|
return model_form
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.object.id})
|
|
|
|
agenda_add = AgendaAddView.as_view()
|
|
|
|
|
|
class AgendasImportView(FormView):
|
|
form_class = AgendasImportForm
|
|
template_name = 'chrono/agendas_import.html'
|
|
success_url = reverse_lazy('chrono-manager-homepage')
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super(AgendasImportView, self).dispatch(request, *args, **kwargs)
|
|
|
|
def form_valid(self, form):
|
|
try:
|
|
agendas_json = json.loads(force_text(self.request.FILES['agendas_json'].read()))
|
|
except ValueError:
|
|
form.add_error('agendas_json', _('File is not in the expected JSON format.'))
|
|
return self.form_invalid(form)
|
|
|
|
try:
|
|
results = import_site(agendas_json, overwrite=True)
|
|
except AgendaImportError as exc:
|
|
form.add_error('agendas_json', u'%s' % exc)
|
|
return self.form_invalid(form)
|
|
|
|
if results.get('created') == 0 and results.get('updated') == 0:
|
|
messages.info(self.request, _('No agendas were found.'))
|
|
else:
|
|
if results.get('created') == 0:
|
|
message1 = _('No agenda created.')
|
|
else:
|
|
message1 = ungettext('An agenda has been created.',
|
|
'%(count)d agendas have been created.', results['created']) % {
|
|
'count': results['created']}
|
|
|
|
if results.get('updated') == 0:
|
|
message2 = _('No agenda updated.')
|
|
else:
|
|
message2 = ungettext('An agenda has been updated.',
|
|
'%(count)d agendas have been updated.', results['updated']) % {
|
|
'count': results['updated']}
|
|
messages.info(self.request, u'%s %s' % (message1, message2))
|
|
|
|
return super(AgendasImportView, self).form_valid(form)
|
|
|
|
agendas_import = AgendasImportView.as_view()
|
|
|
|
|
|
class AgendaEditView(UpdateView):
|
|
template_name = 'chrono/manager_agenda_form.html'
|
|
model = Agenda
|
|
form_class = AgendaEditForm
|
|
|
|
def get_object(self, queryset=None):
|
|
obj = super(AgendaEditView, self).get_object(queryset=queryset)
|
|
if not obj.can_be_managed(self.request.user):
|
|
raise PermissionDenied()
|
|
return obj
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.object.id})
|
|
|
|
agenda_edit = AgendaEditView.as_view()
|
|
|
|
|
|
class AgendaDeleteView(DeleteView):
|
|
template_name = 'chrono/manager_confirm_delete.html'
|
|
model = Agenda
|
|
success_url = reverse_lazy('chrono-manager-homepage')
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if not request.user.is_staff:
|
|
raise PermissionDenied()
|
|
return super(AgendaDeleteView, self).dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super(AgendaDeleteView, self).get_context_data(**kwargs)
|
|
context['cannot_delete'] = Booking.objects.filter(
|
|
event__agenda=self.get_object(),
|
|
event__start_datetime__gt=now(),
|
|
cancellation_datetime__isnull=True).exists()
|
|
return context
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
context = self.get_context_data()
|
|
if context['cannot_delete']:
|
|
raise PermissionDenied()
|
|
return super(AgendaDeleteView, self).delete(request, *args, **kwargs)
|
|
|
|
agenda_delete = AgendaDeleteView.as_view()
|
|
|
|
|
|
class AgendaView(DetailView):
|
|
model = Agenda
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
try:
|
|
agenda = Agenda.objects.get(id=kwargs.get('pk'))
|
|
except Agenda.DoesNotExist:
|
|
raise Http404()
|
|
if not agenda.can_be_viewed(self.request.user):
|
|
raise PermissionDenied()
|
|
|
|
if agenda.kind == 'meetings':
|
|
# redirect to today view
|
|
today = datetime.date.today()
|
|
return HttpResponseRedirect(reverse('chrono-manager-agenda-day-view',
|
|
kwargs={'pk': agenda.id,
|
|
'year': today.year,
|
|
'month': today.month,
|
|
'day': today.day}))
|
|
|
|
# redirect to settings
|
|
return HttpResponseRedirect(
|
|
reverse('chrono-manager-agenda-settings', kwargs={'pk': agenda.id}))
|
|
|
|
agenda_view = AgendaView.as_view()
|
|
|
|
|
|
class AgendaDateView(object):
|
|
model = Event
|
|
month_format = '%m'
|
|
date_field = 'start_datetime'
|
|
allow_empty = True
|
|
allow_future = True
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.agenda = get_object_or_404(Agenda, id=kwargs.get('pk'))
|
|
if self.agenda.kind != 'meetings':
|
|
raise Http404()
|
|
if not self.agenda.can_be_viewed(request.user):
|
|
raise PermissionDenied()
|
|
|
|
# specify 6am time to get the expected timezone on daylight saving time
|
|
# days.
|
|
try:
|
|
self.date = make_aware(datetime.datetime.strptime(
|
|
'%s-%s-%s 06:00' % (self.get_year(), self.get_month(), self.get_day()),
|
|
'%Y-%m-%d %H:%M'))
|
|
except ValueError: # day is out of range for month
|
|
# redirect to last day of month
|
|
date = datetime.date(int(self.get_year()), int(self.get_month()), 1)
|
|
date += datetime.timedelta(days=40)
|
|
date = date.replace(day=1)
|
|
date -= datetime.timedelta(days=1)
|
|
return HttpResponseRedirect(reverse('chrono-manager-agenda-day-view',
|
|
kwargs={'pk': self.agenda.id,
|
|
'year': date.year,
|
|
'month': date.month,
|
|
'day': date.day}))
|
|
return super(AgendaDateView, self).dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super(AgendaDateView, self).get_context_data(**kwargs)
|
|
context['agenda'] = self.agenda
|
|
try:
|
|
context['hour_span'] = max(60 // self.agenda.get_base_meeting_duration(), 1)
|
|
except ValueError: # no meeting types defined
|
|
context['hour_span'] = 1
|
|
context['user_can_manage'] = self.agenda.can_be_managed(self.request.user)
|
|
return context
|
|
|
|
def get_queryset(self):
|
|
queryset = super(AgendaDateView, self).get_queryset()
|
|
queryset = queryset.filter(agenda=self.agenda).prefetch_related('booking_set')
|
|
return queryset
|
|
|
|
def get_days(self):
|
|
return [str(x) for x in range(1, 32)]
|
|
|
|
def get_months(self):
|
|
return [(str(x), MONTHS[x]) for x in range(1, 13)]
|
|
|
|
def get_years(self):
|
|
year = now().year
|
|
return [str(x) for x in range(year-1, year+5)]
|
|
|
|
|
|
class AgendaDayView(AgendaDateView, DayArchiveView):
|
|
template_name = 'chrono/manager_agenda_day_view.html'
|
|
|
|
def get_previous_day_url(self):
|
|
previous_day = self.date.date() - datetime.timedelta(days=1)
|
|
return reverse('chrono-manager-agenda-day-view',
|
|
kwargs={'pk': self.agenda.id,
|
|
'year': previous_day.year,
|
|
'month': previous_day.month,
|
|
'day': previous_day.day})
|
|
|
|
def get_next_day_url(self):
|
|
next_day = self.date.date() + datetime.timedelta(days=1)
|
|
return reverse('chrono-manager-agenda-day-view',
|
|
kwargs={'pk': self.agenda.id,
|
|
'year': next_day.year,
|
|
'month': next_day.month,
|
|
'day': next_day.day})
|
|
|
|
def get_timetable_infos(self):
|
|
timeperiods = TimePeriod.objects.filter(
|
|
desk__agenda=self.agenda,
|
|
weekday=self.date.weekday(),
|
|
)
|
|
if not timeperiods:
|
|
return
|
|
|
|
min_timeperiod = min([x.start_time for x in timeperiods])
|
|
max_timeperiod = max([x.end_time for x in timeperiods])
|
|
|
|
interval = datetime.timedelta(minutes=60)
|
|
current_date = self.date.replace(hour=min_timeperiod.hour, minute=0)
|
|
start_date = current_date
|
|
max_date = self.date.replace(hour=max_timeperiod.hour, minute=0)
|
|
if max_timeperiod.minute != 0:
|
|
# until the end of the last hour.
|
|
max_date += datetime.timedelta(hours=1)
|
|
|
|
desks = self.agenda.desk_set.all()
|
|
|
|
first = True
|
|
while current_date < max_date:
|
|
# for each timeslot return the timeslot date and a list of per-desk
|
|
# bookings
|
|
infos = [] # various infos, for each desk
|
|
for desk in desks:
|
|
info = {'desk': desk}
|
|
if first:
|
|
# use first row to include opening hours
|
|
info['opening_hours'] = opening_hours = []
|
|
for opening_hour in desk.get_opening_hours(current_date.date()):
|
|
opening_hours.append({
|
|
'css_top': 100 * (opening_hour.begin - start_date).seconds // 3600,
|
|
'css_height': 100 * (opening_hour.end - opening_hour.begin).seconds // 3600,
|
|
})
|
|
infos.append(info)
|
|
info['bookings'] = bookings = [] # bookings for this desk
|
|
finish_datetime = current_date + interval
|
|
for event in [x for x in self.object_list if x.desk_id == desk.id and
|
|
x.start_datetime >= current_date and x.start_datetime < finish_datetime]:
|
|
# don't consider cancelled bookings
|
|
for booking in [x for x in event.booking_set.all() if not x.cancellation_datetime]:
|
|
booking.css_top = int(100 * event.start_datetime.minute / 60)
|
|
booking.css_height = int(100 * event.meeting_type.duration / 60)
|
|
bookings.append(booking)
|
|
|
|
yield current_date, infos
|
|
current_date += interval
|
|
first = False
|
|
|
|
agenda_day_view = AgendaDayView.as_view()
|
|
|
|
|
|
class AgendaMonthView(AgendaDateView, MonthArchiveView):
|
|
template_name = 'chrono/manager_agenda_month_view.html'
|
|
|
|
def get_previous_month_url(self):
|
|
previous_month = self.get_previous_month(self.date.date())
|
|
return reverse('chrono-manager-agenda-month-view',
|
|
kwargs={'pk': self.agenda.id,
|
|
'year': previous_month.year,
|
|
'month': previous_month.month})
|
|
|
|
def get_next_month_url(self):
|
|
next_month = self.get_next_month(self.date.date())
|
|
return reverse('chrono-manager-agenda-month-view',
|
|
kwargs={'pk': self.agenda.id,
|
|
'year': next_month.year,
|
|
'month': next_month.month})
|
|
|
|
def get_day(self):
|
|
return '1'
|
|
|
|
def get_timetable_infos(self):
|
|
timeperiods = TimePeriod.objects.filter(desk__agenda=self.agenda)
|
|
if not timeperiods:
|
|
return
|
|
|
|
first_week_number = self.date.isocalendar()[1]
|
|
last_month_day = self.get_next_month(self.date.date()) - datetime.timedelta(days=1)
|
|
last_week_number = last_month_day.isocalendar()[1]
|
|
|
|
if last_week_number < first_week_number: # new year
|
|
last_week_number = 53
|
|
|
|
for week_number in range(first_week_number, last_week_number + 1):
|
|
yield self.get_week_timetable_infos(week_number-first_week_number, timeperiods)
|
|
|
|
def get_week_timetable_infos(self, week_index, timeperiods):
|
|
|
|
date = self.date + datetime.timedelta(week_index*7)
|
|
year, week_number, dow = date.isocalendar()
|
|
start_date = date - datetime.timedelta(dow)
|
|
|
|
self.min_timeperiod = min([x.start_time for x in timeperiods])
|
|
self.max_timeperiod = max([x.end_time for x in timeperiods])
|
|
interval = datetime.timedelta(minutes=60)
|
|
|
|
period = self.date.replace(hour=self.min_timeperiod.hour, minute=0)
|
|
max_date = self.date.replace(hour=self.max_timeperiod.hour, minute=0)
|
|
|
|
periods = []
|
|
while period < max_date:
|
|
periods.append(period)
|
|
period = period + interval
|
|
|
|
return {'days': [self.get_day_timetable_infos(start_date + datetime.timedelta(i), interval) for i in range(1, 8)],
|
|
'periods': periods}
|
|
|
|
def get_day_timetable_infos(self, day, interval):
|
|
day = make_aware(make_naive(day)) # give day correct timezone
|
|
period = current_date = day.replace(hour=self.min_timeperiod.hour, minute=0)
|
|
timetable = {'date': current_date,
|
|
'today': day.date() == datetime.date.today(),
|
|
'other_month': day.month != self.date.month,
|
|
'infos': {'opening_hours': [], 'booked_slots': []}}
|
|
|
|
desks = self.agenda.desk_set.all()
|
|
desks_len = len(desks)
|
|
max_date = day.replace(hour=self.max_timeperiod.hour, minute=0)
|
|
|
|
# compute booking and opening hours only for current month
|
|
if self.date.month != day.month:
|
|
return timetable
|
|
|
|
while period <= max_date:
|
|
left = 1
|
|
period_end = period + interval
|
|
for desk_index, desk in enumerate(desks):
|
|
width = (98.0 / desks_len) - 1
|
|
for event in [x for x in self.object_list if x.desk_id == desk.id and
|
|
x.start_datetime >= period and x.start_datetime < period_end]:
|
|
# don't consider cancelled bookings
|
|
bookings = [x for x in event.booking_set.all() if not x.cancellation_datetime]
|
|
if not bookings:
|
|
continue
|
|
booking = {'css_top': 100 * (event.start_datetime - current_date).seconds // 3600,
|
|
'css_height': 100 * event.meeting_type.duration // 60,
|
|
'css_width': width,
|
|
'css_left': left,
|
|
'desk': desk,
|
|
'booking': bookings[0]
|
|
}
|
|
timetable['infos']['booked_slots'].append(booking)
|
|
|
|
# get desks opening hours on last period iteration
|
|
if period == max_date:
|
|
for hour in desk.get_opening_hours(current_date):
|
|
timetable['infos']['opening_hours'].append({
|
|
'css_top': 100 * (hour.begin - current_date).seconds // 3600,
|
|
'css_height': 100 * (hour.end - hour.begin).seconds // 3600,
|
|
'css_width': width,
|
|
'css_left': left,
|
|
})
|
|
left += width + 1
|
|
period += interval
|
|
|
|
return timetable
|
|
|
|
agenda_monthly_view = AgendaMonthView.as_view()
|
|
|
|
|
|
class ManagedAgendaMixin(object):
|
|
agenda = None
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
try:
|
|
self.agenda = Agenda.objects.get(id=kwargs.get('pk'))
|
|
except Agenda.DoesNotExist:
|
|
raise Http404()
|
|
if not self.agenda.can_be_managed(request.user):
|
|
raise PermissionDenied()
|
|
return super(ManagedAgendaMixin, self).dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super(ManagedAgendaMixin, self).get_context_data(**kwargs)
|
|
context['agenda'] = self.agenda
|
|
return context
|
|
|
|
def get_initial(self):
|
|
initial = super(ManagedAgendaMixin, self).get_initial()
|
|
initial['agenda'] = self.agenda.id
|
|
return initial
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.agenda.id})
|
|
|
|
|
|
class ManagedAgendaSubobjectMixin(object):
|
|
agenda = None
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.agenda = self.get_object().agenda
|
|
if not self.agenda.can_be_managed(request.user):
|
|
raise PermissionDenied()
|
|
return super(ManagedAgendaSubobjectMixin, self).dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super(ManagedAgendaSubobjectMixin, self).get_context_data(**kwargs)
|
|
context['agenda'] = self.object.agenda
|
|
return context
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.agenda.id})
|
|
|
|
|
|
class ManagedDeskMixin(object):
|
|
desk = None
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
try:
|
|
self.desk = Desk.objects.get(id=kwargs.get('pk'))
|
|
except Desk.DoesNotExist:
|
|
raise Http404()
|
|
if not self.desk.agenda.can_be_managed(request.user):
|
|
raise PermissionDenied()
|
|
return super(ManagedDeskMixin, self).dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super(ManagedDeskMixin, self).get_context_data(**kwargs)
|
|
context['desk'] = self.desk
|
|
context['agenda'] = self.desk.agenda
|
|
return context
|
|
|
|
def get_initial(self):
|
|
initial = super(ManagedDeskMixin, self).get_initial()
|
|
initial['desk'] = self.desk
|
|
return initial
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda.id})
|
|
|
|
|
|
class ManagedDeskSubobjectMixin(object):
|
|
desk = None
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.desk = self.get_object().desk
|
|
if not self.desk.agenda.can_be_managed(request.user):
|
|
raise PermissionDenied()
|
|
return super(ManagedDeskSubobjectMixin, self).dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super(ManagedDeskSubobjectMixin, self).get_context_data(**kwargs)
|
|
context['desk'] = self.object.desk
|
|
context['agenda'] = self.object.desk.agenda
|
|
return context
|
|
|
|
def get_success_url(self):
|
|
return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda.id})
|
|
|
|
|
|
class AgendaSettings(ManagedAgendaMixin, DetailView):
|
|
template_name = 'chrono/manager_agenda_settings.html'
|
|
model = Agenda
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
try:
|
|
self.agenda = Agenda.objects.get(id=kwargs.get('pk'))
|
|
except Agenda.DoesNotExist:
|
|
raise Http404()
|
|
if not self.agenda.can_be_managed(request.user):
|
|
# "events" agendas settings page can be access by user with the
|
|
# view permission as there are no other "view" page for this type
|
|
# of agenda.
|
|
if self.agenda.kind != 'events' or not self.agenda.can_be_viewed(request.user):
|
|
raise PermissionDenied()
|
|
return super(DetailView, self).dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super(AgendaSettings, self).get_context_data(**kwargs)
|
|
context['user_can_manage'] = self.get_object().can_be_managed(self.request.user)
|
|
return context
|
|
|
|
agenda_settings = AgendaSettings.as_view()
|
|
|
|
|
|
class AgendaExport(ManagedAgendaMixin, DetailView):
|
|
model = Agenda
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
response = HttpResponse(content_type='application/json')
|
|
json.dump({'agendas': [self.get_object().export_json()]}, response, indent=2)
|
|
return response
|
|
|
|
agenda_export = AgendaExport.as_view()
|
|
|
|
|
|
class AgendaAddEventView(ManagedAgendaMixin, CreateView):
|
|
template_name = 'chrono/manager_event_form.html'
|
|
model = Event
|
|
form_class = NewEventForm
|
|
|
|
agenda_add_event = AgendaAddEventView.as_view()
|
|
|
|
|
|
class AgendaImportEventsSampleView(TemplateView):
|
|
template_name = 'chrono/manager_sample_events.txt'
|
|
content_type = 'text/csv'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super(AgendaImportEventsSampleView, self).get_context_data(**kwargs)
|
|
some_future_date = datetime.datetime.now() + datetime.timedelta(days=10)
|
|
some_future_date = some_future_date.replace(hour=14, minute=0, second=0)
|
|
context['some_future_date'] = some_future_date
|
|
return context
|
|
|
|
agenda_import_events_sample_csv = AgendaImportEventsSampleView.as_view()
|
|
|
|
|
|
class AgendaImportEventsView(ManagedAgendaMixin, FormView):
|
|
form_class = ImportEventsForm
|
|
template_name = 'chrono/manager_import_events.html'
|
|
agenda = None
|
|
|
|
def form_valid(self, form):
|
|
if form.events:
|
|
for event in form.events:
|
|
event.agenda_id = self.kwargs['pk']
|
|
event.save()
|
|
messages.info(self.request, _('%d events have been imported.') % len(form.events))
|
|
return super(AgendaImportEventsView, self).form_valid(form)
|
|
|
|
agenda_import_events = AgendaImportEventsView.as_view()
|
|
|
|
|
|
class EventEditView(ManagedAgendaSubobjectMixin, UpdateView):
|
|
template_name = 'chrono/manager_event_form.html'
|
|
model = Event
|
|
form_class = EventForm
|
|
|
|
event_edit = EventEditView.as_view()
|
|
|
|
|
|
class EventDeleteView(ManagedAgendaSubobjectMixin, DeleteView):
|
|
template_name = 'chrono/manager_confirm_delete.html'
|
|
model = Event
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super(EventDeleteView, self).get_context_data(**kwargs)
|
|
context['cannot_delete'] = bool(
|
|
self.object.booking_set.filter(cancellation_datetime__isnull=True).exists() and
|
|
self.object.start_datetime > now())
|
|
return context
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
context = self.get_context_data()
|
|
if context['cannot_delete']:
|
|
raise PermissionDenied()
|
|
return super(EventDeleteView, self).delete(request, *args, **kwargs)
|
|
|
|
event_delete = EventDeleteView.as_view()
|
|
|
|
|
|
class AgendaAddMeetingTypeView(ManagedAgendaMixin, CreateView):
|
|
template_name = 'chrono/manager_meeting_type_form.html'
|
|
model = Event
|
|
form_class = NewMeetingTypeForm
|
|
|
|
agenda_add_meeting_type = AgendaAddMeetingTypeView.as_view()
|
|
|
|
class MeetingTypeEditView(ManagedAgendaSubobjectMixin, UpdateView):
|
|
template_name = 'chrono/manager_meeting_type_form.html'
|
|
model = MeetingType
|
|
form_class = MeetingTypeForm
|
|
|
|
meeting_type_edit = MeetingTypeEditView.as_view()
|
|
|
|
|
|
class MeetingTypeDeleteView(ManagedAgendaSubobjectMixin, DeleteView):
|
|
template_name = 'chrono/manager_confirm_delete.html'
|
|
model = MeetingType
|
|
|
|
meeting_type_delete = MeetingTypeDeleteView.as_view()
|
|
|
|
|
|
class AgendaAddTimePeriodView(ManagedDeskMixin, FormView):
|
|
template_name = 'chrono/manager_time_period_form.html'
|
|
form_class = TimePeriodAddForm
|
|
|
|
def form_valid(self, form):
|
|
for weekday in form.cleaned_data.get('weekdays'):
|
|
period = TimePeriod(
|
|
weekday=weekday,
|
|
start_time=form.cleaned_data['start_time'],
|
|
end_time=form.cleaned_data['end_time'],
|
|
desk=self.desk)
|
|
period.save()
|
|
return super(AgendaAddTimePeriodView, self).form_valid(form)
|
|
|
|
agenda_add_time_period = AgendaAddTimePeriodView.as_view()
|
|
|
|
|
|
class TimePeriodEditView(ManagedDeskSubobjectMixin, UpdateView):
|
|
template_name = 'chrono/manager_time_period_form.html'
|
|
model = TimePeriod
|
|
form_class = TimePeriodForm
|
|
|
|
time_period_edit = TimePeriodEditView.as_view()
|
|
|
|
|
|
class TimePeriodDeleteView(ManagedDeskSubobjectMixin, DeleteView):
|
|
template_name = 'chrono/manager_confirm_delete.html'
|
|
model = TimePeriod
|
|
|
|
time_period_delete = TimePeriodDeleteView.as_view()
|
|
|
|
|
|
class AgendaAddDesk(ManagedAgendaMixin, CreateView):
|
|
template_name = 'chrono/manager_desk_form.html'
|
|
model = Desk
|
|
form_class = NewDeskForm
|
|
|
|
|
|
agenda_add_desk = AgendaAddDesk.as_view()
|
|
|
|
|
|
class DeskEditView(ManagedAgendaSubobjectMixin, UpdateView):
|
|
template_name = 'chrono/manager_desk_form.html'
|
|
model = Desk
|
|
form_class = DeskForm
|
|
|
|
|
|
desk_edit = DeskEditView.as_view()
|
|
|
|
|
|
class DeskDeleteView(ManagedAgendaSubobjectMixin, DeleteView):
|
|
template_name = 'chrono/manager_confirm_delete.html'
|
|
model = Desk
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super(DeskDeleteView, self).get_context_data(**kwargs)
|
|
context['cannot_delete'] = Booking.objects.filter(
|
|
event__desk=self.get_object(),
|
|
event__start_datetime__gt=now(),
|
|
cancellation_datetime__isnull=True).exists()
|
|
return context
|
|
|
|
def delete(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
context = self.get_context_data()
|
|
if context['cannot_delete']:
|
|
raise PermissionDenied()
|
|
return super(DeskDeleteView, self).delete(request, *args, **kwargs)
|
|
|
|
|
|
desk_delete = DeskDeleteView.as_view()
|
|
|
|
|
|
class AgendaAddTimePeriodExceptionView(ManagedDeskMixin, CreateView):
|
|
template_name = 'chrono/manager_time_period_exception_form.html'
|
|
model = TimePeriodException
|
|
form_class = TimePeriodExceptionForm
|
|
|
|
|
|
agenda_add_time_period_exception = AgendaAddTimePeriodExceptionView.as_view()
|
|
|
|
|
|
class TimePeriodExceptionEditView(ManagedDeskSubobjectMixin, UpdateView):
|
|
template_name = 'chrono/manager_time_period_exception_form.html'
|
|
model = TimePeriodException
|
|
form_class = TimePeriodExceptionForm
|
|
|
|
|
|
time_period_exception_edit = TimePeriodExceptionEditView.as_view()
|
|
|
|
|
|
class TimePeriodExceptionListView(ManagedDeskMixin, ListView):
|
|
template_name = 'chrono/manager_time_period_exception_list.html'
|
|
model = TimePeriodException
|
|
|
|
def get_queryset(self):
|
|
return self.model.objects.filter(desk=self.desk, end_datetime__gte=now())
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super(TimePeriodExceptionListView, self).get_context_data(**kwargs)
|
|
context['user_can_manage'] = self.desk.agenda.can_be_managed(self.request.user)
|
|
return context
|
|
|
|
|
|
time_period_exception_list = TimePeriodExceptionListView.as_view()
|
|
|
|
|
|
class TimePeriodExceptionDeleteView(ManagedDeskSubobjectMixin, DeleteView):
|
|
template_name = 'chrono/manager_confirm_delete.html'
|
|
model = TimePeriodException
|
|
|
|
|
|
time_period_exception_delete = TimePeriodExceptionDeleteView.as_view()
|
|
|
|
|
|
class DeskImportTimePeriodExceptionsView(ManagedAgendaSubobjectMixin, UpdateView):
|
|
model = Desk
|
|
form_class = ExceptionsImportForm
|
|
template_name = 'chrono/manager_import_exceptions.html'
|
|
|
|
def get_initial(self):
|
|
return {'ics_url': self.get_object().timeperiod_exceptions_remote_url}
|
|
|
|
def form_valid(self, form):
|
|
exceptions = None
|
|
try:
|
|
if form.cleaned_data['ics_file']:
|
|
ics_file_content = force_text(form.cleaned_data['ics_file'].read())
|
|
exceptions = form.instance.create_timeperiod_exceptions_from_ics(ics_file_content)
|
|
elif form.cleaned_data['ics_url']:
|
|
exceptions = form.instance.create_timeperiod_exceptions_from_remote_ics(form.cleaned_data['ics_url'])
|
|
else:
|
|
form.instance.remove_timeperiod_exceptions_from_remote_ics()
|
|
except ICSError as e:
|
|
form.add_error(None, force_text(e))
|
|
return self.form_invalid(form)
|
|
form.instance.timeperiod_exceptions_remote_url = form.cleaned_data['ics_url']
|
|
form.instance.save()
|
|
if exceptions is not None:
|
|
message = ungettext('An exception has been imported.',
|
|
'%(count)d exceptions have been imported.', exceptions)
|
|
message = message % {'count': exceptions}
|
|
messages.info(self.request, message)
|
|
return super(DeskImportTimePeriodExceptionsView, self).form_valid(form)
|
|
|
|
desk_import_time_period_exceptions = DeskImportTimePeriodExceptionsView.as_view()
|
|
|
|
|
|
def menu_json(request):
|
|
label = _('Agendas')
|
|
json_str = json.dumps([{'label': force_text(label),
|
|
'slug': 'calendar',
|
|
'url': request.build_absolute_uri(reverse('chrono-manager-homepage'))
|
|
}])
|
|
content_type = 'application/json'
|
|
for variable in ('jsonpCallback', 'callback'):
|
|
if variable in request.GET:
|
|
json_str = '%s(%s);' % (request.GET[variable], json_str)
|
|
content_type = 'application/javascript'
|
|
break
|
|
response = HttpResponse(content_type=content_type)
|
|
response.write(json_str)
|
|
return response
|