From 230f6cfae75e65dc91a887d9c25b33658eaeb4d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Jim=C3=A9nez?= Date: Tue, 20 Nov 2012 00:40:57 +0000 Subject: [PATCH] view only accessible by the secretariat that allows to assign the email of the members and the chair of the NomCom See #904 - Legacy-Id: 5066 --- .../migrations/0004_add_rolename_member.py | 155 ++++++++++++++++++ ietf/nomcom/__init__.py | 0 ietf/nomcom/forms.py | 97 +++++++++++ ietf/nomcom/models.py | 3 + ietf/nomcom/tests.py | 16 ++ ietf/nomcom/urls.py | 7 + ietf/nomcom/views.py | 0 ietf/templates/nomcom/manage_group.html | 18 ++ .../templates/nomcom/manage_group_review.html | 57 +++++++ ietf/urls.py | 1 + ietf/utils/fields.py | 20 +++ 11 files changed, 374 insertions(+) create mode 100644 ietf/name/migrations/0004_add_rolename_member.py create mode 100644 ietf/nomcom/__init__.py create mode 100644 ietf/nomcom/forms.py create mode 100644 ietf/nomcom/models.py create mode 100644 ietf/nomcom/tests.py create mode 100644 ietf/nomcom/urls.py create mode 100644 ietf/nomcom/views.py create mode 100644 ietf/templates/nomcom/manage_group.html create mode 100644 ietf/templates/nomcom/manage_group_review.html create mode 100644 ietf/utils/fields.py diff --git a/ietf/name/migrations/0004_add_rolename_member.py b/ietf/name/migrations/0004_add_rolename_member.py new file mode 100644 index 000000000..20d1f5d90 --- /dev/null +++ b/ietf/name/migrations/0004_add_rolename_member.py @@ -0,0 +1,155 @@ +# encoding: utf-8 +from south.v2 import DataMigration + +from name.models import RoleName + + +class Migration(DataMigration): + + def forwards(self, orm): + RoleName(slug='member', name='Member', used=True).save() + + def backwards(self, orm): + pass + + models = { + 'name.ballotpositionname': { + 'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'}, + 'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.constraintname': { + 'Meta': {'ordering': "['order']", 'object_name': 'ConstraintName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.docrelationshipname': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.docremindertypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctagname': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.doctypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.groupballotpositionname': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupBallotPositionName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.groupstatename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.grouptypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.intendedstdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.liaisonstatementpurposename': { + 'Meta': {'ordering': "['order']", 'object_name': 'LiaisonStatementPurposeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.meetingtypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'MeetingTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.rolename': { + 'Meta': {'ordering': "['order']", 'object_name': 'RoleName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.sessionstatusname': { + 'Meta': {'ordering': "['order']", 'object_name': 'SessionStatusName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.stdlevelname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.streamname': { + 'Meta': {'ordering': "['order']", 'object_name': 'StreamName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'name.timeslottypename': { + 'Meta': {'ordering': "['order']", 'object_name': 'TimeSlotTypeName'}, + 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}), + 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + } + } + + complete_apps = ['name'] diff --git a/ietf/nomcom/__init__.py b/ietf/nomcom/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ietf/nomcom/forms.py b/ietf/nomcom/forms.py new file mode 100644 index 000000000..e219d8146 --- /dev/null +++ b/ietf/nomcom/forms.py @@ -0,0 +1,97 @@ +from django import forms +from django.contrib.formtools.preview import FormPreview +from django.http import HttpResponseRedirect, HttpResponseForbidden +from django.core.urlresolvers import reverse + +from ietf.ietfauth.decorators import has_role +from ietf.utils import fields as custom_fields +from ietf.group.models import Group, Role +from ietf.name.models import RoleName +from ietf.person.models import Email + + +class ManageGroupForm(forms.Form): + + chair = forms.EmailField(label="Chair email", required=False, + widget=forms.TextInput(attrs={'size': '40'})) + members = custom_fields.MultiEmailField(label="Members email", required=False) + + def __init__(self, *args, **kwargs): + super(ManageGroupForm, self).__init__(*args, **kwargs) + + +class ManageGroupFormPreview(FormPreview): + form_template = 'nomcom/manage_group.html' + preview_template = 'nomcom/manage_group_review.html' + + def preview_get(self, request): + if not has_role(request.user, "Secretariat"): + return HttpResponseForbidden("Must be a secretariat") + + return super(ManageGroupFormPreview, self).preview_get(request) + + + def parse_params(self, *args, **kwargs): + group_acronym = kwargs['acronym'] + group = Group.objects.get(acronym=group_acronym) + chairs = group.role_set.filter(name__slug='chair') + members = group.role_set.filter(name__slug='member') + if chairs: + self.form.base_fields['chair'].initial = chairs[0].email.address + if members: + self.form.base_fields['members'].initial = ',\r\n'.join([role.email.address for role in members]) + self.state['group'] = group + + def process_preview(self, request, form, context): + chair_email = form.cleaned_data['chair'] + members_email = form.cleaned_data['members'].replace('\r\n', '').replace(' ', '').split(',') + members_info = [] + emails_not_found = [] + try: + chair_email_obj = Email.objects.get(address=chair_email) + chair_person = chair_email_obj.person + except Email.DoesNotExist: + chair_person = None + chair_email_obj = None + chair_info = {'email': chair_email, + 'email_obj': chair_email_obj, + 'person': chair_person} + + for email in members_email: + try: + email_obj = Email.objects.get(address=email) + person = email_obj.person + except Email.DoesNotExist: + person = None + if person: + members_info.append({'email': email, + 'email_obj': email_obj, + 'person': person}) + else: + emails_not_found.append(email) + self.state.update({'chair_info': chair_info, + 'members_info': members_info, + 'emails_not_found': emails_not_found}) + + def done(self, request, cleaned_data): + group = self.state['group'] + chair_info = self.state['chair_info'] + members_info = self.state['members_info'] + members_email = [member['email'] for member in self.state['members_info']] + members_excluded = group.role_set.filter(name__slug='member').exclude(email__address__in=members_email) + members_excluded.delete() + for member in members_info: + Role.objects.get_or_create(name=RoleName.objects.get(slug="member"), + group=group, + person=member['person'], + email=member['email_obj']) + + chair_exclude = group.role_set.filter(name__slug='chair').exclude(email__address=chair_info['email']) + chair_exclude.delete() + if chair_info['email_obj'] and chair_info['person']: + Role.objects.get_or_create(name=RoleName.objects.get(slug="chair"), + group=group, + person=chair_info['person'], + email=chair_info['email_obj']) + + return HttpResponseRedirect(reverse('manage_group', kwargs={'acronym': group.acronym})) diff --git a/ietf/nomcom/models.py b/ietf/nomcom/models.py new file mode 100644 index 000000000..71a836239 --- /dev/null +++ b/ietf/nomcom/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/ietf/nomcom/tests.py b/ietf/nomcom/tests.py new file mode 100644 index 000000000..501deb776 --- /dev/null +++ b/ietf/nomcom/tests.py @@ -0,0 +1,16 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +from django.test import TestCase + + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) diff --git a/ietf/nomcom/urls.py b/ietf/nomcom/urls.py new file mode 100644 index 000000000..413f708b4 --- /dev/null +++ b/ietf/nomcom/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls.defaults import patterns, url +from ietf.nomcom.forms import ManageGroupForm, ManageGroupFormPreview + +urlpatterns = patterns('ietf.nocom.views', + + url(r'^group/(?P[\w.@+-]+)/$', ManageGroupFormPreview(ManageGroupForm), name='manage_group'), +) diff --git a/ietf/nomcom/views.py b/ietf/nomcom/views.py new file mode 100644 index 000000000..e69de29bb diff --git a/ietf/templates/nomcom/manage_group.html b/ietf/templates/nomcom/manage_group.html new file mode 100644 index 000000000..54a23db08 --- /dev/null +++ b/ietf/templates/nomcom/manage_group.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block title %}Manage group {{ state.group.acronym }}{% endblock %} + +{% block content %} +

Manage group {{ state.group.acronym }}

+ +{% if form.errors %}

Please correct the following errors

{% endif %} + +
{% csrf_token %} + +{{ form }} +
+ +

+
+ +{% endblock %} diff --git a/ietf/templates/nomcom/manage_group_review.html b/ietf/templates/nomcom/manage_group_review.html new file mode 100644 index 000000000..86b379fb2 --- /dev/null +++ b/ietf/templates/nomcom/manage_group_review.html @@ -0,0 +1,57 @@ +{% extends "base.html" %} + +{% block title %}Preview group {{ state.group.acronym }} {% endblock %} + +{% block content %} +

Preview group {{ state.group.acronym }}

+ +

Chair info

+{% if state.chair_info.person.name %} + +{% else %} + email {{ state.chair_info.email }} not found +{% endif %} + +{% if state.members_info %} +

Members info

+ +{% endif %} + +{% if state.emails_not_found %} +

Members not found

+ +{% endif %} + + + +{% csrf_token %} +{% for field in form %}{{ field.as_hidden }} +{% endfor %} + + +

+ + +

Or edit it again

+ +{% csrf_token %} +
+{{ form }} +
+ +

+ + + +{% endblock %} diff --git a/ietf/urls.py b/ietf/urls.py index 3edfa9388..0da3cd45f 100644 --- a/ietf/urls.py +++ b/ietf/urls.py @@ -66,6 +66,7 @@ urlpatterns = patterns('', (r'^submit/', include('ietf.submit.urls')), (r'^streams/', include('ietf.ietfworkflows.urls')), (r'^community/', include('ietf.community.urls')), + (r'^nocom/', include('ietf.nomcom.urls')), (r'^$', 'ietf.idrfc.views.main'), (r'^admin/doc/', include('django.contrib.admindocs.urls')), diff --git a/ietf/utils/fields.py b/ietf/utils/fields.py new file mode 100644 index 000000000..acc814ef1 --- /dev/null +++ b/ietf/utils/fields.py @@ -0,0 +1,20 @@ +from django import forms +from django.forms.util import ValidationError +from django.core.validators import email_re + + +class MultiEmailField(forms.CharField): + widget = forms.widgets.Textarea + + def clean(self, value): + super(MultiEmailField, self).clean(value) + if value: + emails = map(unicode.strip, value.split(',')) + else: + return value + + for email in emails: + if not email_re.match(email): + raise ValidationError("This is not a valid comma separated email list.") + + return value