Skip to content

Commit 230f6cf

Browse files
committed
view only accessible by the secretariat that allows to assign the email of the members and the chair of the NomCom
See ietf-tools#904 - Legacy-Id: 5066
1 parent 6aad0f2 commit 230f6cf

11 files changed

Lines changed: 374 additions & 0 deletions

File tree

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# encoding: utf-8
2+
from south.v2 import DataMigration
3+
4+
from name.models import RoleName
5+
6+
7+
class Migration(DataMigration):
8+
9+
def forwards(self, orm):
10+
RoleName(slug='member', name='Member', used=True).save()
11+
12+
def backwards(self, orm):
13+
pass
14+
15+
models = {
16+
'name.ballotpositionname': {
17+
'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'},
18+
'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
19+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
20+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
21+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
22+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
23+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
24+
},
25+
'name.constraintname': {
26+
'Meta': {'ordering': "['order']", 'object_name': 'ConstraintName'},
27+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
28+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
29+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
30+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
31+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
32+
},
33+
'name.docrelationshipname': {
34+
'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'},
35+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
36+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
37+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
38+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
39+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
40+
},
41+
'name.docremindertypename': {
42+
'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'},
43+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
44+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
45+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
46+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
47+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
48+
},
49+
'name.doctagname': {
50+
'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'},
51+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
52+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
53+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
54+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
55+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
56+
},
57+
'name.doctypename': {
58+
'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'},
59+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
60+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
61+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
62+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
63+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
64+
},
65+
'name.groupballotpositionname': {
66+
'Meta': {'ordering': "['order']", 'object_name': 'GroupBallotPositionName'},
67+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
68+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
69+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
70+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
71+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
72+
},
73+
'name.groupstatename': {
74+
'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'},
75+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
76+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
77+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
78+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
79+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
80+
},
81+
'name.grouptypename': {
82+
'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'},
83+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
84+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
85+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
86+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
87+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
88+
},
89+
'name.intendedstdlevelname': {
90+
'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'},
91+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
92+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
93+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
94+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
95+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
96+
},
97+
'name.liaisonstatementpurposename': {
98+
'Meta': {'ordering': "['order']", 'object_name': 'LiaisonStatementPurposeName'},
99+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
100+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
101+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
102+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
103+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
104+
},
105+
'name.meetingtypename': {
106+
'Meta': {'ordering': "['order']", 'object_name': 'MeetingTypeName'},
107+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
108+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
109+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
110+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
111+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
112+
},
113+
'name.rolename': {
114+
'Meta': {'ordering': "['order']", 'object_name': 'RoleName'},
115+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
116+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
117+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
118+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
119+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
120+
},
121+
'name.sessionstatusname': {
122+
'Meta': {'ordering': "['order']", 'object_name': 'SessionStatusName'},
123+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
124+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
125+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
126+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
127+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
128+
},
129+
'name.stdlevelname': {
130+
'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'},
131+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
132+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
133+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
134+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
135+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
136+
},
137+
'name.streamname': {
138+
'Meta': {'ordering': "['order']", 'object_name': 'StreamName'},
139+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
140+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
141+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
142+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
143+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
144+
},
145+
'name.timeslottypename': {
146+
'Meta': {'ordering': "['order']", 'object_name': 'TimeSlotTypeName'},
147+
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
148+
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
149+
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
150+
'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
151+
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
152+
}
153+
}
154+
155+
complete_apps = ['name']

ietf/nomcom/__init__.py

Whitespace-only changes.

ietf/nomcom/forms.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from django import forms
2+
from django.contrib.formtools.preview import FormPreview
3+
from django.http import HttpResponseRedirect, HttpResponseForbidden
4+
from django.core.urlresolvers import reverse
5+
6+
from ietf.ietfauth.decorators import has_role
7+
from ietf.utils import fields as custom_fields
8+
from ietf.group.models import Group, Role
9+
from ietf.name.models import RoleName
10+
from ietf.person.models import Email
11+
12+
13+
class ManageGroupForm(forms.Form):
14+
15+
chair = forms.EmailField(label="Chair email", required=False,
16+
widget=forms.TextInput(attrs={'size': '40'}))
17+
members = custom_fields.MultiEmailField(label="Members email", required=False)
18+
19+
def __init__(self, *args, **kwargs):
20+
super(ManageGroupForm, self).__init__(*args, **kwargs)
21+
22+
23+
class ManageGroupFormPreview(FormPreview):
24+
form_template = 'nomcom/manage_group.html'
25+
preview_template = 'nomcom/manage_group_review.html'
26+
27+
def preview_get(self, request):
28+
if not has_role(request.user, "Secretariat"):
29+
return HttpResponseForbidden("Must be a secretariat")
30+
31+
return super(ManageGroupFormPreview, self).preview_get(request)
32+
33+
34+
def parse_params(self, *args, **kwargs):
35+
group_acronym = kwargs['acronym']
36+
group = Group.objects.get(acronym=group_acronym)
37+
chairs = group.role_set.filter(name__slug='chair')
38+
members = group.role_set.filter(name__slug='member')
39+
if chairs:
40+
self.form.base_fields['chair'].initial = chairs[0].email.address
41+
if members:
42+
self.form.base_fields['members'].initial = ',\r\n'.join([role.email.address for role in members])
43+
self.state['group'] = group
44+
45+
def process_preview(self, request, form, context):
46+
chair_email = form.cleaned_data['chair']
47+
members_email = form.cleaned_data['members'].replace('\r\n', '').replace(' ', '').split(',')
48+
members_info = []
49+
emails_not_found = []
50+
try:
51+
chair_email_obj = Email.objects.get(address=chair_email)
52+
chair_person = chair_email_obj.person
53+
except Email.DoesNotExist:
54+
chair_person = None
55+
chair_email_obj = None
56+
chair_info = {'email': chair_email,
57+
'email_obj': chair_email_obj,
58+
'person': chair_person}
59+
60+
for email in members_email:
61+
try:
62+
email_obj = Email.objects.get(address=email)
63+
person = email_obj.person
64+
except Email.DoesNotExist:
65+
person = None
66+
if person:
67+
members_info.append({'email': email,
68+
'email_obj': email_obj,
69+
'person': person})
70+
else:
71+
emails_not_found.append(email)
72+
self.state.update({'chair_info': chair_info,
73+
'members_info': members_info,
74+
'emails_not_found': emails_not_found})
75+
76+
def done(self, request, cleaned_data):
77+
group = self.state['group']
78+
chair_info = self.state['chair_info']
79+
members_info = self.state['members_info']
80+
members_email = [member['email'] for member in self.state['members_info']]
81+
members_excluded = group.role_set.filter(name__slug='member').exclude(email__address__in=members_email)
82+
members_excluded.delete()
83+
for member in members_info:
84+
Role.objects.get_or_create(name=RoleName.objects.get(slug="member"),
85+
group=group,
86+
person=member['person'],
87+
email=member['email_obj'])
88+
89+
chair_exclude = group.role_set.filter(name__slug='chair').exclude(email__address=chair_info['email'])
90+
chair_exclude.delete()
91+
if chair_info['email_obj'] and chair_info['person']:
92+
Role.objects.get_or_create(name=RoleName.objects.get(slug="chair"),
93+
group=group,
94+
person=chair_info['person'],
95+
email=chair_info['email_obj'])
96+
97+
return HttpResponseRedirect(reverse('manage_group', kwargs={'acronym': group.acronym}))

ietf/nomcom/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.db import models
2+
3+
# Create your models here.

ietf/nomcom/tests.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""
2+
This file demonstrates writing tests using the unittest module. These will pass
3+
when you run "manage.py test".
4+
5+
Replace this with more appropriate tests for your application.
6+
"""
7+
8+
from django.test import TestCase
9+
10+
11+
class SimpleTest(TestCase):
12+
def test_basic_addition(self):
13+
"""
14+
Tests that 1 + 1 always equals 2.
15+
"""
16+
self.assertEqual(1 + 1, 2)

ietf/nomcom/urls.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from django.conf.urls.defaults import patterns, url
2+
from ietf.nomcom.forms import ManageGroupForm, ManageGroupFormPreview
3+
4+
urlpatterns = patterns('ietf.nocom.views',
5+
6+
url(r'^group/(?P<acronym>[\w.@+-]+)/$', ManageGroupFormPreview(ManageGroupForm), name='manage_group'),
7+
)

ietf/nomcom/views.py

Whitespace-only changes.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{% extends "base.html" %}
2+
3+
{% block title %}Manage group {{ state.group.acronym }}{% endblock %}
4+
5+
{% block content %}
6+
<h1>Manage group {{ state.group.acronym }}</h1>
7+
8+
{% if form.errors %}<h3>Please correct the following errors</h3>{% endif %}
9+
10+
<form action="" method="post">{% csrf_token %}
11+
<table>
12+
{{ form }}
13+
</table>
14+
<input type="hidden" name="{{ stage_field }}" value="1" />
15+
<p><input type="submit" value="Preview" /></p>
16+
</form>
17+
18+
{% endblock %}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{% extends "base.html" %}
2+
3+
{% block title %}Preview group {{ state.group.acronym }} {% endblock %}
4+
5+
{% block content %}
6+
<h1>Preview group {{ state.group.acronym }}</h1>
7+
8+
<h2>Chair info </h2>
9+
{% if state.chair_info.person.name %}
10+
<ul>
11+
<li><strong>Email:</strong> {{ state.chair_info.email }}</li>
12+
<li><strong>Person name:</strong> {{ state.chair_info.person.name }}</li>
13+
</ul>
14+
{% else %}
15+
email {{ state.chair_info.email }} not found
16+
{% endif %}
17+
18+
{% if state.members_info %}
19+
<h2>Members info </h2>
20+
<ul>
21+
{% for member in state.members_info %}
22+
<li><strong>Email:</strong> {{ member.email }}. <strong>Person Name</strong>: {{ member.person.name }}</li>
23+
{% endfor %}
24+
</ul>
25+
{% endif %}
26+
27+
{% if state.emails_not_found %}
28+
<h2>Members not found </h2>
29+
<ul>
30+
{% for email in state.emails_not_found %}
31+
<li>{{ email }}</li>
32+
{% endfor %}
33+
</ul>
34+
{% endif %}
35+
36+
37+
<table>
38+
<form action="" method="post">{% csrf_token %}
39+
{% for field in form %}{{ field.as_hidden }}
40+
{% endfor %}
41+
<input type="hidden" name="{{ stage_field }}" value="2" />
42+
<input type="hidden" name="{{ hash_field }}" value="{{ hash_value }}" />
43+
<p><input type="submit" value="Submit" /></p>
44+
</form>
45+
46+
<h1>Or edit it again</h1>
47+
48+
<form action="" method="post">{% csrf_token %}
49+
<table>
50+
{{ form }}
51+
</table>
52+
<input type="hidden" name="{{ stage_field }}" value="1" />
53+
<p><input type="submit" value="Preview" /></p>
54+
</form>
55+
56+
57+
{% endblock %}

ietf/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
(r'^submit/', include('ietf.submit.urls')),
6767
(r'^streams/', include('ietf.ietfworkflows.urls')),
6868
(r'^community/', include('ietf.community.urls')),
69+
(r'^nocom/', include('ietf.nomcom.urls')),
6970

7071
(r'^$', 'ietf.idrfc.views.main'),
7172
(r'^admin/doc/', include('django.contrib.admindocs.urls')),

0 commit comments

Comments
 (0)