# -*- coding: utf-8 -*- import datetime import logging import os from django.contrib import messages from django.contrib.auth import login from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied, SuspiciousOperation, FieldDoesNotExist from django.db.models import Q from django.forms import modelformset_factory from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.urls import reverse, reverse_lazy from django.utils.decorators import method_decorator from django.utils.text import normalize_newlines from django.utils.translation import ugettext as _ from django.views import generic from .. import choices from .. import emails from .. import forms from .. import models from ..roles import get_users_by_role, has_role from ..workflow import DefaultWorkflow logger = logging.getLogger(__name__) class EventListView(generic.ListView): model = models.Event def get_queryset(self): user = self.request.user if user.is_superuser: qs = self.model.objects.all() elif has_role(user, 'manager_super'): qs = self.model.objects.all() else: filter = Q(owner=user) user_sports_list = list() for k in ('W', 'S', 'M', 'K', 'B'): role = 'manager_{}'.format(k.lower()) if has_role(user, role): user_sports_list.append(k) filter |= Q(sport__in=user_sports_list) if has_role(user, 'publisher'): filter |= Q(flags__status__code='accepted') if has_role(user, 'office'): filter |= Q(flags__status__code='submitted') qs = self.model.objects.filter(filter) return qs def get_context_data(self, **kwargs): context = super(EventListView, self).get_context_data(**kwargs) user = self.request.user context['has_permission_export'] = DefaultWorkflow.has_global_permission(user, 'export') return context @method_decorator(login_required) def dispatch(self, request, *args, **kwargs): return super(EventListView, self).dispatch(request, *args, **kwargs) class EventListExportView(generic.FormView): form_class = forms.events.EventListExportForm template_name = 'dav_events/event_list_export_form.html' def form_valid(self, form): filename = _(u'Veranstaltungen') filter_kwargs = { 'flags__status__code': 'accepted', 'registration_closed': False, } if form.cleaned_data['sport']: sport = form.cleaned_data['sport'] filter_kwargs['sport__in'] = sport sport_labels = [u'%s' % choices.SPORT_CHOICES.get_label(code) for code in sport] filename += u'--%s' % u'_+_'.join(sport_labels) if form.cleaned_data['begin']: date = form.cleaned_data['begin'] filter_kwargs['first_day__gte'] = date filename += u'--%s' % date.strftime('%Y-%m-%d') if form.cleaned_data['end']: date = form.cleaned_data['end'] filter_kwargs['first_day__lte'] = date filename += u'--%s' % date.strftime('%Y-%m-%d') if form.cleaned_data['deadline']: deadline = form.cleaned_data['deadline'] else: deadline = None txt = u'' event_qs = models.Event.objects.filter(**filter_kwargs).order_by('first_day', 'number') for event in event_qs: if deadline and event.registration_required and event.deadline: if event.deadline < deadline: continue txt += normalize_newlines(event.render_as_text(format='ka-alpin')) if event.internal_note: txt += u'\n> Bearbeitungshinweis:\n> ' txt += normalize_newlines(event.internal_note).replace('\n', '\n> ') + u'\n' txt += u'\n' + (u'-' * 72) + '\n\n' filename += u'.txt' response = HttpResponse(txt, content_type='text/plain') response['Content-Disposition'] = 'attachment; filename="{filename}"'.format(filename=filename) return response @method_decorator(login_required) def dispatch(self, request, *args, **kwargs): user = request.user if not DefaultWorkflow.has_global_permission(user, 'export'): raise PermissionDenied('export') return super(EventListExportView, self).dispatch(request, *args, **kwargs) class EventPermissionMixin(object): permission = 'view' def has_permission(self, permission, obj): user = self.request.user return obj.workflow.has_permission(user, permission) def enforce_permission(self, obj, permission=None): if permission is None: permission = self.permission if not self.has_permission(permission, obj): raise PermissionDenied(permission) class EventDetailView(EventPermissionMixin, generic.DetailView): model = models.Event def get_object(self, queryset=None): obj = super(EventDetailView, self).get_object(queryset=queryset) self.enforce_permission(obj) return obj def get_context_data(self, **kwargs): context = super(EventDetailView, self).get_context_data(**kwargs) obj = context.get('event') context['has_permission_submit'] = self.has_permission('submit', obj) context['has_permission_accept'] = self.has_permission('accept', obj) context['has_permission_publish'] = self.has_permission('publish', obj) context['has_permission_clear'] = self.has_permission('clear', obj) context['has_permission_update'] = self.has_permission('update', obj) context['is_submitted'] = obj.workflow.has_reached_status('submitted') context['is_accepted'] = obj.workflow.has_reached_status('accepted') context['is_publishing'] = obj.workflow.has_reached_status('publishing') context['is_publishing_any'] = obj.workflow.has_reached_status('publishing*') context['is_published'] = obj.workflow.has_reached_status('published') context['is_published_any'] = obj.workflow.has_reached_status('published*') context['is_cleared'] = obj.workflow.has_reached_status('cleared') return context @method_decorator(login_required) def dispatch(self, request, *args, **kwargs): return super(EventDetailView, self).dispatch(request, *args, **kwargs) class EventRegistrationsView(EventPermissionMixin, generic.DetailView): model = models.Event template_name = 'dav_events/event_registrations.html' def get_form_kwargs(self, process_request=True): kwargs = {} if process_request and self.request.method in ('POST', 'PUT'): kwargs.update({ 'data': self.request.POST, 'files': self.request.FILES, }) if 'form_prefix' in self.request.POST: form_prefix = self.request.POST.get('form_prefix') kwargs['prefix'] = form_prefix pk_field = form_prefix + '-id' else: pk_field = 'id' if pk_field in self.request.POST: pk = self.request.POST.get(pk_field) event = self.object kwargs['instance'] = event.participants.get(pk=pk) return kwargs def get_participant_form(self, process_request=True): event = self.object form = forms.participant.ParticipantForm(**self.get_form_kwargs(process_request=process_request)) form.instance.event = event if not form.instance.position: form.instance.position = event.participants.count() + 1 return form def get_participant_formset(self): event = self.object ParticipantFormSet = modelformset_factory(models.Participant, form=forms.participant.ParticipantForm, extra=0) formset = ParticipantFormSet(queryset=event.participants.all()) return formset def get_context_data(self, **kwargs): context = super(EventRegistrationsView, self).get_context_data(**kwargs) event = context.get('event') context['has_permission_update'] = self.has_permission('update', event) context['has_permission_update_registration'] = self.has_permission('update-registration', event) context['has_permission_update_participants'] = self.has_permission('update-participants', event) context['has_permission_payment'] = self.has_permission('payment', event) context['is_published_any'] = event.workflow.has_reached_status('published*') context['is_done'] = event.workflow.has_reached_status('expired') participants = event.participants.all() context['participants'] = participants if participants.count() > 1: email_list = [u'"{}" <{}>'.format(p.get_full_name(), p.email_address) for p in participants] email_list.sort() context['participant_emails'] = u', '.join(email_list) if 'participant_formset' not in context: context['participant_formset'] = self.get_participant_formset() if 'create_participant_form' not in context: context['create_participant_form'] = self.get_participant_form(process_request=False) registrations_support = hasattr(event, 'registrations') context['registrations_support'] = registrations_support if registrations_support: registrations_all = event.registrations.all() registrations_pending = registrations_all.filter(answered=False) context['registrations_pending'] = registrations_pending context['registrations_all'] = registrations_all context['registrations'] = registrations_all return context def _notify_publisher(self, event, editor): recipients = get_users_by_role('publisher_web') recipients += get_users_by_role('publisher_facebook') for recipient in recipients: if recipient.email: email = emails.EventRegistrationClosedMail(recipient=recipient, event=event, editor=editor) email.send() def _close_registration(self, request, event): logger.info('Close registration: %s', event) event.registration_closed = True event.save(implicit_update=True) self._notify_publisher(event, request.user) messages.success(request, _(u'Die Anmeldung wurde geschlossen')) def _reopen_registration(self, request, event): logger.info('Reopen registration: %s', event) event.registration_closed = False event.save(implicit_update=True) messages.success(request, _(u'Die Anmeldung wurde geöffnet')) def _kill_deadline(self, request, event): logger.info('Delete deadline: %s', event) event.deadline = None event.registration_closed = False event.save(implicit_update=True) messages.success(request, _(u'Der Anmeldeschluss wurde gelöscht')) def _accept_registration(self, request, registration): event = registration.event position = event.participants.count() + 1 data = { 'event': event, 'position': position, 'personal_names': registration.personal_names, 'family_names': registration.family_names, 'address': registration.address, 'postal_code': registration.postal_code, 'city': registration.city, 'email_address': registration.email_address, 'phone_number': registration.phone_number, 'dav_member': registration.dav_member, 'dav_number': registration.dav_number, 'emergency_contact': registration.emergency_contact, 'experience': registration.experience, 'note': registration.note, 'purge_at': registration.purge_at, } participant = models.Participant.objects.create(**data) registration.answered = True registration.save() messages.success(request, _(u'Teilnehmer hinzugefügt: {}'.format(participant.get_full_name()))) def _reject_registration(self, registration): registration.answered = True registration.save() def _swap_participants_position(self, participant1, participant2): event = participant1.event pos_tmp = event.participants.count() + 1 pos1 = participant1.position pos2 = participant2.position participant1.position = pos_tmp participant1.save() participant2.position = pos1 participant2.save() participant1.position = pos2 participant1.save() def get(self, request, *args, **kwargs): self.object = self.get_object() self.enforce_permission(self.object, permission='view-participants') context = self.get_context_data(object=self.object) return self.render_to_response(context) def post(self, request, *args, **kwargs): self.object = self.get_object() event = self.object action = request.POST.get('action') if action == 'close-registration': self.enforce_permission(event, permission='update-registration') self._close_registration(request, event) elif action == 'open-registration': self.enforce_permission(event, permission='update-registration') self._reopen_registration(request, event) elif action == 'kill-deadline': self.enforce_permission(event, permission='update-registration') self._kill_deadline(request, event) elif action == 'accept_registration': self.enforce_permission(event, permission='update-participants') if hasattr(event, 'registrations'): registration_id = request.POST.get('registration') registration = event.registrations.get(id=registration_id) self._accept_registration(request, registration) else: raise FieldDoesNotExist('Event has no registrations') elif action == 'reject_registration': self.enforce_permission(event, permission='update-participants') if hasattr(event, 'registrations'): registration_id = request.POST.get('registration') registration = event.registrations.get(id=registration_id) self._reject_registration(registration) else: raise FieldDoesNotExist('Event has no registrations') elif action == 'confirm_payment': self.enforce_permission(event, permission='payment') participant_id = request.POST.get('id') participant = event.participants.get(id=participant_id) participant.paid = True participant.save() elif action == 'revoke_payment': self.enforce_permission(event, permission='payment') participant_id = request.POST.get('id') participant = event.participants.get(id=participant_id) participant.paid = False participant.save() elif action == 'remove_participant': self.enforce_permission(event, permission='update-participants') participant_id = request.POST.get('id') participant = event.participants.get(id=participant_id) full_name = participant.get_full_name() position = participant.position participant.delete() qs = event.participants.filter(position__gt=position) for participant in qs: participant.position -= 1 participant.save() messages.success(request, _(u'Teilnehmer gelöscht: {}'.format(full_name))) elif action == 'moveup_participant': self.enforce_permission(event, permission='update-participants') participant_id = request.POST.get('id') participant = event.participants.get(id=participant_id) current_position = participant.position if current_position > 1: upper_position = current_position - 1 upper_participant = event.participants.get(position=upper_position) self._swap_participants_position(participant, upper_participant) else: messages.error(request, 'Participant is already on first position') elif action == 'movedown_participant': self.enforce_permission(event, permission='update-participants') participant_id = request.POST.get('id') participant = event.participants.get(id=participant_id) current_position = participant.position if current_position < event.participants.count(): lower_position = current_position + 1 lower_participant = event.participants.get(position=lower_position) self._swap_participants_position(participant, lower_participant) else: messages.error(request, 'Participant is already on last position') elif action == 'update_participant': self.enforce_permission(event, permission='update-participants') form = self.get_participant_form() if form.is_valid(): form.save() participant = form.instance messages.success(request, _(u'Teilnehmer aktualisiert: {}'.format(participant.get_full_name()))) else: formset = self.get_participant_formset() for i in range(0, len(formset)): orig_form = formset.forms[i] if orig_form.prefix == form.prefix: formset.forms[i] = form messages.error(request, _(u'Ungültige Eingabe - Siehe unten')) return self.render_to_response(self.get_context_data(participant_formset=formset)) elif action == 'create_participant': self.enforce_permission(event, permission='update-participants') form = self.get_participant_form() if form.is_valid(): form.save() participant = form.instance messages.success(request, _(u'Teilnehmer hinzugefügt: {}'.format(participant.get_full_name()))) else: messages.error(request, _(u'Ungültige Eingabe - Siehe unten')) return self.render_to_response(self.get_context_data(create_participant_form=form)) else: messages.error(request, 'unsupported action: {}'.format(action)) return self.render_to_response(self.get_context_data()) @method_decorator(login_required) def dispatch(self, request, *args, **kwargs): return super(EventRegistrationsView, self).dispatch(request, *args, **kwargs) class EventUpdateStatusView(EventPermissionMixin, generic.DetailView): model = models.Event def get(self, request, *args, **kwargs): status = kwargs.get('status') event = self.get_object() if status.startswith('submit'): if not self.has_permission('submit', event): raise PermissionDenied(status) elif status.startswith('accept'): if not self.has_permission('accept', event): raise PermissionDenied(status) elif status.startswith('publish'): if not self.has_permission('publish', event): raise PermissionDenied(status) elif status.startswith('clear'): if not self.has_permission('clear', event): raise PermissionDenied(status) else: if not self.has_permission('update', event): raise PermissionDenied(status) valid, return_code, message = event.workflow.validate_status_update(status) if not valid: if return_code == 'not-submitted': message = _(u'Veranstaltung ist noch nicht eingereicht.') elif return_code == 'not-accepted': message = _(u'Veranstaltung ist noch nicht freigegeben.') messages.error(request, message) return HttpResponseRedirect(event.get_absolute_url()) event.workflow.update_status(status, request.user) if status.startswith('submit'): messages.success(request, _(u'Veranstaltung eingereicht.')) elif status.startswith('accept'): messages.success(request, _(u'Veranstaltung freigegeben.')) elif status.startswith('publish'): messages.success(request, _(u'Veröffentlichung registriert.')) elif status.startswith('clear'): messages.success(request, _(u'Abrechnung vermerkt.')) else: messages.success(request, _(u'Veranstaltungsstatus registriert.')) return HttpResponseRedirect(event.get_absolute_url()) class EventUpdateView(EventPermissionMixin, generic.UpdateView): permission = 'update' model = models.Event form_class = forms.events.EventUpdateForm template_name_suffix = '_update_form' def get_object(self, queryset=None): obj = super(EventUpdateView, self).get_object(queryset=queryset) self.enforce_permission(obj) return obj def get_form(self, form_class=None): form = super(EventUpdateView, self).get_form(form_class) if not (self.has_permission('accept', self.object) or self.has_permission('publish', self.object)): form.fields['registration_howto'].disabled = True form.fields['planned_publication_date'].disabled = True form.fields['planned_publication_date'].widget = forms.generic.forms.HiddenInput() form.fields['number'].disabled = True form.fields['number'].widget = forms.generic.forms.HiddenInput() form.fields['owner'].disabled = True form.fields['owner'].widget = forms.generic.forms.HiddenInput() return form def get_context_data(self, **kwargs): context = super(EventUpdateView, self).get_context_data(**kwargs) obj = context.get('event') context['has_permission_accept'] = self.has_permission('accept', obj) context['has_permission_update'] = self.has_permission('update', obj) context['has_permission_publish'] = self.has_permission('publish', obj) context['is_expired'] = obj.workflow.has_reached_status('expired') context['is_publishing'] = obj.workflow.has_reached_status('publishing') context['is_publishing_any'] = obj.workflow.has_reached_status('publishing*') context['is_accepted'] = obj.workflow.has_reached_status('accepted') if 'form' in kwargs and kwargs['form'].errors: context['decollapseAllForms'] = True else: context['decollapseFirstForm'] = True return context def form_valid(self, form): form.instance.editor = self.request.user self.object = form.save() return HttpResponseRedirect(self.get_success_url()) @method_decorator(login_required) def dispatch(self, request, *args, **kwargs): return super(EventUpdateView, self).dispatch(request, *args, **kwargs) class EventCreateView(EventPermissionMixin, generic.FormView): form_class = forms.events.EventCreateForm template_dir = os.path.join('dav_events', 'event_create') default_template_name = 'default.html' abort_url = reverse_lazy('dav_events:root') def get_template_names(self): form = self.get_form() form_name = form.form_name template = os.path.join(self.template_dir, '{}.html'.format(form_name)) default_template = os.path.join(self.template_dir, self.default_template_name) return [ template, default_template ] def get_form_class(self, form_name=None): if form_name is not None: form_class = getattr(forms.events, form_name) elif 'dav_events_event_create_next_form_name' in self.request.session: form_name = self.request.session['dav_events_event_create_next_form_name'] form_class = getattr(forms.events, form_name) if not issubclass(form_class, self.form_class): raise SuspiciousOperation('Invalid next form: {}'.format(form_name)) else: base_form_class = self.form_class initial_form_name = base_form_class.get_initial_form_name() form_class = getattr(forms.events, initial_form_name) return form_class def get_form_kwargs(self): kwargs = super(EventCreateView, self).get_form_kwargs() if 'request' not in kwargs: kwargs['request'] = self.request return kwargs def get_context_data(self, **kwargs): context = super(EventCreateView, self).get_context_data(**kwargs) context['abort_url'] = self.abort_url return context def form_valid(self, form): event = form.get_instance() next_form_name = form.next_form_name if next_form_name: self.request.session['dav_events_event_create_next_form_name'] = next_form_name next_form_class = self.get_form_class(next_form_name) next_form = next_form_class(request=self.request) return self.render_to_response(self.get_context_data(form=next_form, event=event)) else: event.editor = self.request.user event.save() if 'submit' in form.data: event.workflow.update_status('submitted', event.owner) messages.success(self.request, _(u'Veranstaltung eingereicht.')) else: messages.success(self.request, _(u'Veranstaltung angelegt.')) form.flush_session_data() owner = event.owner self.clean_session_data() if self.request.user.is_authenticated: next_url = reverse('dav_events:list') if self.request.user != event.owner: messages.warning(self.request, u'%s %s' % ( _(u'Du hast jemand anderen als Tourenleiter eingetragen.'), _(u'Warum machst du sowas?') )) elif owner.has_usable_password(): next_url = reverse('dav_events:list') else: login(self.request, owner) next_url = reverse('dav_auth:set_password') messages.success(self.request, _(u'Neuen Benutzer angemeldet: %(username)s') % {'username': owner.username}) messages.warning(self.request, _(u'Bitte neues Passwort setzen!')) return HttpResponseRedirect(next_url) def clean_session_data(self, session=None): if session is None: session = self.request.session if 'dav_events_event_create_next_form_name' in session: del session['dav_events_event_create_next_form_name'] def get(self, request, *args, **kwargs): self.clean_session_data(request.session) if 'abort' in request.GET: form = self.get_form() form.flush_session_data() return HttpResponseRedirect(self.abort_url) elif 'copy' in request.GET: event = get_object_or_404(models.Event, pk=request.GET.get('copy')) if not self.has_permission('view', event): raise PermissionDenied('copy') for field in ('id', 'owner', 'created_at', 'number', 'charge', 'registration_howto', 'registration_closed', 'planned_publication_date', 'internal_note', ): if hasattr(event, field): setattr(event, field, None) initial_form_name = self.form_class.get_initial_form_name() form_class = getattr(forms.events, initial_form_name) form = form_class(request=self.request, instance=event) form.save_session_data() return super(EventCreateView, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): return super(EventCreateView, self).post(request, *args, **kwargs)