def poll_vote_reminder(): """Send an email reminder every 8 hours to remind valid users to cast their vote. """ now = datetime2pdt() polls = Poll.objects.filter(start__lte=now, end__gt=now) for poll in polls: last_notification = (poll.last_nofication if poll.last_notification else poll.created_on) time_diff = (time.mktime(now.timetuple()) - time.mktime(last_notification.timetuple())) if time_diff > NOTIFICATION_INTERVAL: valid_users = User.objects.filter(groups=poll.valid_groups) recipients = (valid_users.exclude(pk__in=poll.users_voted.all()) .values_list('id', flat=True)) subject = ('[Reminder][Voting] Please cast your vote ' 'for "%s" now!' % poll.name) template_reminder = 'emails/voting_vote_reminder.txt' ctx_data = {'poll': poll} send_remo_mail.delay(recipients, subject, template_reminder, ctx_data) Poll.objects.filter(pk=poll.pk).update(last_notification=now)
def clean(self): """Clean form.""" super(PollAddForm, self).clean() cdata = self.cleaned_data date_now = datetime2pdt() # Check if key exists if not 'start_form' in cdata: raise ValidationError('Please correct the form errors.') cdata['start'] = cdata['start_form'].replace(tzinfo=utc) if cdata['start_form'] >= cdata['end_form']: msg = 'Start date should come before end date.' self._errors['start_form'] = self.error_class([msg]) if cdata['start'] < date_now: msg = 'Start date should not be in the past.' self._errors['start_form'] = self.error_class([msg]) # Check that there is at least one radio or range poll if ((not self.range_poll_formset._count_filled_forms()) and (not self.radio_poll_formset._count_filled_forms())): msg = u'You must fill at least one radio or range poll.' raise ValidationError(msg) return cdata
def automated_poll(sender, instance, **kwargs): """Create a radio poll automatically. If a bug lands in our database with council_vote_requested, create a new Poll and let Council members vote. """ if ((not instance.council_vote_requested or Poll.objects.filter(bug=instance).exists())): return date_now = datetime2pdt() remobot = User.objects.get(username='******') with transaction.commit_on_success(): poll = (Poll.objects.create( name=instance.summary, description=instance.first_comment, valid_groups=Group.objects.get(name='Council'), start=date_now, end=(date_now + timedelta(days=VOTING_PERIOD_AUTOMATED_POLLS)), bug=instance, created_by=remobot, automated_poll=True)) radio_poll = RadioPoll.objects.create(poll=poll, question='Budget Approval') RadioPollChoice.objects.create(answer='Approved', radio_poll=radio_poll) RadioPollChoice.objects.create(answer='Denied', radio_poll=radio_poll) statsd.incr('voting.create_automated_poll')
def edit_voting(request, slug=None): """Create/Edit voting view.""" poll, created = get_or_create_instance(Poll, slug=slug) can_delete_voting = False extra = 0 current_voting_edit = False range_poll_formset = None radio_poll_formset = None if created: poll.created_by = request.user extra = 1 else: if (RangePoll.objects.filter(poll=poll).count() or RadioPoll.objects.filter(poll=poll).count()) == 0: extra = 1 can_delete_voting = True date_now = datetime2pdt() if poll.start < date_now and poll.end > date_now: current_voting_edit = True if current_voting_edit: poll_form = forms.PollEditForm(request.POST or None, instance=poll) else: RangePollFormset = (inlineformset_factory(Poll, RangePoll, formset=forms.BaseRangePollInlineFormSet, extra=extra, can_delete=True)) RadioPollFormset = (inlineformset_factory(Poll, RadioPoll, formset=forms.BaseRadioPollInlineFormSet, extra=extra, can_delete=True)) nominee_list = User.objects.filter( groups__name='Rep', userprofile__registration_complete=True) range_poll_formset = RangePollFormset(request.POST or None, instance=poll, user_list=nominee_list) radio_poll_formset = RadioPollFormset(request.POST or None, instance=poll) poll_form = forms.PollAddForm(request.POST or None, instance=poll, radio_poll_formset=radio_poll_formset, range_poll_formset=range_poll_formset) if poll_form.is_valid(): poll_form.save() if created: messages.success(request, 'Voting successfully created.') else: messages.success(request, 'Voting successfully edited.') return redirect('voting_edit_voting', slug=poll.slug) return render(request, 'edit_voting.html', {'creating': created, 'poll': poll, 'poll_form': poll_form, 'range_poll_formset': range_poll_formset, 'radio_poll_formset': radio_poll_formset, 'can_delete_voting': can_delete_voting, 'current_voting_edit': current_voting_edit})
def create_radio_poll(sender, instance, **kwargs): """Create a radio poll automatically when a new budget or swag bug is submitted. """ # Avoid circular dependencies from remo.voting.models import Poll, RadioPoll, RadioPollChoice if (instance.flag_status == '?' and instance.flag_name == 'remo-review' and instance.component in ('Budget Requests', 'Swag Requests')): if not Poll.objects.filter(bug=instance).exists(): date_now = datetime2pdt() remobot = User.objects.get(username='******') poll = (Poll.objects .create(name=instance.summary, description=instance.first_comment, valid_groups=Group.objects.get(name='Council'), start=date_now, end=(date_now + timedelta(days=3)), bug=instance, created_by=remobot, automated_poll=True)) radio_poll = RadioPoll.objects.create(poll=poll, question='Budget Approval') for answer in ('Approved', 'Denied'): RadioPollChoice.objects.create(answer=answer, radio_poll=radio_poll)
def test_view_post_a_comment(self, fake_messages): """Post a comment on poll.""" poll_start = datetime2pdt() - timedelta(days=5) poll_user = UserFactory.create(groups=['Council']) poll_group = Group.objects.get(name='Council') swag_poll = PollFactory.create(name='swag poll', start=poll_start, end=poll_start + timedelta(days=15), created_by=poll_user, valid_groups=poll_group, automated_poll=True, description='Swag poll description.', slug='swag-poll') vote_url = reverse('voting_view_voting', kwargs={'slug': 'swag-poll'}) factory = RequestFactory() request = factory.post(vote_url, {'comment': 'This is a comment'}, follow=True) request.user = poll_user response = view_voting(request, slug=swag_poll.slug) self.assertTemplateUsed(response, 'list_votings.html') poll_comment = PollComment.objects.get(poll=swag_poll) eq_(poll_comment.user, poll_user) eq_(poll_comment.comment, 'This is a comment') fake_messages.success.assert_called_once_with( mock.ANY, 'Your vote has been successfully registered.')
def automated_poll(sender, instance, **kwargs): """Create a radio poll automatically. If a bug lands in our database with council_vote_requested, create a new Poll and let Council members vote. """ if ((not instance.council_vote_requested or Poll.objects.filter(bug=instance).exists())): return date_now = datetime2pdt() remobot = User.objects.get(username='******') with transaction.commit_on_success(): poll = (Poll.objects .create(name=instance.summary, description=instance.first_comment, valid_groups=Group.objects.get(name='Council'), start=date_now, end=(date_now + timedelta(days=VOTING_PERIOD_AUTOMATED_POLLS)), bug=instance, created_by=remobot, automated_poll=True)) radio_poll = RadioPoll.objects.create(poll=poll, question='Budget Approval') RadioPollChoice.objects.create(answer='Approved', radio_poll=radio_poll) RadioPollChoice.objects.create(answer='Denied', radio_poll=radio_poll) statsd.incr('voting.create_automated_poll')
def test_view_post_a_comment(self, fake_messages): """Post a comment on poll.""" poll_start = datetime2pdt() - timedelta(days=5) poll_user = UserFactory.create(groups=["Council"]) poll_group = Group.objects.get(name="Council") swag_poll = PollFactory.create( name="swag poll", start=poll_start, end=poll_start + timedelta(days=15), created_by=poll_user, valid_groups=poll_group, automated_poll=True, description="Swag poll description.", slug="swag-poll", ) vote_url = reverse("voting_view_voting", kwargs={"slug": "swag-poll"}) factory = RequestFactory() request = factory.post(vote_url, {"comment": "This is a comment"}, follow=True) request.user = poll_user response = view_voting(request, slug=swag_poll.slug) self.assertTemplateUsed(response, "list_votings.html") poll_comment = PollComment.objects.get(poll=swag_poll) eq_(poll_comment.user, poll_user) eq_(poll_comment.comment, "This is a comment") fake_messages.success.assert_called_once_with(mock.ANY, "Your vote has been successfully registered.")
def poll_vote_reminder(): """Send an email reminder every 8 hours to remind valid users to cast their vote. """ now = datetime2pdt() polls = Poll.objects.filter(start__lte=now, end__gt=now) for poll in polls: last_notification = (poll.last_nofication if poll.last_notification else poll.created_on) time_diff = (time.mktime(now.timetuple()) - time.mktime(last_notification.timetuple())) if time_diff > NOTIFICATION_INTERVAL: valid_users = User.objects.filter(groups=poll.valid_groups) recipients = (valid_users.exclude( pk__in=poll.users_voted.all()).values_list('id', flat=True)) subject = ('[Reminder][Voting] Please cast your vote ' 'for "%s" now!' % poll.name) template_reminder = 'emails/voting_vote_reminder.txt' ctx_data = {'poll': poll} send_remo_mail.delay(recipients, subject, template_reminder, ctx_data) Poll.objects.filter(pk=poll.pk).update(last_notification=now)
def setUp(self): """Initial data for the tests.""" self.user = User.objects.get(username="******") self.group = Group.objects.get(name="Council") self._now = datetime2pdt() self.now = self._now.replace(microsecond=0) self.start = self.now self.end = self.now + datetime.timedelta(hours=5 * 24) self.voting = Poll(name="poll", start=self.start, end=self.end, valid_groups=self.group, created_by=self.user) self.voting.save()
def setUp(self): """Initial data for the tests.""" self.user = User.objects.get(username='******') self.group = Group.objects.get(name='Admin') self._now = datetime2pdt() self.now = self._now.replace(microsecond=0) self.start = self.now self.end = self.now + datetime.timedelta(days=5) self.voting = Poll(name='poll', start=self.start, end=self.end, valid_groups=self.group, created_by=self.user) self.voting.save()
def test_email_users_without_a_vote(self, fake_datetime2pdt): """Test sending an email to users who have not cast their vote yet. """ # act like it's today + 1 day fake_datetime2pdt.return_value = datetime2pdt() + datetime.timedelta(days=1) args = ["poll_vote_reminder"] management.call_command("cron", *args) eq_(len(mail.outbox), 3) for email in mail.outbox: eq_(email.to, ["*****@*****.**"])
def test_email_users_without_a_vote(self, fake_datetime2pdt): """Test sending an email to users who have not cast their vote yet. """ # act like it's today + 1 day fake_datetime2pdt.return_value = (datetime2pdt() + datetime.timedelta(days=1)) args = ['poll_vote_reminder'] management.call_command('cron', *args) eq_(len(mail.outbox), 3) for email in mail.outbox: eq_(email.to, ['*****@*****.**'])
def setUp(self): """Initial data for the tests.""" UserFactory.create(username='******', email='*****@*****.**', first_name='ReMo', last_name='bot') self.user = User.objects.get(username='******') self.group = Group.objects.get(name='Council') self._now = datetime2pdt() self.now = self._now.replace(microsecond=0) self.start = self.now self.end = self.now + datetime.timedelta(hours=5*24) self.voting = Poll(name='poll', start=self.start, end=self.end, valid_groups=self.group, created_by=self.user) self.voting.save()
def test_email_users_without_a_vote(self, fake_datetime2pdt): """Test sending an email to users who have not cast their vote yet. """ # act like it's today + 1 day fake_datetime2pdt.return_value = (datetime2pdt() + datetime.timedelta(days=1)) args = ['poll_vote_reminder'] management.call_command('cron', *args) recipients = map(lambda x: '%s' % x.email, User.objects.filter(groups=self.group)) eq_(len(mail.outbox), 3) eq_(mail.outbox[2].to, recipients)
def clean(self): """Clean form.""" super(PollEditForm, self).clean() cdata = self.cleaned_data date_now = datetime2pdt() # Check if key exists if not 'end_form' in cdata: raise ValidationError('Please correct the form errors.') cdata['end'] = cdata['end_form'].replace(tzinfo=utc) if cdata['end'] < date_now: msg = 'End date should not be in the past.' self._errors['end_form'] = self.error_class([msg]) return cdata
def test_extend_voting_period_by_24hours(self, fake_datetime2pdt): """Test extending voting period by 24hours if less than 50% of the valid users have voted and the poll ends in less than 8 hours. """ automated_poll = Poll(name='poll', start=self.start, end=self.end, valid_groups=self.group, created_by=self.user, automated_poll=True) automated_poll.save() # act like it's 4 hours before the end of the poll fake_datetime2pdt.return_value = (datetime2pdt() + datetime.timedelta(hours=116)) args = ['extend_voting_period'] management.call_command('cron', *args) poll = Poll.objects.get(pk=automated_poll.id) eq_(poll.end - automated_poll.end, datetime.timedelta(hours=24))
def __init__(self, *args, **kwargs): """Initialize form. Dynamically set some fields of the form. """ super(PollEditForm, self).__init__(*args, **kwargs) instance = self.instance # Set the year portion of the widget now = datetime.utcnow() end_year = min(getattr(self.instance.end, 'year', now.year), now.year - 1) self.fields['end_form'] = forms.DateTimeField( widget=SplitSelectDateTimeWidget( years=range(end_year, now.year + 10), minute_step=5), validators=[validate_datetime]) if self.instance.end: # Convert to server timezone naive_time = make_naive(instance.end, pytz.UTC) server_time = datetime2pdt(naive_time) self.fields['end_form'].initial = server_time
def extend_voting_period(): """Extend the voting period by 24hours if less than 50% of the Council members has voted and the poll ends in less than NOTIFICATION_INTERVAL. """ now = datetime2pdt() polls = Poll.objects.filter(start__lte=now, end__gt=now, automated_poll=True) for poll in polls: vote_count = poll.users_voted.all().count() missing_vote_count = (User.objects .filter(groups=poll.valid_groups) .exclude(pk__in=poll.users_voted.all()) .count()) half_voted = vote_count < missing_vote_count time_diff = (time.mktime(now.timetuple()) - time.mktime(poll.end.timetuple())) if time_diff < NOTIFICATION_INTERVAL and half_voted: poll.end += datetime.timedelta(seconds=EXTEND_VOTING_PERIOD) poll.save()
def __init__(self, *args, **kwargs): """Initialize form. Dynamically set some fields of the form. """ self.range_poll_formset = kwargs.pop('range_poll_formset') self.radio_poll_formset = kwargs.pop('radio_poll_formset') super(PollAddForm, self).__init__(*args, **kwargs) instance = self.instance # Set the year portion of the widget now = datetime.utcnow() start_year = min(getattr(self.instance.start, 'year', now.year), now.year - 1) self.fields['start_form'] = forms.DateTimeField( widget=SplitSelectDateTimeWidget( years=range(start_year, now.year + 10), minute_step=5), validators=[validate_datetime]) if self.instance.start: naive_time = make_naive(instance.start, pytz.UTC) server_time = datetime2pdt(naive_time) self.fields['start_form'].initial = server_time
def __init__(self, *args, **kwargs): """Initialize form. Dynamically set some fields of the form. """ super(PollEditForm, self).__init__(*args, **kwargs) instance = self.instance # Set the year portion of the widget now = datetime.utcnow() end_year = min(getattr(self.instance.end, 'year', now.year), now.year - 1) self.fields['end_form'] = forms.DateTimeField( widget=SplitSelectDateTimeWidget(years=range( end_year, now.year + 10), minute_step=5), validators=[validate_datetime]) if self.instance.end: # Convert to server timezone naive_time = make_naive(instance.end, pytz.UTC) server_time = datetime2pdt(naive_time) self.fields['end_form'].initial = server_time
def extend_voting_period(): """Extend the voting period by 24hours if less than 50% of the Council members has voted and the poll ends in less than NOTIFICATION_INTERVAL. """ now = datetime2pdt() polls = Poll.objects.filter(start__lte=now, end__gt=now, automated_poll=True) for poll in polls: vote_count = poll.users_voted.all().count() missing_vote_count = (User.objects.filter( groups=poll.valid_groups).exclude( pk__in=poll.users_voted.all()).count()) half_voted = vote_count < missing_vote_count time_diff = (time.mktime(now.timetuple()) - time.mktime(poll.end.timetuple())) if time_diff < NOTIFICATION_INTERVAL and half_voted: poll.end += datetime.timedelta(seconds=EXTEND_VOTING_PERIOD) poll.save()
def __init__(self, *args, **kwargs): """Initialize form. Dynamically set some fields of the form. """ self.range_poll_formset = kwargs.pop('range_poll_formset') self.radio_poll_formset = kwargs.pop('radio_poll_formset') super(PollAddForm, self).__init__(*args, **kwargs) instance = self.instance # Set the year portion of the widget now = datetime.utcnow() start_year = min(getattr(self.instance.start, 'year', now.year), now.year - 1) self.fields['start_form'] = forms.DateTimeField( widget=SplitSelectDateTimeWidget(years=range( start_year, now.year + 10), minute_step=5), validators=[validate_datetime]) if self.instance.start: naive_time = make_naive(instance.start, pytz.UTC) server_time = datetime2pdt(naive_time) self.fields['start_form'].initial = server_time
def view_voting(request, slug): """View voting and cast a vote view.""" user = request.user now = datetime2pdt() poll = get_object_or_404(Poll, slug=slug) # If the user does not belong to a valid poll group if not (user.groups.filter(Q(id=poll.valid_groups.id) | Q(name='Admin')).exists()): messages.error(request, ('You do not have the permissions to ' 'vote on this voting.')) return redirect('voting_list_votings') range_poll_choice_forms = {} radio_poll_choice_forms = {} data = {'poll': poll} # if the voting period has ended, display the results if now > poll.end: return render(request, 'view_voting.html', data) if now < poll.start: # Admin can edit future votings if user.groups.filter(name='Admin').exists(): return redirect('voting_edit_voting', slug=poll.slug) else: messages.warning(request, ('This vote has not yet begun. ' 'You can cast your vote on %s PDT.' % poll.start.strftime('%Y %B %d, %H:%M'))) return redirect('voting_list_votings') # avoid multiple votes from the same user if Vote.objects.filter(poll=poll, user=user).exists(): messages.warning(request, ('You have already cast your vote for this ' 'voting. Come back to see the results on ' '%s PDT.' % poll.end.strftime('%Y %B %d, %H:%M'))) return redirect('voting_list_votings') # pack the forms for rendering for item in poll.range_polls.all(): range_poll_choice_forms[item] = forms.RangePollChoiceVoteForm( data=request.POST or None, choices=item.choices.all()) for item in poll.radio_polls.all(): radio_poll_choice_forms[item] = forms.RadioPollChoiceVoteForm( data=request.POST or None, radio_poll=item) if request.method == 'POST': forms_valid = True # validate all forms for item in (range_poll_choice_forms.values() + radio_poll_choice_forms.values()): if not item.is_valid(): forms_valid = False break if forms_valid: for range_poll_form in range_poll_choice_forms.values(): range_poll_form.save() for radio_poll_form in radio_poll_choice_forms.values(): radio_poll_form.save() Vote.objects.create(user=user, poll=poll) messages.success(request, ('Your vote has been ' 'successfully registered.')) return redirect('voting_list_votings') data['range_poll_choice_forms'] = range_poll_choice_forms data['radio_poll_choice_forms'] = radio_poll_choice_forms return render(request, 'vote_voting.html', data)