From ee1eec7de8359ff3d2af978354c777e6ea29e426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?= Date: Wed, 15 May 2013 16:33:01 +0000 Subject: [PATCH] Refactored the feedback pending list. Now you can classify feedback as any type of feedback. The feedback of a type that is not related to a nominee is classified inmediatly. The feedback of a type that is related to a nominee must be completed with the nominee information. Created a view to list feedback that is not related to nominees (e.g. offtopic) Changed the command that retrieves feedback from email to add the subject inside the comment and to set the author using the from field of the email. Fixes #1036. Fixes #1035 - Legacy-Id: 5738 --- ietf/name/fixtures/names.xml | 8 +- ietf/nomcom/decorators.py | 2 +- ietf/nomcom/forms.py | 240 +++++++++--------- .../management/commands/feedback_email.py | 11 +- ietf/nomcom/templatetags/nomcom_tags.py | 6 +- ietf/nomcom/urls.py | 1 + ietf/nomcom/utils.py | 112 +++++++- ietf/nomcom/views.py | 92 ++++++- ietf/settings.py | 4 + ietf/templates/nomcom/view_feedback.html | 21 +- .../nomcom/view_feedback_pending.html | 53 ++-- .../nomcom/view_feedback_unrelated.html | 72 ++++++ static/css/base2.css | 31 +++ 13 files changed, 482 insertions(+), 171 deletions(-) create mode 100644 ietf/templates/nomcom/view_feedback_unrelated.html diff --git a/ietf/name/fixtures/names.xml b/ietf/name/fixtures/names.xml index 9ce3fbd3a..aad6d4ca6 100644 --- a/ietf/name/fixtures/names.xml +++ b/ietf/name/fixtures/names.xml @@ -553,6 +553,12 @@ True 0 + + Offtopic + + True + 0 + reStructuredText @@ -1846,4 +1852,4 @@ 3 - \ No newline at end of file + diff --git a/ietf/nomcom/decorators.py b/ietf/nomcom/decorators.py index c0bb60763..47b016882 100644 --- a/ietf/nomcom/decorators.py +++ b/ietf/nomcom/decorators.py @@ -2,7 +2,7 @@ from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect from django.utils.http import urlquote -from ietf.ietfauth.decorators import passes_test_decorator, has_role +from ietf.ietfauth.decorators import passes_test_decorator from ietf.nomcom.utils import get_nomcom_by_year diff --git a/ietf/nomcom/forms.py b/ietf/nomcom/forms.py index bf66cb7c0..1351d8ec9 100644 --- a/ietf/nomcom/forms.py +++ b/ietf/nomcom/forms.py @@ -1,5 +1,3 @@ -import datetime - from django.conf import settings from django import forms from django.contrib.formtools.preview import FormPreview, AUTO_ID @@ -10,24 +8,19 @@ 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 -from ietf.utils.mail import send_mail, send_mail_text +from ietf.utils.mail import send_mail from ietf.ietfauth.decorators import role_required from ietf.utils import fields as custom_fields from ietf.group.models import Group, Role from ietf.name.models import RoleName, FeedbackType, NomineePositionState -from ietf.person.models import Email, Person +from ietf.person.models import Email from ietf.nomcom.models import NomCom, Nomination, Nominee, NomineePosition, \ Position, Feedback, ReminderDates -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_hash_nominee_position, get_year_by_nomcom, \ - HEADER_QUESTIONNAIRE_TEMPLATE, validate_private_key, \ - validate_public_key +from ietf.nomcom.utils import (NOMINATION_RECEIPT_TEMPLATE, FEEDBACK_RECEIPT_TEMPLATE, + get_user_email, validate_private_key, validate_public_key, + get_or_create_nominee) from ietf.nomcom.decorators import nomcom_member_required @@ -441,22 +434,15 @@ class NominateForm(BaseNomcomForm, forms.ModelForm): comments = self.cleaned_data['comments'] confirmation = self.cleaned_data['confirmation'] nomcom_template_path = '/nomcom/%s/' % self.nomcom.group.acronym - nomcom_chair = self.nomcom.group.get_chair() - nomcom_chair_mail = nomcom_chair and nomcom_chair.email.address or None - # Create person and email if candidate email does't exist and send email - email, created_email = Email.objects.get_or_create(address=candidate_email) - if created_email: - email.person = Person.objects.create(name=candidate_name, - ascii=unaccent.asciify(candidate_name), - address=candidate_email) - email.save() - - # Add the nomination for a particular position - nominee, created = Nominee.objects.get_or_create(email=email, nomcom=self.nomcom) - while nominee.duplicated: - nominee = nominee.duplicated - nominee_position, nominee_position_created = NomineePosition.objects.get_or_create(position=position, nominee=nominee) + author = None + if self.public: + author = get_user_email(self.user) + else: + if nominator_email: + emails = Email.objects.filter(address=nominator_email) + author = emails and emails[0] or None + nominee = get_or_create_nominee(self.nomcom, candidate_name, candidate_email, position, author) # Complete nomination data feedback = Feedback.objects.create(nomcom=self.nomcom, @@ -465,13 +451,6 @@ class NominateForm(BaseNomcomForm, forms.ModelForm): user=self.user) feedback.positions.add(position) feedback.nominees.add(nominee) - author = None - if self.public: - author = get_user_email(self.user) - else: - if nominator_email: - emails = Email.objects.filter(address=nominator_email) - author = emails and emails[0] or None if author: nomination.nominator_email = author.address @@ -485,87 +464,13 @@ class NominateForm(BaseNomcomForm, forms.ModelForm): if commit: nomination.save() - if created_email: - # send email to secretariat and nomcomchair to warn about the new person - subject = 'New person is created' - from_email = settings.NOMCOM_FROM_EMAIL - to_email = [settings.NOMCOM_ADMIN_EMAIL] - context = {'email': email.address, - 'fullname': email.person.name, - 'person_id': email.person.id} - path = nomcom_template_path + INEXISTENT_PERSON_TEMPLATE - if nomcom_chair_mail: - to_email.append(nomcom_chair_mail) - send_mail(None, to_email, from_email, subject, path, context) - - # send email to nominee - if nominee_position_created: - 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, - '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) - - # send email to nominee with questionnaire - if nominee_position_created: - if self.nomcom.send_questionnaire: - subject = '%s Questionnaire' % position - from_email = settings.NOMCOM_FROM_EMAIL - to_email = email.address - context = {'nominee': email.person.name, - 'position': position.name} - path = '%s%d/%s' % (nomcom_template_path, - position.id, HEADER_QUESTIONNAIRE_TEMPLATE) - body = render_to_string(path, context) - path = '%s%d/%s' % (nomcom_template_path, - position.id, QUESTIONNAIRE_TEMPLATE) - body += '\n\n%s' % render_to_string(path, context) - send_mail_text(None, to_email, from_email, subject, body) - - # send emails to nomcom chair - subject = 'Nomination Information' - from_email = settings.NOMCOM_FROM_EMAIL - to_email = nomcom_chair_mail - context = {'nominee': email.person.name, - 'nominee_email': email.address, - 'position': position.name} - if author: - context.update({'nominator': author.person.name, - 'nominator_email': author.address}) - path = nomcom_template_path + NOMINATION_EMAIL_TEMPLATE - send_mail(None, to_email, from_email, subject, path, context) - # send receipt email to nominator if confirmation: if author: subject = 'Nomination Receipt' from_email = settings.NOMCOM_FROM_EMAIL to_email = author.address - context = {'nominee': email.person.name, + context = {'nominee': nominee.email.person.name, 'comments': comments, 'position': position.name} path = nomcom_template_path + NOMINATION_RECEIPT_TEMPLATE @@ -795,37 +700,43 @@ class PrivateKeyForm(BaseNomcomForm, forms.Form): class PendingFeedbackForm(BaseNomcomForm, forms.ModelForm): + type = forms.ModelChoiceField(queryset=FeedbackType.objects.all(), widget=forms.RadioSelect, empty_label='Unclassified', required=False) + class Meta: model = Feedback - fields = ('author', 'type', 'nominee') + fields = ('type', ) def __init__(self, *args, **kwargs): super(PendingFeedbackForm, self).__init__(*args, **kwargs) - self.fields['type'].queryset = FeedbackType.objects.exclude(slug='nomina') + try: + self.default_type = FeedbackType.objects.get(slug=settings.DEFAULT_FEEDBACK_TYPE) + except FeedbackType.DoesNotExist: + self.default_type = None def set_nomcom(self, nomcom, user): self.nomcom = nomcom self.user = user - self.fields['nominee'] = MultiplePositionNomineeField(nomcom=self.nomcom, - required=True, - widget=forms.SelectMultiple, - help_text='Hold down "Control", or "Command" on a Mac, to select more than one.') + #self.fields['nominee'] = MultiplePositionNomineeField(nomcom=self.nomcom, + #required=True, + #widget=forms.SelectMultiple, + #help_text='Hold down "Control", or "Command" on a Mac, to select more than one.') def save(self, commit=True): feedback = super(PendingFeedbackForm, self).save(commit=False) - - author = get_user_email(self.user) - - if author: - feedback.author = author - feedback.nomcom = self.nomcom feedback.user = self.user feedback.save() - self.save_m2m() - for (position, nominee) in self.cleaned_data['nominee']: - feedback.nominees.add(nominee) - feedback.positions.add(position) + return feedback + + def move_to_default(self): + if not self.default_type or self.cleaned_data.get('type', None): + return None + feedback = super(PendingFeedbackForm, self).save(commit=False) + feedback.nomcom = self.nomcom + feedback.user = self.user + feedback.type = self.default_type + feedback.save() + return feedback class ReminderDatesForm(forms.ModelForm): @@ -837,3 +748,82 @@ class ReminderDatesForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(ReminderDatesForm, self).__init__(*args, **kwargs) self.fields['date'].required = False + + +class MutableFeedbackForm(forms.ModelForm): + + type = forms.ModelChoiceField(queryset=FeedbackType.objects.all(), widget=forms.HiddenInput) + + class Meta: + model = Feedback + fields = ('type', ) + + def set_nomcom(self, nomcom, user, instances=None): + self.nomcom = nomcom + self.user = user + instances = instances or [] + self.feedback_type = None + for i in instances: + if i.id == self.instance.id: + self.feedback_type = i.type + break + self.feedback_type = self.feedback_type or self.fields['type'].clean(self.fields['type'].widget.value_from_datadict(self.data, self.files, self.add_prefix('type'))) + + self.initial['type'] = self.feedback_type + + if self.feedback_type.slug != 'nomina': + self.fields['nominee'] = MultiplePositionNomineeField(nomcom=self.nomcom, + required=True, + widget=forms.SelectMultiple, + help_text='Hold down "Control", or "Command" on a Mac, to select more than one.') + else: + self.fields['position'] = forms.ModelChoiceField(queryset=Position.objects.get_by_nomcom(self.nomcom).opened(), label="Position") + self.fields['candidate_name'] = forms.CharField(label="Candidate name") + self.fields['candidate_email'] = forms.EmailField(label="Candidate email") + self.fields['candidate_phone'] = forms.CharField(label="Candidate phone", required=False) + + def save(self, commit=True): + feedback = super(MutableFeedbackForm, self).save(commit=False) + if self.instance.type.slug == 'nomina': + candidate_email = self.cleaned_data['candidate_email'] + candidate_name = self.cleaned_data['candidate_name'] + candidate_phone = self.cleaned_data['candidate_phone'] + position = self.cleaned_data['position'] + + nominator_email = feedback.author + feedback.save() + + emails = Email.objects.filter(address=nominator_email) + author = emails and emails[0] or None + + nominee = get_or_create_nominee(self.nomcom, candidate_name, candidate_email, position, author) + feedback.nominees.add(nominee) + feedback.positions.add(position) + Nomination.objects.create( + position=self.cleaned_data.get('position'), + candidate_name=candidate_name, + candidate_email=candidate_email, + candidate_phone=candidate_phone, + nominee=nominee, + comments=feedback, + nominator_email=nominator_email, + user=self.user, + ) + return feedback + else: + feedback.save() + self.save_m2m() + for (position, nominee) in self.cleaned_data['nominee']: + feedback.nominees.add(nominee) + feedback.positions.add(position) + return feedback + + +class FullFeedbackFormSet(forms.models.BaseModelFormSet): + + model = Feedback + extra = 0 + max_num = 0 + form = MutableFeedbackForm + can_order = False + can_delete = False diff --git a/ietf/nomcom/management/commands/feedback_email.py b/ietf/nomcom/management/commands/feedback_email.py index af9d6ee56..81a114e9a 100644 --- a/ietf/nomcom/management/commands/feedback_email.py +++ b/ietf/nomcom/management/commands/feedback_email.py @@ -5,7 +5,7 @@ import syslog from django.core.management.base import BaseCommand, CommandError from ietf.nomcom.utils import parse_email -from ietf.nomcom.models import Nominee, NomCom, Feedback +from ietf.nomcom.models import NomCom, Feedback class Command(BaseCommand): @@ -19,7 +19,6 @@ class Command(BaseCommand): email = options.get('email', None) year = options.get('year', None) msg = None - nominee = None nomcom = None help_message = 'Usage: feeback_email --nomcom-year --email-file ' @@ -38,16 +37,12 @@ class Command(BaseCommand): raise CommandError("NomCom %s does not exist or it isn't active" % year) by, subject, body = parse_email(msg) + body = 'Subject: %s\n\n%s' % (subject, body) name, addr = parseaddr(by) - try: - nominee = Nominee.objects.get_by_nomcom(nomcom).not_duplicated().get(email__address__icontains=addr) - except Nominee.DoesNotExist: - pass feedback = Feedback(nomcom=nomcom, + author=addr, comments=body) feedback.save() - if nominee: - feedback.nominees.add(nominee) syslog.syslog(u"Read feedback email by %s" % by) diff --git a/ietf/nomcom/templatetags/nomcom_tags.py b/ietf/nomcom/templatetags/nomcom_tags.py index 9821e0487..3e717b870 100644 --- a/ietf/nomcom/templatetags/nomcom_tags.py +++ b/ietf/nomcom/templatetags/nomcom_tags.py @@ -60,7 +60,7 @@ def formatted_email(address): @register.simple_tag -def decrypt(string, request, year): +def decrypt(string, request, year, plain=False): key = retrieve_nomcom_private_key(request, year) if not key: @@ -79,4 +79,6 @@ def decrypt(string, request, year): if error: return '<-Encripted text [Your private key is invalid]->' - return linebreaksbr(out) + if not plain: + return linebreaksbr(out) + return out diff --git a/ietf/nomcom/urls.py b/ietf/nomcom/urls.py index 12982285b..0676e8c04 100644 --- a/ietf/nomcom/urls.py +++ b/ietf/nomcom/urls.py @@ -10,6 +10,7 @@ urlpatterns = patterns('ietf.nomcom.views', url(r'^(?P\d{4})/private/feedback/$', 'private_feedback', name='nomcom_private_feedback'), url(r'^(?P\d{4})/private/questionnaire-response/$', 'private_questionnaire', name='nomcom_private_questionnaire'), url(r'^(?P\d{4})/private/view-feedback/$', 'view_feedback', name='nomcom_view_feedback'), + url(r'^(?P\d{4})/private/view-feedback/unrelated/$', 'view_feedback_unrelated', name='nomcom_view_feedback_unrelated'), url(r'^(?P\d{4})/private/view-feedback/pending/$', 'view_feedback_pending', name='nomcom_view_feedback_pending'), url(r'^(?P\d{4})/private/view-feedback/nominee/(?P\d+)$', 'view_feedback_nominee', name='nomcom_view_feedback_nominee'), url(r'^(?P\d{4})/private/merge/$', 'private_merge', name='nomcom_private_merge'), diff --git a/ietf/nomcom/utils.py b/ietf/nomcom/utils.py index 896f7635c..2b9eaeddf 100644 --- a/ietf/nomcom/utils.py +++ b/ietf/nomcom/utils.py @@ -13,9 +13,11 @@ from django.template.loader import render_to_string from django.shortcuts import get_object_or_404 from ietf.dbtemplate.models import DBTemplate -from ietf.person.models import Email +from ietf.person.models import Email, Person from ietf.utils.pipe import pipe -from ietf.utils.mail import send_mail_text +from ietf.utils import unaccent +from ietf.utils.mail import send_mail_text, send_mail + MAIN_NOMCOM_TEMPLATE_PATH = '/nomcom/defaults/' QUESTIONNAIRE_TEMPLATE = 'position/questionnaire.txt' @@ -157,10 +159,14 @@ def extract_body(payload): def parse_email(text): - msg = email.message_from_string(text.encode("utf-8")) + msg = email.message_from_string(text) # comment - body = extract_body(msg.get_payload()) + #body = quopri.decodestring(extract_body(msg.get_payload())) + charset = msg.get_content_charset() + body = extract_body(msg.get_payload(decode=True)) + if charset: + body = body.decode(charset) return msg['From'], msg['Subject'], body @@ -235,3 +241,101 @@ def send_reminder_to_nominees(nominees): for nominee in nominees: for nominee_position in nominee.nomineeposition_set.pending(): send_reminder_to_nominee(nominee_position) + + +def get_or_create_nominee(nomcom, candidate_name, candidate_email, position, author): + from ietf.nomcom.models import Nominee, NomineePosition + + nomcom_template_path = '/nomcom/%s/' % nomcom.group.acronym + nomcom_chair = nomcom.group.get_chair() + nomcom_chair_mail = nomcom_chair and nomcom_chair.email.address or None + + # Create person and email if candidate email does't exist and send email + email, created_email = Email.objects.get_or_create(address=candidate_email) + if created_email: + email.person = Person.objects.create(name=candidate_name, + ascii=unaccent.asciify(candidate_name), + address=candidate_email) + email.save() + + # Add the nomination for a particular position + nominee, created = Nominee.objects.get_or_create(email=email, nomcom=nomcom) + while nominee.duplicated: + nominee = nominee.duplicated + nominee_position, nominee_position_created = NomineePosition.objects.get_or_create(position=position, nominee=nominee) + + if created_email: + # send email to secretariat and nomcomchair to warn about the new person + subject = 'New person is created' + from_email = settings.NOMCOM_FROM_EMAIL + to_email = [settings.NOMCOM_ADMIN_EMAIL] + context = {'email': email.address, + 'fullname': email.person.name, + 'person_id': email.person.id} + path = nomcom_template_path + INEXISTENT_PERSON_TEMPLATE + if nomcom_chair_mail: + to_email.append(nomcom_chair_mail) + send_mail(None, to_email, from_email, subject, path, context) + + if nominee_position_created: + # send email to nominee + 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(nomcom), + nominee_position.id, + 'accepted', + today, + hash)) + decline_url = reverse('nomcom_process_nomination_status', + None, + args=(get_year_by_nomcom(nomcom), + nominee_position.id, + 'declined', + today, + hash)) + + context = {'nominee': email.person.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) + + # send email to nominee with questionnaire + if nomcom.send_questionnaire: + subject = '%s Questionnaire' % position + from_email = settings.NOMCOM_FROM_EMAIL + to_email = email.address + context = {'nominee': email.person.name, + 'position': position.name} + path = '%s%d/%s' % (nomcom_template_path, + position.id, HEADER_QUESTIONNAIRE_TEMPLATE) + body = render_to_string(path, context) + path = '%s%d/%s' % (nomcom_template_path, + position.id, QUESTIONNAIRE_TEMPLATE) + body += '\n\n%s' % render_to_string(path, context) + send_mail_text(None, to_email, from_email, subject, body) + + # send emails to nomcom chair + subject = 'Nomination Information' + from_email = settings.NOMCOM_FROM_EMAIL + to_email = nomcom_chair_mail + context = {'nominee': email.person.name, + 'nominee_email': email.address, + 'position': position.name} + + if author: + context.update({'nominator': author.person.name, + 'nominator_email': author.address}) + path = nomcom_template_path + NOMINATION_EMAIL_TEMPLATE + send_mail(None, to_email, from_email, subject, path, context) + + return nominee diff --git a/ietf/nomcom/views.py b/ietf/nomcom/views.py index 56f743a68..f42c18fc9 100644 --- a/ietf/nomcom/views.py +++ b/ietf/nomcom/views.py @@ -5,13 +5,14 @@ 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.contrib.messages.api import success, get_messages from django.core.urlresolvers import reverse 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 from django.utils import simplejson -from django.db.models import Count, Q +from django.db.models import Count from django.forms.models import modelformset_factory, inlineformset_factory @@ -23,7 +24,7 @@ from ietf.nomcom.decorators import nomcom_member_required, nomcom_private_key_re from ietf.nomcom.forms import (NominateForm, FeedbackForm, QuestionnaireForm, MergeForm, NomComTemplateForm, PositionForm, PrivateKeyForm, EditNomcomForm, PendingFeedbackForm, - ReminderDatesForm) + ReminderDatesForm, FullFeedbackFormSet) from ietf.nomcom.models import Position, NomineePosition, Nominee, Feedback, NomCom, ReminderDates from ietf.nomcom.utils import (get_nomcom_by_year, store_nomcom_private_key, get_hash_nominee_position, send_reminder_to_nominees, @@ -380,17 +381,26 @@ def process_nomination_status(request, year, nominee_position_id, state, date, h def view_feedback(request, year): nomcom = get_nomcom_by_year(year) nominees = Nominee.objects.get_by_nomcom(nomcom).not_duplicated().distinct() - feedback_types = FeedbackType.objects.all() + independent_feedback_types = [] + feedback_types = [] + for ft in FeedbackType.objects.all(): + if ft.slug in settings.NOMINEE_FEEDBACK_TYPES: + feedback_types.append(ft) + else: + independent_feedback_types.append(ft) nominees_feedback = {} for nominee in nominees: nominee_feedback = [(ft.name, nominee.feedback_set.by_type(ft.slug).count()) for ft in feedback_types] nominees_feedback.update({nominee: nominee_feedback}) + independent_feedback = [ft.feedback_set.get_by_nomcom(nomcom).count() for ft in independent_feedback_types] return render_to_response('nomcom/view_feedback.html', {'year': year, 'selected': 'view_feedback', 'nominees': nominees, 'feedback_types': feedback_types, + 'independent_feedback_types': independent_feedback_types, + 'independent_feedback': independent_feedback, 'nominees_feedback': nominees_feedback, 'nomcom': nomcom}, RequestContext(request)) @@ -400,23 +410,63 @@ def view_feedback(request, year): def view_feedback_pending(request, year): nomcom = get_nomcom_by_year(year) message = None + for message in get_messages(request): + message = ('success', message.message) FeedbackFormSet = modelformset_factory(Feedback, form=PendingFeedbackForm, - exclude=('nomcom', 'comments'), extra=0) - feedbacks = Feedback.objects.filter(Q(type__isnull=True) | - Q(nominees__isnull=True) | - Q(positions__isnull=True)) - if request.method == 'POST': + feedbacks = Feedback.objects.filter(type__isnull=True, nomcom=nomcom) + + try: + default_type = FeedbackType.objects.get(slug=settings.DEFAULT_FEEDBACK_TYPE) + except FeedbackType.DoesNotExist: + default_type = None + + extra_step = False + if request.method == 'POST' and request.POST.get('move_to_default'): formset = FeedbackFormSet(request.POST) + if formset.is_valid(): + for form in formset.forms: + form.set_nomcom(nomcom, request.user) + form.move_to_default() + formset = FeedbackFormSet(queryset=feedbacks) + for form in formset.forms: + form.set_nomcom(nomcom, request.user) + success(request, 'Feedback saved') + return HttpResponseRedirect(reverse('nomcom_view_feedback_pending', None, args=(year, ))) + elif request.method == 'POST' and request.POST.get('end'): + extra_step = True + formset = FullFeedbackFormSet(request.POST) for form in formset.forms: form.set_nomcom(nomcom, request.user) if formset.is_valid(): formset.save() - message = ('success', 'The feedbacks has been saved.') - formset = FeedbackFormSet(queryset=feedbacks) + success(request, 'Feedback saved') + return HttpResponseRedirect(reverse('nomcom_view_feedback_pending', None, args=(year, ))) + elif request.method == 'POST': + formset = FeedbackFormSet(request.POST) + for form in formset.forms: + form.set_nomcom(nomcom, request.user) + if formset.is_valid(): + extra = [] + moved = 0 for form in formset.forms: - form.set_nomcom(nomcom, request.user) + if form.instance.type and form.instance.type.slug in settings.NOMINEE_FEEDBACK_TYPES: + extra.append(form.instance) + else: + if form.instance.type: + moved += 1 + form.save() + if extra: + extra_step = True + formset = FullFeedbackFormSet(queryset=Feedback.objects.filter(id__in=[i.id for i in extra])) + for form in formset.forms: + form.set_nomcom(nomcom, request.user, extra) + if moved: + message = ('success', '%s messages classified. You must enter more information for the following feedback.' % moved) + else: + success(request, 'Feedback saved') + return HttpResponseRedirect(reverse('nomcom_view_feedback_pending', None, args=(year, ))) else: formset = FeedbackFormSet(queryset=feedbacks) for form in formset.forms: @@ -426,6 +476,24 @@ def view_feedback_pending(request, year): 'selected': 'view_feedback', 'formset': formset, 'message': message, + 'extra_step': extra_step, + 'default_type': default_type, + 'nomcom': nomcom}, RequestContext(request)) + + +@nomcom_member_required(role='member') +@nomcom_private_key_required +def view_feedback_unrelated(request, year): + nomcom = get_nomcom_by_year(year) + feedback_types = [] + for ft in FeedbackType.objects.exclude(slug__in=settings.NOMINEE_FEEDBACK_TYPES): + feedback_types.append({'ft': ft, + 'feedback': ft.feedback_set.get_by_nomcom(nomcom)}) + + return render_to_response('nomcom/view_feedback_unrelated.html', + {'year': year, + 'selected': 'view_feedback', + 'feedback_types': feedback_types, 'nomcom': nomcom}, RequestContext(request)) @@ -434,7 +502,7 @@ def view_feedback_pending(request, year): def view_feedback_nominee(request, year, nominee_id): nomcom = get_nomcom_by_year(year) nominee = get_object_or_404(Nominee, id=nominee_id) - feedback_types = FeedbackType.objects.all() + feedback_types = FeedbackType.objects.filter(slug__in=settings.NOMINEE_FEEDBACK_TYPES) return render_to_response('nomcom/view_feedback_nominee.html', {'year': year, diff --git a/ietf/settings.py b/ietf/settings.py index 0758b2b33..a9c6f5638 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -102,6 +102,7 @@ MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.RemoteUserMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.doc.XViewMiddleware', 'ietf.middleware.SQLLogMiddleware', 'ietf.middleware.SMTPExceptionMiddleware', @@ -135,6 +136,7 @@ INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.admindocs', 'django.contrib.humanize', + 'django.contrib.messages', 'south', 'workflows', 'permissions', @@ -266,6 +268,8 @@ NOMCOM_FROM_EMAIL = DEFAULT_FROM_EMAIL NOMCOM_ADMIN_EMAIL = DEFAULT_FROM_EMAIL OPENSSL_COMMAND = '/usr/bin/openssl' DAYS_TO_EXPIRE_NOMINATION_LINK = '' +DEFAULT_FEEDBACK_TYPE = 'offtopic' +NOMINEE_FEEDBACK_TYPES = ['comment', 'questio', 'nomina'] # Days from meeting to cut off dates on submit FIRST_CUTOFF_DAYS = 19 diff --git a/ietf/templates/nomcom/view_feedback.html b/ietf/templates/nomcom/view_feedback.html index df02b51c5..2cb15dfb4 100644 --- a/ietf/templates/nomcom/view_feedback.html +++ b/ietf/templates/nomcom/view_feedback.html @@ -10,7 +10,7 @@

Feedback pending

{% endif %} -

List of Nominees

+

Feedback related to Nominees

@@ -29,4 +29,21 @@ {% endfor %}
-{% endblock %} \ No newline at end of file +{% if independent_feedback_types %} +

Feedback not related to Nominees

+ + + + {% for ft in independent_feedback_types %} + + {% endfor %} + + + + {% for count in independent_feedback %} + + {% endfor %} + +
{{ ft.name }}
View feedback not related to nominees{{ count }}
+{% endif %} +{% endblock %} diff --git a/ietf/templates/nomcom/view_feedback_pending.html b/ietf/templates/nomcom/view_feedback_pending.html index 368f981ca..812beb5f7 100644 --- a/ietf/templates/nomcom/view_feedback_pending.html +++ b/ietf/templates/nomcom/view_feedback_pending.html @@ -15,26 +15,40 @@
{{ message.1 }}
{% endif %} +{% if formset.forms %} +{% if extra_step %} +

Please, provide the following information about nominees to complete the classification of this feedback.

+{% endif %} +
{% csrf_token %} +
+ {% if extra_step %} + + Cancel and leave the following feedback unclassified + {% else %} + + {% if default_type %}{% endif %} + {% endif %} +
{{ formset.management_form }} {% for form in formset.forms %} -

{{ form.instance.time|date:"Y-m-d" }} id:{{ form.instance.id }}

{% if form.errors %}
Please correct the following errors
{% endif %} -
-

Comment meta data

+

{{ form.instance.time|date:"Y-m-d" }} id:{{ form.instance.id }}

{% for field in form %} -
+ {% if extra_step %} -
+ {% endif %} +
{{ field.help_text }}
{{ field }} {{ field.errors }} @@ -42,20 +56,27 @@
{% endfor %} +

Type

+
{{ form.feedback_type }}
+

Author

+
{{ form.instance.author }}
+

Feedback body

+
{% decrypt form.instance.comments request year 1 %}
-
-
-
-

Comment body

-
{% decrypt form.instance.comments request year %}
-
-
-
{% endfor %} -
- +
+ {% if extra_step %} + + Cancel and leave the following feedback unclassified + {% else %} + + {% if default_type %}{% endif %} + {% endif %} +
- +{% else %} +

There is no pending feedback.

+{% endif %} {% endblock %} diff --git a/ietf/templates/nomcom/view_feedback_unrelated.html b/ietf/templates/nomcom/view_feedback_unrelated.html new file mode 100644 index 000000000..c7c6dbf5d --- /dev/null +++ b/ietf/templates/nomcom/view_feedback_unrelated.html @@ -0,0 +1,72 @@ +{% extends "nomcom/nomcom_private_base.html" %} + +{% load nomcom_tags %} + +{% block pagehead %} + {{ block.super }} + + + +{% endblock pagehead %} + +{% block subtitle %} - View unrelated feedback{% endblock %} + +{% block nomcom_content %} + +

Back to list of feedback

+ +

Feedback not related to nominees

+ +
+ + Pick the feedback type to view from the list immediately above +
+ {% for ft in feedback_types %} +
+ {% for feedback in ft.feedback %} +
+

From {{ feedback.author|formatted_email|default:"Anonymous" }} ({{ feedback.time|date:"Y-m-d" }})

+ {% ifequal ft.slug "nomina" %} + {% for fn in feedback.nomination_set.all %} + {% if fn.candidate_name %} +

Candidate name: {{ fn.candidate_name }}

+ {% endif %} + {% if fn.candidate_phone %} +

Candidate phone: {{ fn.candidate_phone }}

+ {% endif %} + {% endfor %} + {% endifequal %} + Positions: {{ feedback.positions.all|join:"," }} +

+ {% decrypt feedback.comments request year %} +

+
+ {% endfor %} +
+ {% endfor %} +
+ + + + + +{% endblock %} diff --git a/static/css/base2.css b/static/css/base2.css index f108c5bbf..7b9dfeb63 100644 --- a/static/css/base2.css +++ b/static/css/base2.css @@ -249,6 +249,37 @@ div.info-message-error { border: 1px solid red; background-color: #ffeebb; paddi margin-left: 150px; } +#feedbackformset .fieldWidget { + margin-left: 0px; +} + +#feedbackformset .fieldWidget.defaultPos { + margin-left: 150px; +} + +#feedbackformset ul { + padding: 0px; +} + +#feedbackformset ul li { + display: inline; +} + +#feedbackformset ul li label { + display: inline; + float: none; +} + +#feedbackformset .baseform h3 { + margin-left: 12px; + margin-top: 24px; + margin-bottom: 0px; +} + +#feedbackformset pre { + margin: 12px; +} + .baseform select, .baseform textarea, .baseform input {