class Event(DeckBaseModel):
    allow_public_voting = models.BooleanField(_('Allow Public Voting'),
                                              default=True)
    closing_date = models.DateTimeField(null=False, blank=False)
    slots = models.SmallIntegerField(_('Slots'), default=10)

    # relations
    jury = models.OneToOneField(to='jury.Jury', related_name='event',
                                null=True, blank=True)
    anonymous_voting = models.BooleanField(
        _('Anonymous Voting?'), default=False)

    class Meta:
        ordering = ['-closing_date', '-created_at']
        verbose_name = _('Event')
        verbose_name_plural = _('Events')

    @property
    def closing_date_is_passed(self):
        return timezone.now() > self.closing_date

    @property
    def closing_date_is_close(self):
        if self.closing_date_is_passed:
            return False
        return timezone.now() > self.closing_date - timezone.timedelta(days=7)

    def get_absolute_url(self):
        return reverse('view_event', kwargs={'slug': self.slug})

    def user_can_see_proposals(self, user):
        can_see_proposals = False
        if user.is_superuser or self.author == user:
            can_see_proposals = True
        elif self.allow_public_voting:
            can_see_proposals = True
        elif (not user.is_anonymous() and
              self.jury.users.filter(pk=user.pk).exists()):
            can_see_proposals = True
        return can_see_proposals

    def get_proposers_count(self):
        return self.proposals.values_list(
            'author', flat=True).distinct().count()

    def get_votes_count(self):
        return self.proposals.values_list('votes', flat=True).count()

    def get_votes_to_export(self):
        return self.proposals.values(
            'id', 'title', 'author__username', 'author__email'
        ).annotate(
            Sum('votes__rate')
        ).annotate(Count('votes'))

    def get_schedule(self):
        schedule = Activity.objects.filter(track__event=self)\
            .cached_authors()\
            .annotate(Sum('proposal__votes__rate'))\
            .extra(select=dict(track_isnull='track_id IS NULL'))\
            .order_by('track_isnull', 'track_order',
                      '-proposal__votes__rate__sum')
        return schedule

    def get_not_approved_schedule(self):
        return self.proposals\
            .cached_authors()\
            .filter(
                models.Q(is_approved=False) |
                models.Q(track__isnull=True))

    def get_main_track(self):
        return self.tracks.first()

    def filter_not_scheduled_by_slots(self):
        return self.get_not_approved_schedule()[:self.slots]

    def user_in_jury(self, user):
        return self.jury.users.filter(pk=user.pk).exists() class Proposal(Activity):
    is_approved = models.BooleanField(_('Is approved'), default=False)
    more_information = models.TextField(
        _('More information'), max_length=10000, null=True, blank=True)
    slides_url = models.CharField(
        _('speakerdeck.com/'), max_length=250, null=True, blank=True)

    def get_full_slides_url(self):
        if self.slides_url:
            return 'http://www.speakerdeck.com/{0}'.format(self.slides_url)

    # relations
    event = models.ForeignKey(to='deck.Event', related_name='proposals')

    class Meta:
        ordering = ['title']
        verbose_name = _('Proposal')
        verbose_name_plural = _('Proposals')

    def save(self, *args, **kwargs):
        if not self.pk and self.event.closing_date_is_passed:
            raise ValidationError(
                _("This Event doesn't accept Proposals anymore."))
        return super(Proposal, self).save(*args, **kwargs)

    @property
    def get_rate(self):
        rate = None
        try:
            rate = self.votes__rate__sum
        except AttributeError:
            rate = self.votes.aggregate(Sum('rate'))['rate__sum']
        finally:
            return rate or 0

    def rate(self, user, rate):
        rate_int = [r[0] for r in Vote.VOTE_RATES if rate in r][0]
        with transaction.atomic():
            self.votes.update_or_create(user=user, defaults={'rate': rate_int})

    def user_already_voted(self, user):
        if isinstance(user, AnonymousUser):
            return False
        return self.votes.filter(user=user).exists()

    def user_can_vote(self, user):
        can_vote = False
        if self.author == user and not self.event.author == user:
            pass
        elif self.event.allow_public_voting:
            can_vote = True
        elif user.is_superuser:
            can_vote = True
        elif self.event.jury.users.filter(pk=user.pk).exists():
            can_vote = True
        return can_vote

    def user_can_approve(self, user):
        can_approve = False
        if user.is_superuser:
            can_approve = True
        elif self.event.jury.users.filter(pk=user.pk).exists():
            can_approve = True
        return can_approve

    def get_absolute_url(self):
        return reverse('view_event', kwargs={'slug': self.event.slug}) + \
            '#' + self.slug

    def approve(self):
        if self.is_approved:
            raise ValidationError(_("This Proposal was already approved."))
        self.is_approved = True
        self.save()

    def disapprove(self):
        if not self.is_approved:
            raise ValidationError(_("This Proposal was already disapproved."))
        self.is_approved = False
        self.save() class Vote(models.Model):
    ANGRY, SLEEPY, SAD, HAPPY, LAUGHING = range(-1, 4)
    VOTE_TITLES = dict(
        angry=_('Angry'), sad=_('Sad'),
        sleepy=_('Sleepy'), happy=_('Happy'),
        laughing=_('Laughing')
    )
    VOTE_RATES = ((ANGRY, 'angry'),
                  (SAD, 'sad'),
                  (SLEEPY, 'sleepy'),
                  (HAPPY, 'happy'),
                  (LAUGHING, 'laughing'))
    rate = models.SmallIntegerField(_('Rate Index'), null=True, blank=True,
                                    choices=VOTE_RATES)

    # relations
    proposal = models.ForeignKey(to='deck.Proposal', related_name='votes')
    user = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='votes')

    class Meta:
        verbose_name = _('Vote')
        verbose_name_plural = _('Votes')
        unique_together = (('proposal', 'user'),)

    def __str__(self):
        return six.text_type("{0.user}: {0.rate} in {0.proposal}".format(self))

    def save(self, *args, **kwargs):
        validation_message = None

        user_is_in_jury = self.proposal.event.jury.users.filter(
            pk=self.user.pk).exists()
        if (self.user.is_superuser or user_is_in_jury):
            pass
        elif self.user == self.proposal.author:
            validation_message = _(u'You cannot Rate your own proposals.')
        elif not self.proposal.event.allow_public_voting:
            validation_message = _(u"Proposal doesn't accept Public Voting.")

        if validation_message:
            raise ValidationError(_(validation_message))

        return super(Vote, self).save(*args, **kwargs) class Activity(DeckBaseModel):
    PROPOSAL = 'proposal'
    WORKSHOP = 'workshop'
    OPENNING = 'openning'
    COFFEEBREAK = 'coffee-break'
    LUNCH = 'lunch'
    LIGHTNINGTALKS = 'lightning-talks'
    ENDING = 'ending'
    ACTIVITY_TYPES = (
        (PROPOSAL, _('Proposal')),
        (WORKSHOP, _('Workshop')),
        (OPENNING, _('Openning')),
        (COFFEEBREAK, _('Coffee Break')),
        (LUNCH, _('Lunch')),
        (LIGHTNINGTALKS, _('Lightning Talks')),
        (ENDING, _('Ending')),
    )
    start_timetable = models.TimeField(
        _('Start Timetable'), null=True, blank=False)
    end_timetable = models.TimeField(
        _('End Timetable'), null=True, blank=False)
    track_order = models.SmallIntegerField(_('Order'), null=True, blank=True)
    activity_type = models.CharField(
        _('Type'), choices=ACTIVITY_TYPES, default=PROPOSAL, max_length=50)

    # relations
    track = models.ForeignKey(to='deck.Track', related_name='activities',
                              null=True, blank=True)

    class Meta:
        ordering = ('track_order', 'start_timetable', 'pk')
        verbose_name = _('Activity')
        verbose_name_plural = _('Activities')

    @property
    def timetable(self):
        if all([self.start_timetable is None, self.end_timetable is None]):
            return '--:--'

        return '{0} - {1}'.format(
            self.start_timetable.strftime('%H:%M'),
            self.end_timetable.strftime('%H:%M')
        )

    @property
    def is_proposal(self):
        return self.activity_type == self.PROPOSAL def get_activities_by_parameters_order(ids):
    activities = []
    for id_ in ids:
        activities.append(
            Activity.objects.get(id=id_)
        )
    return activities class EventForm(forms.ModelForm):
    class Meta:
        model = Event
        exclude = ['author', 'jury']
        widgets = {
            'title': forms.TextInput(attrs={'class': 'inline-input'}),
            'closing_date': CustomDateTimeWidget(attrs={
                'id': 'id_closing_date',
                'class': 'inline-input',
                'placeholder': 'Closing Date'
            }),
        } class ProposalForm(forms.ModelForm):
    description = forms.CharField(required=True, widget=forms.Textarea())

    class Meta:
        model = Proposal
        exclude = [
            'event', 'author', 'track', 'rate',
            'is_approved', 'track_order',
            'activity_type', 'start_timetable', 'end_timetable',
        ]
        widgets = {
            'title': forms.TextInput(attrs={'class': 'inline-input'}),
            'slides_url': forms.TextInput(
                attrs={
                    'class': 'form-control',
                    'placeholder': 'jane_doe/talk',
                }),
        } class ActivityForm(forms.ModelForm):
    # Removing the Proposal type from the available options
    activity_type = forms.ChoiceField(
        choices=[actitvity_type for actitvity_type in Activity.ACTIVITY_TYPES
                 if actitvity_type[0] != Activity.PROPOSAL])

    class Meta:
        model = Activity
        fields = [
            'title', 'description', 'activity_type',
            'start_timetable', 'end_timetable',
        ]
        widgets = {
            'title': forms.TextInput(attrs={'class': 'inline-input'}),
            'start_timetable': CustomTimeInputWidget(format='%H:%M'),
            'end_timetable': CustomTimeInputWidget(format='%H:%M'),
        } class ActivityTimetableForm(forms.ModelForm):
    class Meta:
        model = Activity
        fields = [
            'start_timetable', 'end_timetable',
        ]
        widgets = {
            'start_timetable': CustomTimeInputWidget(format='%H:%M'),
            'end_timetable': CustomTimeInputWidget(format='%H:%M'),
        } def has_manage_schedule_permission(user, event):
    return user.is_superuser or event.user_in_jury(user) def initialize_event_schedule(event):
    track = event.get_main_track()

    if not track.has_activities():
        proposals_to_schedule = event.filter_not_scheduled_by_slots()

        for order, proposal in enumerate(proposals_to_schedule):
            track.add_proposal_to_slot(proposal, order)

    return True def rearrange_event_schedule(event, new_activities_arrangement):
    if not new_activities_arrangement:
        raise EmptyActivitiesArrangementException

    track = event.get_main_track()
    track.refresh_track()

    for order, activity in enumerate(new_activities_arrangement):
        track.add_activity_to_slot(activity, order)

    return new_activities_arrangement class EmptyActivitiesArrangementException(Exception):
    """
    Raised when activities arrangement is empty.
    """
import json
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ValidationError
from django.core.mail import send_mail
from django.core.urlresolvers import reverse
from django.db import IntegrityError, models
from django.template.loader import render_to_string
from django.http import HttpResponse, HttpResponseRedirect
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _
from vanilla import CreateView, DeleteView, DetailView, ListView, UpdateView
from djqscsv import render_to_csv_response
from .models import Event, Proposal, Vote, Activity, get_activities_by_parameters_order
from .forms import EventForm, ProposalForm, ActivityForm, ActivityTimetableForm
from core.mixins import LoginRequiredMixin, FormValidRedirectMixing
from deck.permissions import has_manage_schedule_permission
from deck.use_cases import initialize_event_schedule, rearrange_event_schedule
from deck.exceptions import EmptyActivitiesArrangementException
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
    def form_valid(self, form):
        self.object = form.save(commit=False)
        self.object.author = self.request.user
        self.object.save()

        if settings.SEND_NOTIFICATIONS:
            self.send_event_creation_email()

        return self.success_redirect(_(u'Event created.'))

    def send_event_creation_email(self):
        event = self.object
        context = {'event_title': event.title}
        message = render_to_string('mailing/event_created.txt', context)
        subject = _(u'Your event is ready to receive proposals')
        send_mail(subject, message,
                  settings.NO_REPLY_EMAIL, [event.author.email])


class DetailEvent(BaseEventView, DetailView):
    template_name = 'event/event_detail.html'

    def get_context_data(self, **kwargs):
        context = super(DetailEvent, self).get_context_data(**kwargs)
        context['vote_rates'] = Vote.VOTE_RATES
        event_proposals = self.object.proposals.cached_authors()
        if self.object.user_can_see_proposals(self.request.user):
            if not self.request.user.is_anonymous():
                event_proposals = event_proposals.order_by_never_voted(
                    user_id=self.request.user.id)
        elif not self.request.user.is_anonymous():
            event_proposals = event_proposals.filter(author=self.request.user)
        else:
            event_proposals = event_proposals.none()
        context.update(event_proposals=event_proposals)
        return context


class ListMyEvents(LoginRequiredMixin, BaseEventView, ListView):
    template_name = 'event/my_events.html'

    def get_queryset(self):
        return Event.objects.filter(author_id=self.request.user.id)


class UpdateEvent(BaseEventView, UpdateView, FormValidRedirectMixing):
    template_name = 'event/event_form.html'

    def form_valid(self, form):
        self.object = form.save()
        return self.success_redirect(_(u'Event updated.'))

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        event = self.get_object()
        if (event.author != self.request.user and
           not self.request.user.is_superuser):
            messages.error(
                self.request, _(u'You are not allowed to see this page.'))
            return HttpResponseRedirect(
                reverse('view_event', kwargs={'slug': event.slug}),
            )
        return super(UpdateEvent, self).dispatch(*args, **kwargs)


class DeleteEvent(BaseEventView, DeleteView):
    template_name = 'event/event_confirm_delete.html'

    def post(self, request, *args, **kwargs):
        event = self.get_object()
        event.delete()
        messages.success(self.request, _(u'Event deleted.'))
        return HttpResponseRedirect(reverse('my_events'))

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        event = self.get_object()
        if (event.author != self.request.user and
           not self.request.user.is_superuser):
            messages.error(
                self.request, _(u'You are not allowed to see this page.'))
            return HttpResponseRedirect(event.get_absolute_url())
        return super(DeleteEvent, self).dispatch(*args, **kwargs)


class ExportEvent(BaseEventView, DetailView):
    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        event = self.get_object()
        if (event.author != self.request.user and
           not self.request.user.is_superuser):
            messages.error(
                self.request, _(u'You are not allowed to see this page.'))
            return HttpResponseRedirect(reverse('list_events'))
        return super(ExportEvent, self).dispatch(*args, **kwargs)

    def get(self, request, *args, **kwargs):
        event = self.get_object()
        filename = "event_%s_export" % event.slug.replace('-', '_')
        field_header_map = {
            'author__username': _('Author'),
            'author__email': _('Author E-Mail'),
            'votes__rate__sum': _('Vote Rate'),
            'votes__count': _('Votes Count'),
        }
        proposals = event.get_votes_to_export()
        return render_to_csv_response(
            proposals,
            append_datestamp=True,
            filename=filename,
            field_header_map=field_header_map
        )


class CreateEventSchedule(BaseEventView, DetailView):
    template_name = 'event/event_create_schedule.html'

    def get_context_data(self, **kwargs):
        context = super(CreateEventSchedule, self).get_context_data(**kwargs)
        context.update(activity_form=ActivityForm())

        context.update(activity_timetable_form=ActivityTimetableForm())