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
This commit is contained in:
Emilio A. Sánchez López 2013-05-15 16:33:01 +00:00
parent 0bc7e181cd
commit ee1eec7de8
13 changed files with 482 additions and 171 deletions

View file

@ -553,6 +553,12 @@
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="offtopic" model="name.feedbacktype">
<field type="CharField" name="name">Offtopic</field>
<field type="TextField" name="desc"></field>
<field type="BooleanField" name="used">True</field>
<field type="IntegerField" name="order">0</field>
</object>
<object pk="rst" model="name.dbtemplatetypename">
<field type="CharField" name="name">reStructuredText</field>
<field type="TextField" name="desc"></field>
@ -1846,4 +1852,4 @@
<field type="IntegerField" name="order">3</field>
<field to="name.ballotpositionname" name="positions" rel="ManyToManyRel"><object pk="yes"></object><object pk="noobj"></object><object pk="block"></object><object pk="abstain"></object><object pk="norecord"></object></field>
</object>
</django-objects>
</django-objects>

View file

@ -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

View file

@ -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

View file

@ -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 <nomcom-year> --email-file <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)

View file

@ -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

View file

@ -10,6 +10,7 @@ urlpatterns = patterns('ietf.nomcom.views',
url(r'^(?P<year>\d{4})/private/feedback/$', 'private_feedback', name='nomcom_private_feedback'),
url(r'^(?P<year>\d{4})/private/questionnaire-response/$', 'private_questionnaire', name='nomcom_private_questionnaire'),
url(r'^(?P<year>\d{4})/private/view-feedback/$', 'view_feedback', name='nomcom_view_feedback'),
url(r'^(?P<year>\d{4})/private/view-feedback/unrelated/$', 'view_feedback_unrelated', name='nomcom_view_feedback_unrelated'),
url(r'^(?P<year>\d{4})/private/view-feedback/pending/$', 'view_feedback_pending', name='nomcom_view_feedback_pending'),
url(r'^(?P<year>\d{4})/private/view-feedback/nominee/(?P<nominee_id>\d+)$', 'view_feedback_nominee', name='nomcom_view_feedback_nominee'),
url(r'^(?P<year>\d{4})/private/merge/$', 'private_merge', name='nomcom_private_merge'),

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -10,7 +10,7 @@
<p><a href="{% url nomcom_view_feedback_pending year %}">Feedback pending</a></p>
{% endif %}
<h2>List of Nominees</h2>
<h2>Feedback related to Nominees</h2>
<table class="ietf-table ietf-doctable">
<tr>
@ -29,4 +29,21 @@
{% endfor %}
</table>
{% endblock %}
{% if independent_feedback_types %}
<h2>Feedback not related to Nominees</h2>
<table class="ietf-table ietf-doctable">
<tr>
<th></th>
{% for ft in independent_feedback_types %}
<th>{{ ft.name }}</th>
{% endfor %}
</tr>
<tr class="evenrow">
<td><a href="{% url nomcom_view_feedback_unrelated year %}">View feedback not related to nominees</td>
{% for count in independent_feedback %}
<td>{{ count }}</td>
{% endfor %}
</tr>
</table>
{% endif %}
{% endblock %}

View file

@ -15,26 +15,40 @@
<div class="info-message-{{ message.0 }}">{{ message.1 }}</div>
{% endif %}
{% if formset.forms %}
{% if extra_step %}
<p>Please, provide the following information about nominees to complete the classification of this feedback.</p>
{% endif %}
<div>
<form id="feedbackformset" action="" method="post">{% csrf_token %}
<div class="sumit-row">
{% if extra_step %}
<input type="submit" value="Save feedback" name="end" />
<a href="{% url nomcom_view_feedback_pending year %}">Cancel and leave the following feedback unclassified</a>
{% else %}
<input type="submit" value="Classify" />
{% if default_type %}<input type="submit" name="move_to_default" value="Move all unclassified feedback to {{ default_type }}" />{% endif %}
{% endif %}
</div>
{{ formset.management_form }}
{% for form in formset.forms %}
<h3 class="ietf-divider" style="margin-bottom: 0px;">{{ form.instance.time|date:"Y-m-d" }} id:{{ form.instance.id }}</h3>
{% if form.errors %}<div class="info-message-error">Please correct the following errors</div>{% endif %}
<table style="width: 100%;"><tr><td>
<div class="baseform">
<div class="fieldset">
<h2>Comment meta data</h2>
<h2>{{ form.instance.time|date:"Y-m-d" }} id:{{ form.instance.id }}</h2>
{% for field in form %}
<div id="baseform-fieldname-{{ field.html_name }}"
<div id="baseform-fieldname-{{ field.html_name }}"
{% if field.field.widget.is_hidden %}style="display: none;"{% endif %}
class="{% if field.errors %}fieldError {% endif %}field BaseFormStringWidget{% if field.field.column_style %} {{ field.field.column_style }}{% endif %}">
{% if extra_step %}
<label for="id_{{ field.html_name }}">{{ field.label }}
{% if field.field.required %}
<span class="fieldRequired" title="Required">*</span>
{% endif %}
</label>
<div class="fieldWidget">
{% endif %}
<div class="fieldWidget{% if extra_step %} defaultPos{% endif %}">
<div id="{{ field.html_name }}_help" class="formHelp"> {{ field.help_text }}</div>
{{ field }}
{{ field.errors }}
@ -42,20 +56,27 @@
<div class="endfield"></div>
</div>
{% endfor %}
<h3>Type</h3>
<pre>{{ form.feedback_type }}</pre>
<h3>Author</h3>
<pre>{{ form.instance.author }}</pre>
<h3>Feedback body</h3>
<pre>{% decrypt form.instance.comments request year 1 %}</pre>
</div>
</div>
</td><td style="width: 50%; vertical-align: top;">
<div class="baseform">
<div class="fieldset">
<h2>Comment body</h2>
<pre style="padding: 1em;">{% decrypt form.instance.comments request year %}</pre>
</div>
</div>
</td></tr></table>
{% endfor %}
<div class="ietf-divider"></div>
<input type="submit" value="Submit" />
<div class="sumit-row">
{% if extra_step %}
<input type="submit" value="Save feedback" name="end" />
<a href="{% url nomcom_view_feedback_pending year %}">Cancel and leave the following feedback unclassified</a>
{% else %}
<input type="submit" value="Classify" />
{% if default_type %}<input type="submit" name="move_to_default" value="Move all unclassified feedback to {{ default_type }}" />{% endif %}
{% endif %}
</div>
</form>
</div>
{% else %}
<p>There is no pending feedback.</p>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,72 @@
{% extends "nomcom/nomcom_private_base.html" %}
{% load nomcom_tags %}
{% block pagehead %}
{{ block.super }}
<script type="text/javascript" src="/js/lib/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="/js/yui/yui-20100305.js"></script>
<script type="text/javascript" src="/js/base.js"></script>
{% endblock pagehead %}
{% block subtitle %} - View unrelated feedback{% endblock %}
{% block nomcom_content %}
<p>Back to list of <a href="{% url nomcom_view_feedback year %}">feedback</a></p>
<h2>Feedback not related to nominees</h2>
<div id="mytabs" class="yui-navset">
<ul class="yui-nav">
{% for ft in feedback_types %}
<li><a href="#{{ ft.ft.slug }}"><em>{{ ft.ft.name }}</em></a></li>
{% endfor %}
</ul>
Pick the feedback type to view from the list immediately above
<div class="yui-content">
{% for ft in feedback_types %}
<div id="#{{ ft.ft.slug }}">
{% for feedback in ft.feedback %}
<div>
<h3 class="ietf-divider">From {{ feedback.author|formatted_email|default:"Anonymous" }} ({{ feedback.time|date:"Y-m-d" }})</h3>
{% ifequal ft.slug "nomina" %}
{% for fn in feedback.nomination_set.all %}
{% if fn.candidate_name %}
<p><b>Candidate name:</b> {{ fn.candidate_name }}</p>
{% endif %}
{% if fn.candidate_phone %}
<p><b>Candidate phone:</b> {{ fn.candidate_phone }}</p>
{% endif %}
{% endfor %}
{% endifequal %}
<b>Positions:</b> {{ feedback.positions.all|join:"," }}
<p>
{% decrypt feedback.comments request year %}
</p>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
<script type="text/javascript">
//<![CDATA[
var tabView = new YAHOO.widget.TabView('mytabs');
var url = location.href.split('#');
if (url[1]) {
url[1] = "#"+url[1];
var tabs = tabView.get('tabs');
for (var i = 0; i < tabs.length; i++) {
if (url[1].indexOf(tabs[i].get('href')) == 0) {
tabView.set('activeIndex', i);
break;
}
}
}
//]]>
</script>
{% endblock %}

View file

@ -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 {