diff --git a/ietf/group/factories.py b/ietf/group/factories.py new file mode 100644 index 000000000..9cef50afd --- /dev/null +++ b/ietf/group/factories.py @@ -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') diff --git a/ietf/ietfauth/utils.py b/ietf/ietfauth/utils.py index 2a1db3938..e28587aec 100644 --- a/ietf/ietfauth/utils.py +++ b/ietf/ietfauth/utils.py @@ -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", ), } diff --git a/ietf/nomcom/factories.py b/ietf/nomcom/factories.py new file mode 100644 index 000000000..eed265eff --- /dev/null +++ b/ietf/nomcom/factories.py @@ -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 + diff --git a/ietf/nomcom/models.py b/ietf/nomcom/models.py index 6b466dd98..36c295086 100644 --- a/ietf/nomcom/models.py +++ b/ietf/nomcom/models.py @@ -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) diff --git a/ietf/nomcom/tests.py b/ietf/nomcom/tests.py index e81546f04..0520151aa 100644 --- a/ietf/nomcom/tests.py +++ b/ietf/nomcom/tests.py @@ -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()) + diff --git a/ietf/nomcom/utils.py b/ietf/nomcom/utils.py index 084cf7db7..55cdfa4fb 100644 --- a/ietf/nomcom/utils.py +++ b/ietf/nomcom/utils.py @@ -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): diff --git a/ietf/nomcom/views.py b/ietf/nomcom/views.py index f6554cb8c..bafa77d55 100644 --- a/ietf/nomcom/views.py +++ b/ietf/nomcom/views.py @@ -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,11 +363,12 @@ def feedback(request, year, public): has_publickey = nomcom.public_key and True or False nominee = None position = None - selected_nominee = request.GET.get('nominee') - selected_position = request.GET.get('position') - if selected_nominee and selected_position: - nominee = get_object_or_404(Nominee, id=selected_nominee) - position = get_object_or_404(Position, id=selected_position) + if nomcom.group.state_id != 'conclude': + selected_nominee = request.GET.get('nominee') + selected_position = request.GET.get('position') + if selected_nominee and selected_position: + nominee = get_object_or_404(Nominee, id=selected_nominee) + position = get_object_or_404(Position, id=selected_position) positions = Position.objects.get_by_nomcom(nomcom=nomcom).opened() @@ -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) diff --git a/ietf/person/factories.py b/ietf/person/factories.py new file mode 100644 index 000000000..79db6fcff --- /dev/null +++ b/ietf/person/factories.py @@ -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')) diff --git a/ietf/templates/nomcom/feedback.html b/ietf/templates/nomcom/feedback.html index 7334e9362..1a5e70310 100644 --- a/ietf/templates/nomcom/feedback.html +++ b/ietf/templates/nomcom/feedback.html @@ -9,8 +9,12 @@ {% block nomcom_content %} {% origin %} -
- Select a nominee from the list of nominees to the right to obtain a new feedback form. +
+ {% 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 %}
{% if message %} @@ -21,7 +25,7 @@ {% if nomcom|has_publickey %}