diff --git a/ietf/nomcom/forms.py b/ietf/nomcom/forms.py index 303ab6c12..f16d0d51b 100644 --- a/ietf/nomcom/forms.py +++ b/ietf/nomcom/forms.py @@ -1,3 +1,5 @@ +import datetime + from django.conf import settings from django import forms from django.contrib.formtools.preview import FormPreview, AUTO_ID @@ -8,6 +10,7 @@ from django.template.loader import render_to_string from django.utils.decorators import method_decorator from django.shortcuts import render_to_response from django.template.context import RequestContext +from django.contrib.sites.models import Site from ietf.dbtemplate.forms import DBTemplateForm from ietf.utils import unaccent @@ -22,7 +25,7 @@ from ietf.nomcom.models import NomCom, Nomination, Nominee, NomineePosition, \ from ietf.nomcom.utils import QUESTIONNAIRE_TEMPLATE, NOMINATION_EMAIL_TEMPLATE, \ INEXISTENT_PERSON_TEMPLATE, NOMINEE_EMAIL_TEMPLATE, \ NOMINATION_RECEIPT_TEMPLATE, FEEDBACK_RECEIPT_TEMPLATE, \ - get_user_email + get_user_email, get_hash_nominee_position, get_year_by_nomcom from ietf.nomcom.decorators import member_required ROLODEX_URL = getattr(settings, 'ROLODEX_URL', None) @@ -190,17 +193,13 @@ class EditChairFormPreview(FormPreview): return HttpResponseRedirect(reverse('nomcom_edit_chair', kwargs={'year': self.year})) -class EditPublicKeyForm(BaseNomcomForm, forms.ModelForm): +class EditNomcomForm(BaseNomcomForm, forms.ModelForm): - fieldsets = [('Public Key', ('public_key',))] + fieldsets = [('Edit nomcom', ('public_key', 'send_questionnaire'))] class Meta: model = NomCom - fields = ('public_key',) - - def __init__(self, *args, **kwargs): - super(EditPublicKeyForm, self).__init__(*args, **kwargs) - self.fields['public_key'].required = True + fields = ('public_key', 'send_questionnaire') class MergeForm(BaseNomcomForm, forms.Form): @@ -393,8 +392,30 @@ class NominateForm(BaseNomcomForm, forms.ModelForm): subject = 'IETF Nomination Information' from_email = settings.NOMCOM_FROM_EMAIL to_email = email.address + domain = Site.objects.get_current().domain + today = datetime.date.today().strftime('%Y%m%d') + hash = get_hash_nominee_position(today, nominee_position.id) + accept_url = reverse('nomcom_process_nomination_status', + None, + args=(get_year_by_nomcom(self.nomcom), + nominee_position.id, + 'accepted', + today, + hash)) + decline_url = reverse('nomcom_process_nomination_status', + None, + args=(get_year_by_nomcom(self.nomcom), + nominee_position.id, + 'declined', + today, + hash)) + context = {'nominee': email.person.name, - 'position': position.name} + 'position': position.name, + 'domain': domain, + 'accept_url': accept_url, + 'decline_url': decline_url} + path = nomcom_template_path + NOMINEE_EMAIL_TEMPLATE send_mail(None, to_email, from_email, subject, path, context) diff --git a/ietf/nomcom/urls.py b/ietf/nomcom/urls.py index c907d9e8a..58602e32e 100644 --- a/ietf/nomcom/urls.py +++ b/ietf/nomcom/urls.py @@ -1,4 +1,5 @@ from django.conf.urls.defaults import patterns, url +from django.views.generic.simple import direct_to_template from ietf.nomcom.forms import EditChairForm, EditChairFormPreview, \ EditMembersForm, EditMembersFormPreview @@ -14,7 +15,9 @@ urlpatterns = patterns('ietf.nomcom.views', url(r'^(?P\d{4})/private/send-reminder-mail/$', 'send_reminder_mail', name='nomcom_send_reminder_mail'), url(r'^(?P\d{4})/private/edit-members/$', EditMembersFormPreview(EditMembersForm), name='nomcom_edit_members'), url(r'^(?P\d{4})/private/edit-chair/$', EditChairFormPreview(EditChairForm), name='nomcom_edit_chair'), - url(r'^(?P\d{4})/private/edit-publickey/$', 'edit_publickey', name='nomcom_edit_publickey'), + url(r'^(?P\d{4})/private/edit-nomcom/$', 'edit_nomcom', name='nomcom_edit_nomcom'), + url(r'^(?P\d{4})/private/delete-nomcom/$', 'delete_nomcom', name='nomcom_delete_nomcom'), + url(r'^deleted/$', direct_to_template, {'template': 'nomcom/deleted.html'}, name='nomcom_deleted'), url(r'^(?P\d{4})/private/chair/templates/$', 'list_templates', name='nomcom_list_templates'), url(r'^(?P\d{4})/private/chair/templates/(?P\d+)/$', 'edit_template', name='nomcom_edit_template'), url(r'^(?P\d{4})/private/chair/position/$', 'list_positions', name='nomcom_list_positions'), @@ -27,6 +30,7 @@ urlpatterns = patterns('ietf.nomcom.views', url(r'^(?P\d{4})/questionnaires/$', 'questionnaires', name='nomcom_questionnaires'), url(r'^(?P\d{4})/feedback/$', 'public_feedback', name='nomcom_public_feedback'), url(r'^(?P\d{4})/nominate/$', 'public_nominate', name='nomcom_public_nominate'), + url(r'^(?P\d{4})/process-nomination-status/(?P\d+)/(?P[\w]+)/(?P[\d]+)/(?P[a-f0-9]+)/$', 'process_nomination_status', name='nomcom_process_nomination_status'), url(r'^ajax/position-text/(?P\d+)/$', 'ajax_position_text', name='nomcom_ajax_position_text'), ) diff --git a/ietf/nomcom/utils.py b/ietf/nomcom/utils.py index 7d4f605c9..d0fcfaa11 100644 --- a/ietf/nomcom/utils.py +++ b/ietf/nomcom/utils.py @@ -1,3 +1,6 @@ +import hashlib +import re + from django.conf import settings from django.core.exceptions import PermissionDenied from django.shortcuts import get_object_or_404 @@ -34,6 +37,12 @@ def get_nomcom_by_year(year): group__state__slug='active') +def get_year_by_nomcom(nomcom): + acronym = nomcom.group.acronym + m = re.search('(?P\d\d\d\d)', acronym) + return m.group(0) + + def get_user_email(user): emails = Email.objects.filter(person__user=user) email = emails and emails[0] or None @@ -52,6 +61,10 @@ def is_nomcom_chair(user, nomcom): raise PermissionDenied("Must be nomcom chair") +def get_hash_nominee_position(date, nominee_position_id): + return hashlib.md5('%s%s%s' % (settings.SECRET_KEY, date, nominee_position_id)).hexdigest() + + def initialize_templates_for_group(group): for template_name in DEFAULT_NOMCOM_TEMPLATES: template_path = MAIN_NOMCOM_TEMPLATE_PATH + template_name diff --git a/ietf/nomcom/views.py b/ietf/nomcom/views.py index 56429ef33..89535857a 100644 --- a/ietf/nomcom/views.py +++ b/ietf/nomcom/views.py @@ -1,8 +1,11 @@ # -*- coding: utf-8 -*- +import datetime + +from django.views.generic.create_update import delete_object from django.conf import settings from django.contrib.auth.decorators import login_required from django.core.urlresolvers import reverse -from django.http import HttpResponse, Http404, HttpResponseRedirect +from django.http import HttpResponse, Http404, HttpResponseRedirect, HttpResponseForbidden from django.shortcuts import render_to_response, get_object_or_404 from django.template import RequestContext from django.template.loader import render_to_string @@ -17,12 +20,14 @@ from ietf.dbtemplate.views import template_edit from ietf.name.models import NomineePositionState, FeedbackType from ietf.nomcom.decorators import member_required, private_key_required -from ietf.nomcom.forms import (EditPublicKeyForm, NominateForm, FeedbackForm, MergeForm, - NomComTemplateForm, PositionForm, PrivateKeyForm) -from ietf.nomcom.models import Position, NomineePosition, Nominee, Feedback +from ietf.nomcom.forms import (NominateForm, FeedbackForm, MergeForm, + NomComTemplateForm, PositionForm, PrivateKeyForm, + EditNomcomForm) +from ietf.nomcom.models import Position, NomineePosition, Nominee, Feedback, NomCom from ietf.nomcom.utils import (get_nomcom_by_year, HOME_TEMPLATE, retrieve_nomcom_private_key, - store_nomcom_private_key, NOMINEE_REMINDER_TEMPLATE) + store_nomcom_private_key, get_hash_nominee_position, + NOMINEE_REMINDER_TEMPLATE) def index(request, year): @@ -71,13 +76,13 @@ def private_index(request, year): nominations = all_nominee_positions.filter(id__in=nominations_to_modify) if action == "set_as_accepted": nominations.update(state='accepted') - message = ('success', 'The selected nominations has been set as accepted') + message = ('success', 'The selected nominations have been set as accepted') elif action == "set_as_declined": nominations.update(state='declined') - message = ('success', 'The selected nominations has been set as declined') + message = ('success', 'The selected nominations have been set as declined') elif action == "set_as_pending": nominations.update(state='pending') - message = ('success', 'The selected nominations has been set as pending') + message = ('success', 'The selected nominations have been set as pending') else: message = ('warning', "Please, select some nominations to work with") @@ -164,7 +169,7 @@ def private_merge(request, year): form = MergeForm(request.POST, nomcom=nomcom) if form.is_valid(): form.save() - message = ('success', 'The emails has been unified') + message = ('success', 'The emails have been unified') else: form = MergeForm(nomcom=nomcom) @@ -251,6 +256,42 @@ def private_feedback(request, year): return feedback(request, year, False) +def process_nomination_status(request, year, nominee_position_id, state, date, hash): + valid = get_hash_nominee_position(date, nominee_position_id) == hash + if not valid: + return HttpResponseForbidden("Bad hash!") + expiration_days = getattr(settings, 'DAYS_TO_EXPIRE_NOMINATION_LINK', None) + if expiration_days: + request_date = datetime.date(int(date[:4]), int(date[4:6]), int(date[6:])) + if datetime.date.today() > (request_date + datetime.timedelta(days=settings.DAYS_TO_EXPIRE_REGISTRATION_LINK)): + return HttpResponseForbidden("Link expired") + + need_confirmation = True + nomcom = get_nomcom_by_year(year) + nominee_position = get_object_or_404(NomineePosition, id=nominee_position_id) + if nominee_position.state.slug != "pending": + return HttpResponseForbidden("The nomination already was %s" % nominee_position.state) + + state = get_object_or_404(NomineePositionState, slug=state) + message = ('warning', "Are you sure to change the nomination on %s as %s?" % (nominee_position.position.name, + state.name)) + if request.method == 'POST': + nominee_position.state = state + nominee_position.save() + need_confirmation = False + message = message = ('success', 'Your nomination on %s has been set as %s' % (nominee_position.position.name, + state.name)) + + return render_to_response('nomcom/process_nomination_status.html', + {'message': message, + 'nomcom': nomcom, + 'year': year, + 'nominee_position': nominee_position, + 'state': state, + 'need_confirmation': need_confirmation, + 'selected': 'feedback'}, RequestContext(request)) + + def feedback(request, year, public): nomcom = get_nomcom_by_year(year) has_publickey = nomcom.public_key and True or False @@ -358,27 +399,42 @@ def view_feedback_nominee(request, year, nominee_id): @member_required(role='chair') -def edit_publickey(request, year): +def edit_nomcom(request, year): nomcom = get_nomcom_by_year(year) message = ('warning', 'Previous data will remain encrypted with the old key') if request.method == 'POST': - form = EditPublicKeyForm(request.POST, - request.FILES, - instance=nomcom, - initial={'public_key': None}) + form = EditNomcomForm(request.POST, + request.FILES, + instance=nomcom) if form.is_valid(): form.save() - message = ('success', 'The public key has been changed') + message = ('success', 'The nomcom has been changed') else: - form = EditPublicKeyForm() + form = EditNomcomForm(instance=nomcom) - return render_to_response('nomcom/edit_publickey.html', + return render_to_response('nomcom/edit_nomcom.html', {'form': form, - 'group': nomcom.group, + 'nomcom': nomcom, 'message': message, 'year': year, - 'selected': 'edit_publickey'}, RequestContext(request)) + 'selected': 'edit_nomcom'}, RequestContext(request)) + + +@member_required(role='chair') +def delete_nomcom(request, year): + nomcom = get_nomcom_by_year(year) + post_delete_redirect = reverse('nomcom_deleted') + extra_context = {'year': year, + 'selected': 'edit_nomcom', + 'nomcom': nomcom} + + return delete_object(request, + model=NomCom, + object_id=nomcom.id, + post_delete_redirect=post_delete_redirect, + template_name='nomcom/delete_nomcom.html', + extra_context=extra_context) @member_required(role='chair') diff --git a/ietf/templates/nomcom/delete_nomcom.html b/ietf/templates/nomcom/delete_nomcom.html new file mode 100644 index 000000000..15f85f312 --- /dev/null +++ b/ietf/templates/nomcom/delete_nomcom.html @@ -0,0 +1,15 @@ +{% extends "nomcom/nomcom_private_base.html" %} + +{% block subtitle %}- Delete Nomcom{% endblock %} + +{% block nomcom_content %} + +

Are you sure you want to delete all data about {{ nomcom.group.name }}?

+
{% csrf_token %} +
+ + +
+
+ +{% endblock %} diff --git a/ietf/templates/nomcom/deleted.html b/ietf/templates/nomcom/deleted.html new file mode 100644 index 000000000..5728b6c09 --- /dev/null +++ b/ietf/templates/nomcom/deleted.html @@ -0,0 +1,7 @@ +{% extends "nomcom/nomcom_base.html" %} + +{% block content %} + +
All data about the nomcom has been removed
+ +{% endblock %} diff --git a/ietf/templates/nomcom/edit_publickey.html b/ietf/templates/nomcom/edit_nomcom.html similarity index 66% rename from ietf/templates/nomcom/edit_publickey.html rename to ietf/templates/nomcom/edit_nomcom.html index 633890d10..bc918d28c 100644 --- a/ietf/templates/nomcom/edit_publickey.html +++ b/ietf/templates/nomcom/edit_nomcom.html @@ -1,9 +1,9 @@ {% extends "nomcom/nomcom_private_base.html" %} -{% block subtitle %}- Edit public key{% endblock %} +{% block subtitle %}- Edit Nomcom{% endblock %} {% block nomcom_content %} -

Edit public key

+

Edit Nomcom

{% if message %}
{{ message.1 }}
@@ -18,4 +18,8 @@

+

Delete Nomcom

+ +

To delete all data about {{ nomcom.group.name }}, click here

+ {% endblock %} diff --git a/ietf/templates/nomcom/nomcom_private_base.html b/ietf/templates/nomcom/nomcom_private_base.html index 906f50414..bf1a00224 100644 --- a/ietf/templates/nomcom/nomcom_private_base.html +++ b/ietf/templates/nomcom/nomcom_private_base.html @@ -16,7 +16,7 @@ {% if selected == "merge" %}Merge nominee email addr{% else %}Merge nominee email addr{% endif %} | {% if selected == "send_reminder_mail" %}Send Reminder Mail{% else %}Send reminder mail{% endif %} | {% if selected == "edit_members" %}Nomcom members{% else %}Nomcom members{% endif %} | - {% if selected == "edit_publickey" %}Public key{% else %}Public key{% endif %} | + {% if selected == "edit_nomcom" %}Edit Nomcom{% else %}Edit Nomcom{% endif %} | {% if selected == "edit_templates" %}Templates{% else %}Templates{% endif %} | {% if selected == "edit_positions" %}Positions{% else %}Positions{% endif %} {% endif %} diff --git a/ietf/templates/nomcom/process_nomination_status.html b/ietf/templates/nomcom/process_nomination_status.html new file mode 100644 index 000000000..80f2faeb9 --- /dev/null +++ b/ietf/templates/nomcom/process_nomination_status.html @@ -0,0 +1,22 @@ +{% extends "nomcom/nomcom_public_base.html" %} + +{% block subtitle %} - Change Nomination {% endblock %} + +{% block nomcom_content %} + +{% if message %} +
{{ message.1 }}
+{% endif %} + +{% if need_confirmation %} +
{% csrf_token %} + {{ form }} + +
+ +
+ +
+{% endif %} + +{% endblock %}