Expose views for concluded nomcoms. Close feedback and nomination. Initial work on factory-boy based testing. Partially addresses #1856

- Legacy-Id: 10520
This commit is contained in:
Robert Sparks 2015-11-25 22:17:41 +00:00
parent 2197259102
commit e81b473282
14 changed files with 283 additions and 18 deletions

10
ietf/group/factories.py Normal file
View file

@ -0,0 +1,10 @@
import factory
from ietf.group.models import Group
class GroupFactory(factory.DjangoModelFactory):
class Meta:
model = Group
name = factory.Faker('sentence',nb_words=6)
acronym = factory.Faker('word')

View file

@ -64,9 +64,9 @@ def has_role(user, role_names, *args, **kwargs):
"RG Secretary": Q(person=person,name="secr", group__type="rg", group__state__in=["active","proposed"]),
"AG Secretary": Q(person=person,name="secr", group__type="ag", group__state__in=["active"]),
"Team Chair": Q(person=person,name="chair", group__type="team", group__state="active"),
"Nomcom Chair": Q(person=person, name="chair", group__type="nomcom", group__state="active", group__acronym__icontains=kwargs.get('year', '0000')),
"Nomcom Advisor": Q(person=person, name="advisor", group__type="nomcom", group__state="active", group__acronym__icontains=kwargs.get('year', '0000')),
"Nomcom": Q(person=person, group__type="nomcom", group__state="active", group__acronym__icontains=kwargs.get('year', '0000')),
"Nomcom Chair": Q(person=person, name="chair", group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),
"Nomcom Advisor": Q(person=person, name="advisor", group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),
"Nomcom": Q(person=person, group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),
"Liaison Manager": Q(person=person,name="liaiman",group__type="sdo",group__state="active", ),
"Authorized Individual": Q(person=person,name="auth",group__type="sdo",group__state="active", ),
}

125
ietf/nomcom/factories.py Normal file
View file

@ -0,0 +1,125 @@
import factory
import random
from ietf.nomcom.models import NomCom, Position, Nominee, NomineePosition
from ietf.group.factories import GroupFactory
from ietf.person.factories import PersonFactory
import debug # pyflakes:ignore
cert = '''-----BEGIN CERTIFICATE-----
MIIDHjCCAgagAwIBAgIJAKDCCjbQboJzMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV
BAMMCE5vbUNvbTE1MB4XDTE0MDQwNDIxMTQxNFoXDTE2MDQwMzIxMTQxNFowEzER
MA8GA1UEAwwITm9tQ29tMTUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQC2QXCsAitYSOgPYor77zQnEeHuVqlcuhpH1wpKB+N6WcScA5N3AnX9uZEFOt6M
cJ+MCiHECdqDlH6npQTJlpCpIVgAD4B6xzjRBRww8d3lClA/kKwsKzuX93RS0Uv3
0hAD6q9wjqK/m6vR5Y1SsvJYV0y+Yu5j9xUEsojMH7O3NlXWAYOb6oH+f/X7PX27
IhtiCwfICMmVWh/hKeXuFx6HSOcH3gZ6Tlk1llfDbE/ArpsZ6JmnLn73+64yqIoO
ZOc4JJUPrdsmbNwXoxQSQhrpwjN8NpSkQaJbHGB3G+OWvP4fpqcweFHxlEq1Hhef
uR9E6jc3qwxVQfwjbcq6N/4JAgMBAAGjdTBzMB0GA1UdDgQWBBTJow+TJynRWsTQ
LzoS861FGb/rxDAOBgNVHQ8BAf8EBAMCBLAwDwYDVR0TAQH/BAUwAwEB/zAcBgNV
HREEFTATgRFub21jb20xNUBpZXRmLm9yZzATBgNVHSUEDDAKBggrBgEFBQcDBDAN
BgkqhkiG9w0BAQsFAAOCAQEAJwLapB9u5N3iK6SCTqh+PVkigZeB2YMVBW8WA3Ut
iRPBj+jHWOpF5pzZHTOcNaAxDEG9lyIlcWqc93A24K/Gen11Tx0hO4FAPOG0+PP8
4lx7F6xeeyUNR44pInrB93G2q0jl+3wjZH8uhBKlGji4UTMpDPpEl6uiyQCbkMMm
Vr7HZH5Dv/lsjGHHf8uJO7+mcMh+tqxLn3DzPrm61OfeWdkoVX2pTz0imRQ3Es+8
I7zNMk+fNNaEEyPnEyHfuWq0uD/qKeP27NZIoINy6E3INQ5QaE2uc1nQULg5y7uJ
toX3j+FUe2UiUak3ACXdrOPSsFP0KRrFwuMnuHHXkGj/Uw==
-----END CERTIFICATE-----
'''
key = '''-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC2QXCsAitYSOgP
Yor77zQnEeHuVqlcuhpH1wpKB+N6WcScA5N3AnX9uZEFOt6McJ+MCiHECdqDlH6n
pQTJlpCpIVgAD4B6xzjRBRww8d3lClA/kKwsKzuX93RS0Uv30hAD6q9wjqK/m6vR
5Y1SsvJYV0y+Yu5j9xUEsojMH7O3NlXWAYOb6oH+f/X7PX27IhtiCwfICMmVWh/h
KeXuFx6HSOcH3gZ6Tlk1llfDbE/ArpsZ6JmnLn73+64yqIoOZOc4JJUPrdsmbNwX
oxQSQhrpwjN8NpSkQaJbHGB3G+OWvP4fpqcweFHxlEq1HhefuR9E6jc3qwxVQfwj
bcq6N/4JAgMBAAECggEAb5SS4YwWc193S2v+QQ2KdVz6YEuINq/tRQw/TWGVACQT
PZzm3FaSXDsOsRAAjiSpWTgewgFyWVpBTGu4CZ73g8RZNvhGpWRwwW8KemCpg/8T
cEcnUYdKXdhuzAE9LETb7znwHM4Gj55DzCZopjfOLQ2Ne4XgAy2THaQcIjRKd6Bw
3mteJ2ityDj3iFN7cq9ntDzp+2BqLOi7AZmLntmUZxtkPCT6k5/dcKFYQW9Eb3bt
MON+BIYVzqhAijkP/cAWmbgZAP9EFng5PpE1lc/shl0W8eX4yvjNoMPRq3wphS4j
L16VncUeDep3vR0CECx7gnTfR0uCDEgKow50pzGQAQKBgQDaQWwK/o39zI3lCGzy
oSNJRNQJ/iZBkbbwpCCaka7VnBfd0ZH54VEWL3oMTkkWRSZtjsPAqT+ndwZitm0D
Kww9FUDMP7j/tMOwAUHYfjYFqFTn6ipkBuby9tbZtL7lgJO6Iu2Qk3afqADD0kcP
zRLxcYSLjrmp9NyUlNnpswR4CQKBgQDVxjwG/orCmiuyA1Bu4u1hdUD0w9CKnyjp
VTbkv8lxk5V3pYzms2Awb0X43W2OioYGBk5yw+9GCF//xCrfbGV7BLZnDTGShjkJ
8oTpLPGBsDSfaKVXE3Hko4LVLBMQIm0tDyuPD1Naia7ZknYn906skonEG8WgHUyp
c/BgkvzWAQKBgBdojuL6/FWtO8bFyZGYUMWJ+Uf9FzNPIpTatZh+aYcFj9W9pW9s
iBreCrQJLXOTBRUZC8u9G1Olw2yQ7k45rr1aazG83+WlCJv29o32s2qV7E1XYyaJ
SvniGZcN+K96w91h46Lu/fkPts1J309FinOU3kdtjmI5HfNdp6WWCrOpAoGBAMjc
TEaeIK8cwPWwG4E1A6pQy8mvu2Ckj4I+KSfh9FsdOpGDIdMas8SOqQZet7P5AFjk
0A0RgN8iu2DMZyQq62cdVG2bffqY1zs7fhrBueILOEaXwtMAWEFmSWYW1YqRbleq
K1luIvms6HdSIGcI/gk0XvG+zn/VR9ToNPHo6lwBAoGBAIrYGYPf+cjZ1V/tNqnL
IecEZb4Gkp1hVhOpNT4U+T2LROxrZtFxxsw2vuIRa5a5FtMbDq9Xyhkm0QppliBd
KQ38jTT0EaD2+vstTqL8vxupo25RQWV1XsmLL4pLbKnm2HnnwB3vEtsiokWKW0q0
Tdb0MiLc+r/zvx8oXtgDjDUa
-----END PRIVATE KEY-----
'''
def nomcom_kwargs_for_year(year=None, *args, **kwargs):
if not year:
year = random.randint(1980,2100)
if 'group__state_id' not in kwargs:
kwargs['group__state_id']='active'
if 'group__acronym' not in kwargs:
kwargs['group__acronym'] = 'nomcom%d'%year
if 'group__name' not in kwargs:
kwargs['group__name'] = 'TEST VERSION of IAB/IESG Nominating Committee %d/%d'%(year,year+1)
return kwargs
class NomComFactory(factory.DjangoModelFactory):
class Meta:
model = NomCom
group = factory.SubFactory(GroupFactory,type_id='nomcom')
public_key = factory.django.FileField(data=cert)
@factory.post_generation
def populate_positions(self, create, extracted, **kwargs):
'''
Create a set of nominees and positions unless NomcomFactory is called
with populate_positions=False
'''
if extracted is None:
extracted = True
if create and extracted:
nominees = [Nominee.objects.create(nomcom=self, email=PersonFactory().email_set.first()) for i in range(2)]
positions = [PositionFactory(nomcom=self) for i in range(3)]
def npc(x,y):
return NomineePosition.objects.create(position=x,
nominee=y,
state_id='accepted')
# This gives us positions with 0, 1 and 2 nominees, and
# one person who's been nomminated for more than one position
npc(positions[0],nominees[0])
npc(positions[1],nominees[0])
npc(positions[1],nominees[1])
@factory.post_generation
def populate_personnel(self, create, extracted, **kwargs):
'''
Create a default set of role holders, unless the factory is called
with populate_personnel=False
'''
if extracted is None:
extracted = True
if create and extracted:
#roles= ['chair', 'advisor'] + ['member']*10
roles = ['chair', 'advisor', 'member']
for role in roles:
p = PersonFactory()
self.group.role_set.create(name_id=role,person=p,email=p.email_set.first())
class PositionFactory(factory.DjangoModelFactory):
class Meta:
model = Position
name = factory.Faker('sentence',nb_words=10)
description = factory.Faker('paragraph',nb_sentences=4)
is_open = True

View file

@ -66,6 +66,14 @@ class NomCom(models.Model):
if created:
initialize_templates_for_group(self)
def year(self):
year = getattr(self,'_cached_year',None)
if year is None:
if self.group and self.group.acronym.startswith('nomcom'):
year = int(self.group.acronym[6:])
self._cached_year = year
return year
def delete_nomcom(sender, **kwargs):
nomcom = kwargs.get('instance', None)

View file

@ -30,6 +30,9 @@ from ietf.nomcom.forms import EditMembersForm, EditMembersFormPreview
from ietf.nomcom.utils import get_nomcom_by_year, get_or_create_nominee
from ietf.nomcom.management.commands.send_reminders import Command, is_time_to_send
from ietf.nomcom.factories import NomComFactory, nomcom_kwargs_for_year
from ietf.person.factories import PersonFactory
client_test_cert_files = None
def get_cert_files():
@ -920,3 +923,51 @@ class ReminderTest(TestCase):
self.assertEqual(len(outbox), messages_before + 1)
self.assertTrue('nominee1@' in outbox[-1]['To'])
class InactiveNomcomTests(TestCase):
def setUp(self):
self.nc = NomComFactory.create(**nomcom_kwargs_for_year(group__state_id='conclude'))
self.plain_person = PersonFactory.create()
def test_feedback_closed(self):
for view in ['nomcom_public_feedback', 'nomcom_private_feedback']:
url = reverse(view, kwargs={'year': self.nc.year()})
who = self.plain_person if 'public' in view else self.nc.group.role_set.filter(name='member').first().person
login_testing_unauthorized(self, who.user.username, url)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
q = PyQuery(response.content)
self.assertTrue( '(Concluded)' in q('h1').text())
self.assertTrue( 'closed' in q('#instructions').text())
self.assertTrue( q('#nominees a') )
self.assertFalse( q('#nominees a[href]') )
url += "?nominee=%d&position=%d" % (self.nc.nominee_set.first().id, self.nc.nominee_set.first().nomineeposition_set.first().position.id)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
q = PyQuery(response.content)
self.assertFalse( q('#feedbackform'))
empty_outbox()
fb_before = self.nc.feedback_set.count()
test_data = {'comments': u'Test feedback view. Comments with accents äöåÄÖÅ éáíóú âêîôû ü àèìòù.',
'nominator_email': self.plain_person.email_set.first().address,
'confirmation': True}
response = self.client.post(url, test_data)
self.assertEqual(response.status_code, 200)
q = PyQuery(response.content)
self.assertTrue( 'closed' in q('#instructions').text())
self.assertEqual( len(outbox), 0 )
self.assertEqual( fb_before, self.nc.feedback_set.count() )
def test_nominations_closed(self):
for view in ['nomcom_public_nominate', 'nomcom_private_nominate']:
url = reverse(view, kwargs={'year': self.nc.year() })
who = self.plain_person if 'public' in view else self.nc.group.role_set.filter(name='member').first().person
login_testing_unauthorized(self, who.user.username, url)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
q = PyQuery(response.content)
self.assertTrue( '(Concluded)' in q('h1').text())
self.assertTrue( 'closed' in q('.alert-warning').text())

View file

@ -53,7 +53,7 @@ def get_nomcom_by_year(year):
from ietf.nomcom.models import NomCom
return get_object_or_404(NomCom,
group__acronym__icontains=year,
group__state__slug='active')
)
def get_year_by_nomcom(nomcom):

View file

@ -322,6 +322,14 @@ def nominate(request, year, public):
'year': year,
'selected': 'nominate'}, RequestContext(request))
if nomcom.group.state_id == 'conclude':
message = ('warning', "Nominations to this Nomcom are closed.")
return render_to_response(template,
{'message': message,
'nomcom': nomcom,
'year': year,
'selected': 'nominate'}, RequestContext(request))
message = None
if request.method == 'POST':
form = NominateForm(data=request.POST, nomcom=nomcom, user=request.user, public=public)
@ -355,6 +363,7 @@ def feedback(request, year, public):
has_publickey = nomcom.public_key and True or False
nominee = None
position = None
if nomcom.group.state_id != 'conclude':
selected_nominee = request.GET.get('nominee')
selected_position = request.GET.get('position')
if selected_nominee and selected_position:
@ -379,7 +388,7 @@ def feedback(request, year, public):
})
message = None
if request.method == 'POST':
if nominee and position and request.method == 'POST':
form = FeedbackForm(data=request.POST,
nomcom=nomcom, user=request.user,
public=public, position=position, nominee=nominee)

56
ietf/person/factories.py Normal file
View file

@ -0,0 +1,56 @@
import factory
import faker
from unidecode import unidecode
from django.contrib.auth.models import User
from ietf.person.models import Person, Alias, Email
fake = faker.Factory.create()
class UserFactory(factory.DjangoModelFactory):
class Meta:
model = User
django_get_or_create = ('username',)
first_name = factory.Faker('first_name')
last_name = factory.Faker('last_name')
email = factory.LazyAttribute(lambda u: '%s.%s@%s'%(u.first_name,u.last_name,fake.domain_name()))
username = factory.LazyAttribute(lambda u: u.email)
@factory.post_generation
def set_password(self, create, extracted, **kwargs):
self.set_password( '%s+password' % self.username )
class PersonFactory(factory.DjangoModelFactory):
class Meta:
model = Person
user = factory.SubFactory(UserFactory)
name = factory.LazyAttribute(lambda p: '%s %s'%(p.user.first_name,p.user.last_name))
ascii = factory.LazyAttribute(lambda p: unidecode(p.name))
@factory.post_generation
def default_aliases(self, create, extracted, **kwargs):
make_alias = getattr(AliasFactory, 'create' if create else 'build')
make_alias(person=self,name=self.name)
make_alias(person=self,name=self.ascii)
@factory.post_generation
def default_emails(self, create, extracted, **kwargs):
make_email = getattr(EmailFactory, 'create' if create else 'build')
make_email(person=self,address=self.user.email)
class AliasFactory(factory.DjangoModelFactory):
class Meta:
model = Alias
django_get_or_create = ('name',)
name = factory.Faker('name')
class EmailFactory(factory.DjangoModelFactory):
class Meta:
model = Email
django_get_or_create = ('address',)
address = '%s.%s@%s' % (factory.Faker('first_name'),factory.Faker('last_name'),factory.Faker('domain_name'))

View file

@ -9,8 +9,12 @@
{% block nomcom_content %}
{% origin %}
<p class="alert alert-info">
<p id="instructions" class="alert alert-info">
{% if nomcom.group.state_id == 'conclude' %}
Feedback to this nomcom is closed.
{% else %}
Select a nominee from the list of nominees to the right to obtain a new feedback form.
{% endif %}
</p>
{% if message %}
@ -21,7 +25,7 @@
{% if nomcom|has_publickey %}
<div class="row">
<div class="col-sm-4 col-sm-push-8">
<div id="nominees" class="col-sm-4 col-sm-push-8">
<h3>Nominees</h3>
{% for p in positions %}
@ -29,7 +33,7 @@
<h4>{{ p.name }}</h4>
<div class="btn-group-vertical form-group">
{% for np in p.nomineeposition_set.accepted.not_duplicated %}
<a class="btn btn-default btn-xs" href="?nominee={{np.nominee.id}}&position={{ np.position.id}}">
<a class="btn btn-default btn-xs" {% if nomcom.group.state_id != 'conclude' %}href="?nominee={{np.nominee.id}}&position={{ np.position.id}}"{% endif %}>
{{ np.nominee }}
{% add_num_nominations user np.position np.nominee %}
</a>

View file

@ -9,7 +9,7 @@
{% block content %}
{% origin %}
<h1>NomCom {{ year }} <small>Private area {% if is_chair_task %}- Chair/Advisors only{% endif %}</small></h1>
<h1>NomCom {{ year }} {% if nomcom.group.state_id == 'conclude' %}(Concluded){% endif %} <small>Private area {% if is_chair_task %}- Chair/Advisors only{% endif %}</small></h1>
<ul class="nav nav-tabs" role="tablist">
<li {% if selected == "index" %}class="active"{% endif %}><a href="{% url "nomcom_private_index" year %}">Nominees</a></li>

View file

@ -9,7 +9,7 @@
{% block content %}
{% origin %}
<h1>NomCom {{ year }}</h1>
<h1>NomCom {{ year }} {% if nomcom.group.state_id == 'conclude' %}(Concluded){% endif %}</h1>
<ul class="nav nav-tabs" role="tablist">
<li {% if selected == "index" %}class="active"{% endif %}><a href="{% url "nomcom_year_index" year %}">Home</a></li>

View file

@ -17,7 +17,7 @@
<p class="alert alert-{{ message.0 }}">{{ message.1 }}</p>
{% endif %}
{% if nomcom|has_publickey %}
{% if form %}
<form id="nominate-form" method="post">
{% csrf_token %}

View file

@ -15,7 +15,7 @@
{% bootstrap_messages %}
{% if nomcom|has_publickey %}
{% if form %}
<form id="nominate-form" method="post">
{% csrf_token %}
{% bootstrap_form form %}

View file

@ -25,3 +25,5 @@ six>=1.8.0
wsgiref>=0.1.2
xml2rfc>=2.5.0
django>=1.7.10,<1.8
factory-boy>=2.6.0
Unidecode>=0.4.18