Skip to content

Commit 1f3ca71

Browse files
committed
add apps
- Legacy-Id: 5430
1 parent 5c5fc3f commit 1f3ca71

114 files changed

Lines changed: 300766 additions & 8 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ietf/secr/announcement/__init__.py

Whitespace-only changes.

ietf/secr/announcement/forms.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
from django import forms
2+
from django.core.validators import validate_email
3+
4+
from models import *
5+
from ietf.secr.utils.mail import MultiEmailField
6+
from ietf.secr.utils.group import current_nomcom
7+
8+
from ietf.message.models import Message
9+
from ietf.ietfauth.decorators import has_role
10+
from ietf.wgchairs.accounts import get_person_for_user
11+
12+
# ---------------------------------------------
13+
# Globals
14+
# ---------------------------------------------
15+
16+
ANNOUNCE_FROM_GROUPS = ['ietf','rsoc','iab',current_nomcom().acronym]
17+
ANNOUNCE_TO_GROUPS= ['ietf']
18+
19+
# this list isn't currently available as a Role query so it's hardcoded
20+
FROM_LIST = ('IETF Secretariat <ietf-secretariat@ietf.org>',
21+
'IESG Secretary <iesg-secretary@ietf.org>',
22+
'The IESG <iesg@ietf.org>',
23+
'Internet-Drafts Administrator <internet-drafts@ietf.org>',
24+
'IETF Agenda <agenda@ietf.org>',
25+
'IETF Chair <chair@ietf.org>',
26+
'IAB Chair <iab-chair@ietf.org> ',
27+
'NomCom Chair <nomcom-chair@ietf.org>',
28+
'IETF Registrar <ietf-registrar@ietf.org>',
29+
'IETF Administrative Director <iad@ietf.org>',
30+
'IETF Executive Director <exec-director@ietf.org>',
31+
'The IAOC <bob.hinden@gmail.com>',
32+
'The IETF Trust <tme@multicasttech.com>',
33+
'RSOC Chair <rsoc-chair@iab.org>',
34+
'ISOC Board of Trustees <eburger@standardstrack.com>',
35+
'RFC Series Editor <rse@rfc-editor.org>')
36+
37+
TO_LIST = ('IETF Announcement List <ietf-announce@ietf.org>',
38+
'I-D Announcement List <i-d-announce@ietf.org>',
39+
'The IESG <iesg@ietf.org>',
40+
'Working Group Chairs <wgchairs@ietf.org>',
41+
'BoF Chairs <bofchairs@ietf.org>',
42+
'Other...')
43+
# ---------------------------------------------
44+
# Custom Fields
45+
# ---------------------------------------------
46+
47+
class MultiEmailField(forms.Field):
48+
def to_python(self, value):
49+
"Normalize data to a list of strings."
50+
51+
# Return an empty list if no input was given.
52+
if not value:
53+
return []
54+
55+
import types
56+
if isinstance(value, types.StringTypes):
57+
values = value.split(',')
58+
return [ x.strip() for x in values ]
59+
else:
60+
return value
61+
62+
def validate(self, value):
63+
"Check if value consists only of valid emails."
64+
65+
# Use the parent's handling of required fields, etc.
66+
super(MultiEmailField, self).validate(value)
67+
68+
for email in value:
69+
validate_email(email)
70+
71+
# ---------------------------------------------
72+
# Helper Functions
73+
# ---------------------------------------------
74+
75+
def get_from_choices(user):
76+
'''
77+
This function returns a choices tuple containing
78+
all the Announced From choices. Including
79+
leadership chairs and other entities.
80+
'''
81+
person = user.get_profile()
82+
if has_role(user,'Secretariat'):
83+
f = FROM_LIST
84+
elif has_role(user,'IETF Chair'):
85+
f = (FROM_LIST[2],FROM_LIST[5])
86+
elif has_role(user,'IAB Chair'):
87+
f = (FROM_LIST[6],)
88+
elif has_role(user,'IAD'):
89+
f = (FROM_LIST[9],)
90+
# NomCom, RSOC Chair, IAOC Chair aren't supported by has_role()
91+
elif Role.objects.filter(name="chair",
92+
group__acronym__startswith="nomcom",
93+
group__state="active",
94+
group__type="ietf",
95+
person=person):
96+
f = (FROM_LIST[7],)
97+
elif Role.objects.filter(person=person,
98+
group__acronym='rsoc',
99+
name="chair"):
100+
f = (FROM_LIST[13],)
101+
elif Role.objects.filter(person=person,
102+
group__acronym='iaoc',
103+
name="chair"):
104+
f = (FROM_LIST[11],)
105+
elif Role.objects.filter(person=person,
106+
group__acronym='rse',
107+
name="chair"):
108+
f = (FROM_LIST[15],)
109+
return zip(f,f)
110+
111+
def get_to_choices():
112+
#groups = Group.objects.filter(acronym__in=ANNOUNCE_TO_GROUPS)
113+
#roles = Role.objects.filter(group__in=(groups),name="Announce")
114+
#choices = [ (r.email, r.person.name) for r in roles ]
115+
#choices.append(('Other...','Other...'),)
116+
return zip(TO_LIST,TO_LIST)
117+
118+
# ---------------------------------------------
119+
# Select Choices
120+
# ---------------------------------------------
121+
#TO_CHOICES = tuple(AnnouncedTo.objects.values_list('announced_to_id','announced_to'))
122+
TO_CHOICES = get_to_choices()
123+
#FROM_CHOICES = get_from_choices()
124+
125+
# ---------------------------------------------
126+
# Forms
127+
# ---------------------------------------------
128+
129+
class AnnounceForm(forms.ModelForm):
130+
nomcom = forms.BooleanField(required=False)
131+
to_custom = MultiEmailField(required=False,label='')
132+
#cc = MultiEmailField(required=False)
133+
134+
class Meta:
135+
model = Message
136+
fields = ('nomcom', 'to','to_custom','frm','cc','bcc','reply_to','subject','body')
137+
138+
def __init__(self, *args, **kwargs):
139+
user = kwargs.pop('user')
140+
super(AnnounceForm, self).__init__(*args, **kwargs)
141+
self.fields['to'].widget = forms.Select(choices=TO_CHOICES)
142+
self.fields['to'].help_text = 'Select name OR select Other... and enter email below'
143+
self.fields['cc'].help_text = 'Use comma separated lists for emails (Cc, Bcc, Reply To)'
144+
self.fields['frm'].widget = forms.Select(choices=get_from_choices(user))
145+
self.fields['frm'].label = 'From'
146+
self.fields['nomcom'].label = 'NomCom message?'
147+
148+
def clean(self):
149+
super(AnnounceForm, self).clean()
150+
data = self.cleaned_data
151+
if self.errors:
152+
return self.cleaned_data
153+
if data['to'] == 'Other...' and not data['to_custom']:
154+
raise forms.ValidationError('You must enter a "To" email address')
155+
156+
return data
157+
158+
def save(self, *args, **kwargs):
159+
user = kwargs.pop('user')
160+
message = super(AnnounceForm, self).save(commit=False)
161+
message.by = get_person_for_user(user)
162+
if self.cleaned_data['to'] == 'Other...':
163+
message.to = self.cleaned_data['to_custom']
164+
if kwargs['commit']:
165+
message.save()
166+
167+
# add nomcom to related groups if checked
168+
if self.cleaned_data.get('nomcom', False):
169+
nomcom = current_nomcom()
170+
message.related_groups.add(nomcom)
171+
172+
return message

ietf/secr/announcement/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from django.db import models
2+
from ietf.announcements.models import AnnouncedTo
3+
#from ietf.message.models import Message
4+
from ietf.group.models import Group, Role

ietf/secr/announcement/tests.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from django.core.urlresolvers import reverse
2+
from django.test import TestCase
3+
from django.contrib.auth.models import User
4+
5+
from ietf.group.models import Group
6+
from ietf.ietfauth.decorators import has_role
7+
from ietf.person.models import Person
8+
from ietf.utils.mail import outbox
9+
10+
from pyquery import PyQuery
11+
12+
SEC_USER='rcross'
13+
WG_USER=''
14+
AD_USER=''
15+
16+
class MainTestCase(TestCase):
17+
fixtures = ['names',
18+
'test-meeting',
19+
'test-group',
20+
'test-person',
21+
'test-user',
22+
'test-email',
23+
'test-role']
24+
25+
# ------- Test View -------- #
26+
def test_main(self):
27+
"Main Test"
28+
url = reverse('announcement')
29+
r = self.client.get(url,REMOTE_USER=SEC_USER)
30+
self.assertEquals(r.status_code, 200)
31+
32+
class UnauthorizedCase(TestCase):
33+
fixtures = ['names',
34+
'test-group',
35+
'test-person',
36+
'test-user',
37+
'test-email',
38+
'test-role']
39+
40+
def test_unauthorized(self):
41+
"Unauthorized Test"
42+
url = reverse('announcement')
43+
# get random working group chair
44+
person = Person.objects.filter(role__group__type='wg')[0]
45+
r = self.client.get(url,REMOTE_USER=person.user)
46+
self.assertEquals(r.status_code, 403)
47+
48+
class SubmitCase(TestCase):
49+
fixtures = ['names',
50+
'test-meeting',
51+
'test-group',
52+
'test-person',
53+
'test-user',
54+
'test-email',
55+
'test-role']
56+
57+
def test_invalid_submit(self):
58+
"Invalid Submit"
59+
url = reverse('announcement')
60+
post_data = {'id_subject':''}
61+
#self.client.login(remote_user='rcross')
62+
r = self.client.post(url,post_data,REMOTE_USER=SEC_USER)
63+
self.assertEquals(r.status_code, 200)
64+
q = PyQuery(r.content)
65+
self.assertTrue(len(q('form ul.errorlist')) > 0)
66+
67+
def test_valid_submit(self):
68+
"Valid Submit"
69+
#ietf.utils.mail.test_mode = True
70+
url = reverse('announcement')
71+
redirect = reverse('home')
72+
post_data = {'to':'Other...',
73+
'to_custom':'rcross@amsl.com',
74+
'frm':'IETF Secretariat &lt;ietf-secretariat@ietf.org&gt;',
75+
'subject':'Test Subject',
76+
'body':'This is a test.'}
77+
r = self.client.post(url,post_data,follow=True,REMOTE_USER=SEC_USER)
78+
self.assertRedirects(r, redirect)
79+
self.assertEqual(len(outbox), 1)
80+
#self.assertTrue(len(outbox) > mailbox_before)

ietf/secr/announcement/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.conf.urls.defaults import *
2+
3+
urlpatterns = patterns('ietf.secr.announcement.views',
4+
url(r'^$', 'main', name='announcement'),
5+
url(r'^confirm/$', 'confirm', name='announcement_confirm'),
6+
)

ietf/secr/announcement/views.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
from django.contrib import messages
2+
from django.core.urlresolvers import reverse
3+
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden
4+
from django.shortcuts import render_to_response, get_object_or_404
5+
from django.template import RequestContext
6+
7+
from ietf.ietfauth.decorators import has_role
8+
from ietf.utils.mail import send_mail_text
9+
from ietf.wgchairs.accounts import get_person_for_user
10+
from ietf.group.models import Group
11+
from ietf.secr.utils.group import current_nomcom
12+
from ietf.secr.utils.decorators import check_for_cancel
13+
14+
from forms import *
15+
16+
# -------------------------------------------------
17+
# Helper Functions
18+
# -------------------------------------------------
19+
def check_access(user):
20+
'''
21+
This function takes a Django User object and returns true if the user has access to the
22+
Announcement app. Accepted roles are:
23+
Secretariat, IAD, IAB Chair, IETF Chair, RSOC Chair, IAOC Chair, NomCom Chair, RSE Chair
24+
'''
25+
person = user.get_profile()
26+
groups_with_access = ("iab", "rsoc", "ietf", "iaoc", "rse")
27+
if Role.objects.filter(person=person,
28+
group__acronym__in=groups_with_access,
29+
name="chair") or has_role(user, ["Secretariat","IAD"]):
30+
return True
31+
if Role.objects.filter(name="chair",
32+
group__acronym__startswith="nomcom",
33+
group__state="active",
34+
group__type="ietf",
35+
person=person):
36+
return True
37+
38+
return False
39+
40+
# --------------------------------------------------
41+
# STANDARD VIEW FUNCTIONS
42+
# --------------------------------------------------
43+
# this seems to cause some kind of circular problem
44+
# @check_for_cancel(reverse('home'))
45+
@check_for_cancel('../')
46+
def main(request):
47+
'''
48+
Main view for Announcement tool. Authrozied users can fill out email details: header, body, etc
49+
and send.
50+
'''
51+
if not check_access(request.user):
52+
return HttpResponseForbidden('Restricted to: Secretariat, IAD, or chair of IETF, IAB, RSOC, RSE, IAOC, NomCom.')
53+
54+
form = AnnounceForm(request.POST or None,user=request.user)
55+
56+
if form.is_valid():
57+
request.session['data'] = form.cleaned_data
58+
59+
url = reverse('announcement_confirm')
60+
return HttpResponseRedirect(url)
61+
62+
return render_to_response('announcement/main.html', {
63+
'form': form},
64+
RequestContext(request, {}),
65+
)
66+
67+
@check_for_cancel('../')
68+
def confirm(request):
69+
70+
# testing
71+
#assert False, (request.session.get_expiry_age(),request.session.get_expiry_date())
72+
73+
if request.method == 'POST':
74+
form = AnnounceForm(request.session['data'],user=request.user)
75+
message = form.save(user=request.user,commit=True)
76+
send_mail_text(None,
77+
message.to,
78+
message.frm,
79+
message.subject,
80+
message.body,
81+
cc=message.cc,
82+
bcc=message.bcc)
83+
84+
# clear session
85+
request.session.clear()
86+
87+
messages.success(request, 'The announcement was sent.')
88+
url = reverse('announcement')
89+
return HttpResponseRedirect(url)
90+
91+
if request.session.get('data',None):
92+
data = request.session['data']
93+
else:
94+
messages.error(request, 'No session data. Your session may have expired or cookies are disallowed.')
95+
redirect_url = reverse('announcement')
96+
return HttpResponseRedirect(redirect_url)
97+
98+
if data['to'] == 'Other...':
99+
to = ','.join(data['to_custom'])
100+
else:
101+
to = data['to']
102+
103+
return render_to_response('announcement/confirm.html', {
104+
'message': data,
105+
'to': to},
106+
RequestContext(request, {}),
107+
)

ietf/secr/areas/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)