# 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 . import datetime import json from django.contrib import messages from django.core.exceptions import PermissionDenied from django.db.models import Q from django.forms import ValidationError from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.urls import reverse, reverse_lazy 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, TimePeriodExceptionSource, ) 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 get_form_kwargs(self): kwargs = super(AgendaImportEventsView, self).get_form_kwargs() kwargs['agenda_pk'] = self.kwargs['pk'] return kwargs def form_valid(self, form): if form.events: for event in form.events: event.agenda_id = self.kwargs['pk'] if event.slug and Event.objects.filter(agenda_id=event.agenda_id, slug=event.slug).exists(): raise ValidationError(_('Duplicated event identifier')) 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 paginate_by = 20 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 TimePeriodExceptionExtractListView(TimePeriodExceptionListView): paginate_by = None # no pagination def get_queryset(self): # but limit queryset return super(TimePeriodExceptionExtractListView, self).get_queryset()[:10] time_period_exception_extract_list = TimePeriodExceptionExtractListView.as_view() class TimePeriodExceptionDeleteView(ManagedDeskSubobjectMixin, DeleteView): template_name = 'chrono/manager_confirm_exception_delete.html' model = TimePeriodException def get_success_url(self): referer = self.request.META.get('HTTP_REFERER') success_url = reverse('chrono-manager-time-period-exception-list', kwargs={'pk': self.desk.agenda_id}) if success_url in referer: return success_url return super(TimePeriodExceptionDeleteView, self).get_success_url() 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_context_data(self, **kwargs): context = super(DeskImportTimePeriodExceptionsView, self).get_context_data(**kwargs) context['exception_sources'] = self.get_object().timeperiodexceptionsource_set.all() return context def form_valid(self, form): exceptions = None try: if form.cleaned_data['ics_file']: exceptions = form.instance.import_timeperiod_exceptions_from_ics_file( form.cleaned_data['ics_file'] ) elif form.cleaned_data['ics_url']: exceptions = form.instance.import_timeperiod_exceptions_from_remote_ics( form.cleaned_data['ics_url'] ) except ICSError as e: form.add_error(None, force_text(e)) return self.form_invalid(form) 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() class TimePeriodExceptionSourceDeleteView(ManagedDeskSubobjectMixin, DeleteView): template_name = 'chrono/manager_confirm_source_delete.html' model = TimePeriodExceptionSource def get_success_url(self): return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda_id}) time_period_exception_source_delete = TimePeriodExceptionSourceDeleteView.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