diff --git a/ietf/liaisons/accountsREDESIGN.py b/ietf/liaisons/accountsREDESIGN.py index 557a196b0..a33189799 100644 --- a/ietf/liaisons/accountsREDESIGN.py +++ b/ietf/liaisons/accountsREDESIGN.py @@ -1,14 +1,14 @@ from redesign.person.models import Person from redesign.group.models import Role - +from ietf.liaisons.proxy import proxy_personify_role LIAISON_EDIT_GROUPS = ['Secretariat'] # this is not working anymore, refers to old auth model def get_ietf_chair(): try: - return Person.objects.get(email__role__name="chair", email__role__group__acronym="ietf") - except Person.DoesNotExist: + return proxy_personify_role(Role.objects.get(name="chair", group__acronym="ietf")) + except Role.DoesNotExist: return None @@ -18,14 +18,14 @@ def get_iesg_chair(): def get_iab_chair(): try: - return Person.objects.get(email__role__name="chair", email__role__group__acronym="iab") - except Person.DoesNotExist: + return proxy_personify_role(Role.objects.get(name="chair", group__acronym="iab")) + except Role.DoesNotExist: return None def get_iab_executive_director(): try: - return Person.objects.get(email__role__name="execdir", email__role__group__acronym="iab") + return proxy_personify_role(Role.objects.get(name="execdir", group__acronym="iab")) except Person.DoesNotExist: return None diff --git a/ietf/liaisons/feeds.py b/ietf/liaisons/feeds.py index c64357beb..900728bd2 100644 --- a/ietf/liaisons/feeds.py +++ b/ietf/liaisons/feeds.py @@ -1,5 +1,6 @@ # Copyright The IETF Trust 2007, All Rights Reserved +from django.conf import settings from django.contrib.syndication.feeds import Feed, FeedDoesNotExist from django.utils.feedgenerator import Atom1Feed from django.db.models import Q @@ -8,6 +9,9 @@ from ietf.idtracker.models import Acronym from datetime import datetime, time import re +if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from ietf.liaisons.proxy import LiaisonDetailProxy as LiaisonDetail + # A slightly funny feed class, the 'object' is really # just a dict with some parameters that items() uses # to construct a queryset. @@ -24,6 +28,15 @@ class Liaisons(Feed): if bits[0] == 'from': if len(bits) != 2: raise FeedDoesNotExist + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + from redesign.group.models import Group + try: + group = Group.objects.get(acronym=bits[1]) + obj['filter'] = { 'from_group': group } + obj['title'] = u'Liaison Statements from %s' % group.name + return obj + except Group.DoesNotExist: + raise FeedDoesNotExist try: acronym = Acronym.objects.get(acronym=bits[1]) obj['filter'] = {'from_id': acronym.acronym_id} @@ -49,16 +62,22 @@ class Liaisons(Feed): if bits[0] == 'to': if len(bits) != 2: raise FeedDoesNotExist - # The schema uses two different fields for the same - # basic purpose, depending on whether it's a Secretariat-submitted - # or Liaison-tool-submitted document. - obj['q'] = [ (Q(by_secretariat=0) & Q(to_body__icontains=bits[1])) | (Q(by_secretariat=1) & Q(submitter_name__icontains=bits[1])) ] + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + obj['filter'] = dict(to_name__icontains=bits[1]) + else: + # The schema uses two different fields for the same + # basic purpose, depending on whether it's a Secretariat-submitted + # or Liaison-tool-submitted document. + obj['q'] = [ (Q(by_secretariat=0) & Q(to_body__icontains=bits[1])) | (Q(by_secretariat=1) & Q(submitter_name__icontains=bits[1])) ] obj['title'] = 'Liaison Statements where to matches %s' % bits[1] return obj if bits[0] == 'subject': if len(bits) != 2: raise FeedDoesNotExist - obj['q'] = [ Q(title__icontains=bits[1]) | Q(uploads__file_title__icontains=bits[1]) ] + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + obj['q'] = [ Q(title__icontains=bits[1]) | Q(attachments__title__icontains=bits[1]) ] + else: + obj['q'] = [ Q(title__icontains=bits[1]) | Q(uploads__file_title__icontains=bits[1]) ] obj['title'] = 'Liaison Statements where subject matches %s' % bits[1] return obj raise FeedDoesNotExist diff --git a/ietf/liaisons/formsREDESIGN.py b/ietf/liaisons/formsREDESIGN.py index c1b36088b..f4c3b83f3 100644 --- a/ietf/liaisons/formsREDESIGN.py +++ b/ietf/liaisons/formsREDESIGN.py @@ -1,4 +1,4 @@ -import datetime +import datetime, os from email.utils import parseaddr from django import forms @@ -14,11 +14,14 @@ from ietf.liaisons.utils import IETFHM from ietf.liaisons.widgets import (FromWidget, ReadOnlyWidget, ButtonWidget, ShowAttachmentsWidget, RelatedLiaisonWidget) from ietf.liaisons.models import LiaisonStatement, LiaisonStatementPurposeName +from ietf.liaisons.proxy import LiaisonDetailProxy from redesign.group.models import Group from redesign.person.models import Person +from redesign.doc.models import Document class LiaisonForm(forms.Form): + person = forms.ModelChoiceField(Person.objects.all()) from_field = forms.ChoiceField(widget=FromWidget, label=u'From') replyto = forms.CharField(label=u'Reply to') organization = forms.ChoiceField() @@ -64,15 +67,16 @@ class LiaisonForm(forms.Form): self.fake_person = None self.person = get_person_for_user(user) if kwargs.get('data', None): - kwargs['data'].update({'person': self.person.pk}) if is_secretariat(self.user) and 'from_fake_user' in kwargs['data'].keys(): self.fake_person = Person.objects.get(pk=kwargs['data']['from_fake_user']) kwargs['data'].update({'person': self.fake_person.pk}) + else: + kwargs['data'].update({'person': self.person.pk}) self.instance = kwargs.pop("instance", None) super(LiaisonForm, self).__init__(*args, **kwargs) - + # now copy in values from instance, like a ModelForm if self.instance: for name, field in self.fields.iteritems(): @@ -120,8 +124,7 @@ class LiaisonForm(forms.Form): assert NotImplemented def set_replyto_field(self): - email = self.person.email_address() - self.fields['replyto'].initial = email + self.fields['replyto'].initial = self.person.email()[1] def set_organization_field(self): assert NotImplemented @@ -193,7 +196,7 @@ class LiaisonForm(forms.Form): return self.hm.get_entity_by_key(organization_key) def get_poc(self, organization): - return ', '.join([i.email_address() for i in organization.get_poc()]) + return ', '.join(u"%s <%s>" % i.email() for i in organization.get_poc()) def clean_cc1(self): value = self.cleaned_data.get('cc1', '') @@ -223,7 +226,7 @@ class LiaisonForm(forms.Form): def save(self, *args, **kwargs): l = self.instance if not l: - l = LiaisonStatement() + l = LiaisonDetailProxy() l.title = self.cleaned_data["title"] l.purpose = LiaisonStatementPurposeName.objects.get(order=self.cleaned_data["purpose"]) @@ -238,11 +241,11 @@ class LiaisonForm(forms.Form): l.modified = now l.submitted = datetime.datetime.combine(self.cleaned_data["submitted_date"], now.time()) - if self.cleaned_data.get("approval"): # FIXME - if not l.approved: - l.approved = now + if not l.approved: + l.approved = now self.save_extra_fields(l) + l.save() # we have to save here to make sure we get an id for the attachments self.save_attachments(l) @@ -250,23 +253,19 @@ class LiaisonForm(forms.Form): def save_extra_fields(self, liaison): from_entity = self.get_from_entity() - print from_entity, type(from_entity) - print self.cleaned_data.get("from_field") - liason.from_name = from_entity.name - print from_entity.obj # FIXME? c.get("from_field") - liason.from_group = from_entity.obj - liason.from_contact = self.person # FIXME? + liaison.from_name = from_entity.name + liaison.from_group = from_entity.obj + liaison.from_contact = self.cleaned_data["person"].email_address() organization = self.get_to_entity() - liason.to_name = organization.name - print organization.obj # FIXME? self.cleaned_data.get('organization') - liason.to_group = organization.obj - liason.to_contact = self.get_poc(organization) - + liaison.to_name = organization.name + liaison.to_group = organization.obj + liaison.to_contact = self.get_poc(organization) + liaison.cc = self.get_cc(from_entity, organization) def save_attachments(self, instance): - return # FIXME + written = instance.attachments.all().count() for key in self.files.keys(): title_key = key.replace('file', 'title') if not key.startswith('attach_file_') or not title_key in self.data.keys(): @@ -277,13 +276,16 @@ class LiaisonForm(forms.Form): extension = '.' + extension[1] else: extension = '' - attach = Uploads.objects.create( - file_title = self.data.get(title_key), - person = self.person, - detail = instance, - file_extension = extension, + written += 1 + name = instance.name() + ("-attachment-%s" % written) + attach = Document.objects.create( + title = self.data.get(title_key), + type_id = "liaison", + name = name, + external_url = name + extension, # strictly speaking not necessary, but just for the time being ... ) - attach_file = open('%sfile%s%s' % (settings.LIAISON_ATTACH_PATH, attach.pk, attach.file_extension), 'w') + instance.attachments.add(attach) + attach_file = open(os.path.join(settings.LIAISON_ATTACH_PATH, attach.name + extension), 'w') attach_file.write(attached_file.read()) attach_file.close() @@ -380,21 +382,13 @@ class OutgoingLiaisonForm(LiaisonForm): return self.cleaned_data['to_poc'] def save_extra_fields(self, liaison): - raise NotImplemented super(OutgoingLiaisonForm, self).save_extra_fields(liaison) from_entity = self.get_from_entity() needs_approval = from_entity.needs_approval(self.person) if not needs_approval or self.cleaned_data.get('approved', False): - approved = True - approval_date = datetime.datetime.now() + liaison.approved = datetime.datetime.now() else: - approved = False - approval_date = None - approval = OutgoingLiaisonApproval.objects.create( - approved = approved, - approval_date = approval_date) - liaison.approval = approval - liaison.save() + liaison.approved = None def clean_to_poc(self): value = self.cleaned_data.get('to_poc', None) diff --git a/ietf/liaisons/mails.py b/ietf/liaisons/mails.py new file mode 100644 index 000000000..50ab75c3b --- /dev/null +++ b/ietf/liaisons/mails.py @@ -0,0 +1,59 @@ +from django.conf import settings +from django.template.loader import render_to_string +from django.core.urlresolvers import reverse as urlreverse + +from ietf.liaisons.mail import IETFEmailMessage + +def send_liaison_by_email(liaison, fake=False): + if not liaison.is_pending(): # this conditional should definitely be at the caller, not here + return notify_pending_by_email(liaison, fake) + + subject = u'New Liaison Statement, "%s"' % (liaison.title) + from_email = settings.LIAISON_UNIVERSAL_FROM + to_email = liaison.to_poc.split(',') + cc = liaison.cc1.split(',') + if liaison.technical_contact: + cc += liaison.technical_contact.split(',') + if liaison.response_contact: + cc += liaison.response_contact.split(',') + bcc = ['statements@ietf.org'] + body = render_to_string('liaisons/liaison_mail.txt', + {'liaison': liaison, + 'url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=liaison.pk)) + }) + mail = IETFEmailMessage(subject=subject, + to=to_email, + from_email=from_email, + cc = cc, + bcc = bcc, + body = body) + # rather than this fake stuff, it's probably better to start a + # debug SMTP server as explained in the Django docs + if not fake: + mail.send() + return mail + +def notify_pending_by_email(liaison, fake): + from ietf.liaisons.utils import IETFHM + + from_entity = IETFHM.get_entity_by_key(liaison.from_raw_code) + if not from_entity: + return None + to_email = [] + for person in from_entity.can_approve(): + to_email.append('%s <%s>' % person.email()) + subject = u'New Liaison Statement, "%s" needs your approval' % (liaison.title) + from_email = settings.LIAISON_UNIVERSAL_FROM + body = render_to_string('liaisons/pending_liaison_mail.txt', + {'liaison': liaison, + 'url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=liaison.pk)), + 'approve_url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_approval_detail", kwargs=dict(object_id=liaison.pk)) + }) + mail = IETFEmailMessage(subject=subject, + to=to_email, + from_email=from_email, + body = body) + if not fake: + mail.send() + return mail + diff --git a/ietf/liaisons/models.py b/ietf/liaisons/models.py index 0550458b5..fe8152f8c 100644 --- a/ietf/liaisons/models.py +++ b/ietf/liaisons/models.py @@ -5,6 +5,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.template.loader import render_to_string from django.contrib.auth.models import User +from django.core.urlresolvers import reverse as urlreverse from ietf.idtracker.models import Acronym, PersonOrOrgInfo, Area, IESGLogin from ietf.liaisons.mail import IETFEmailMessage from ietf.ietfauth.models import LegacyLiaisonUser @@ -143,6 +144,7 @@ class LiaisonDetail(models.Model): from_email = settings.LIAISON_UNIVERSAL_FROM body = render_to_string('liaisons/pending_liaison_mail.txt', {'liaison': self, + 'url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=liaison.pk)) }) mail = IETFEmailMessage(subject=subject, to=to_email, @@ -166,6 +168,8 @@ class LiaisonDetail(models.Model): bcc = ['statements@ietf.org'] body = render_to_string('liaisons/liaison_mail.txt', {'liaison': self, + 'url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=liaison.pk)), + 'approve_url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_approval_detail", kwargs=dict(object_id=liaison.pk)) }) mail = IETFEmailMessage(subject=subject, to=to_email, @@ -289,6 +293,8 @@ class Uploads(models.Model): detail = models.ForeignKey(LiaisonDetail) def __str__(self): return self.file_title + def filename(self): + return "file%s%s" % (self.file_id, self.file_extension) class Meta: db_table = 'uploads' @@ -320,12 +326,12 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES or hasattr(settings, "IMPORTING_FROM_O related_to = models.ForeignKey('LiaisonStatement', blank=True, null=True) - from_group = models.ForeignKey(Group, related_name="liaisonstatement_from_set", null=True, blank=True, help_text="From body, if it exists") + from_group = models.ForeignKey(Group, related_name="liaisonstatement_from_set", null=True, blank=True, help_text="Sender group, if it exists") from_name = models.CharField(max_length=255, help_text="Name of the sender body") from_contact = models.ForeignKey(Email, blank=True, null=True) - to_group = models.ForeignKey(Group, related_name="liaisonstatement_to_set", null=True, blank=True, help_text="To body, if it exists") + to_group = models.ForeignKey(Group, related_name="liaisonstatement_to_set", null=True, blank=True, help_text="Recipient group, if it exists") to_name = models.CharField(max_length=255, help_text="Name of the recipient body") - to_contact = models.CharField(blank=True, max_length=255, help_text="Contacts at to body") + to_contact = models.CharField(blank=True, max_length=255, help_text="Contacts at recipient body") reply_to = models.CharField(blank=True, max_length=255) diff --git a/ietf/liaisons/proxy.py b/ietf/liaisons/proxy.py index 15413f4bb..2964a308f 100644 --- a/ietf/liaisons/proxy.py +++ b/ietf/liaisons/proxy.py @@ -96,14 +96,22 @@ class LiaisonDetailProxy(LiaisonStatement): @property def from_raw_body(self): return self.from_name + + def raw_codify(self, group): + if not group: + return "" + if group.type_id in ("sdo", "wg", "area"): + return "%s_%s" % (group.type_id, group.id) + return group.acronym + #from_raw_code = models.CharField(blank=True, null=True, max_length=255) @property def from_raw_code(self): - return self.from_group_id + return self.raw_codify(self.from_group) #to_raw_code = models.CharField(blank=True, null=True, max_length=255) @property def to_raw_code(self): - return self.to_body_id + return self.raw_codify(self.to_group) #approval = models.ForeignKey(OutgoingLiaisonApproval, blank=True, null=True) @property def approval(self): @@ -134,54 +142,10 @@ class LiaisonDetailProxy(LiaisonStatement): class Meta: proxy = True - def notify_pending_by_email(self, fake): - raise NotImplemented - from ietf.liaisons.utils import IETFHM - - from_entity = IETFHM.get_entity_by_key(self.from_raw_code) - if not from_entity: - return None - to_email = [] - for person in from_entity.can_approve(): - to_email.append('%s <%s>' % person.email()) - subject = 'New Liaison Statement, "%s" needs your approval' % (self.title) - from_email = settings.LIAISON_UNIVERSAL_FROM - body = render_to_string('liaisons/pending_liaison_mail.txt', - {'liaison': self, - }) - mail = IETFEmailMessage(subject=subject, - to=to_email, - from_email=from_email, - body = body) - if not fake: - mail.send() - return mail - def send_by_email(self, fake=False): - raise NotImplemented - if self.is_pending(): - return self.notify_pending_by_email(fake) - subject = 'New Liaison Statement, "%s"' % (self.title) - from_email = settings.LIAISON_UNIVERSAL_FROM - to_email = self.to_poc.split(',') - cc = self.cc1.split(',') - if self.technical_contact: - cc += self.technical_contact.split(',') - if self.response_contact: - cc += self.response_contact.split(',') - bcc = ['statements@ietf.org'] - body = render_to_string('liaisons/liaison_mail.txt', - {'liaison': self, - }) - mail = IETFEmailMessage(subject=subject, - to=to_email, - from_email=from_email, - cc = cc, - bcc = bcc, - body = body) - if not fake: - mail.send() - return mail + # grab this from module instead of stuffing in on the model + from ietf.liaisons.mails import send_liaison_by_email + return send_liaison_by_email(self, fake) def is_pending(self): return not self.approved @@ -190,7 +154,10 @@ class UploadsProxy(Document): #file_id = models.AutoField(primary_key=True) @property def file_id(self): - return int(self.external_url.split(".")[0]) + if self.external_url.startswith(self.name): + return self.name # new data + else: + return int(self.external_url.split(".")[0][len(file):]) # old data #file_title = models.CharField(blank=True, max_length=255) @property def file_title(self): @@ -208,6 +175,14 @@ class UploadsProxy(Document): @property def detail(self): return self.liaisonstatement_set.all()[0] - + def filename(self): + return self.external_url class Meta: proxy = True + +def proxy_personify_role(role): + """Turn role into person with email() method using email from role.""" + p = role.email.person + p.email = lambda: (p.name, role.email.address) + return p + diff --git a/ietf/liaisons/tests.py b/ietf/liaisons/tests.py index 78cd9c43d..734466747 100644 --- a/ietf/liaisons/tests.py +++ b/ietf/liaisons/tests.py @@ -1,9 +1,10 @@ -import datetime +import datetime, os, shutil from django.conf import settings from django.contrib.auth.models import User from django.core.urlresolvers import reverse as urlreverse import django.test +from StringIO import StringIO from pyquery import PyQuery from ietf.utils.test_utils import SimpleUrlTestCase, canonicalize_feed, canonicalize_sitemap, login_testing_unauthorized @@ -76,6 +77,15 @@ def make_liaison_models(): class LiaisonManagementTestCase(django.test.TestCase): fixtures = ['names'] + def setUp(self): + self.liaison_dir = os.path.abspath("tmp-liaison-dir") + os.mkdir(self.liaison_dir) + + settings.LIAISON_ATTACH_PATH = self.liaison_dir + + def tearDown(self): + shutil.rmtree(self.liaison_dir) + def test_taken_care_of(self): make_test_data() liaison = make_liaison_models() @@ -96,13 +106,44 @@ class LiaisonManagementTestCase(django.test.TestCase): self.assertEquals(len(q('form input[name=do_action_taken]')), 1) # mark action taken - r = self.client.post(url, dict(do_action_taken=1)) + r = self.client.post(url, dict(do_action_taken="1")) self.assertEquals(r.status_code, 200) q = PyQuery(r.content) self.assertEquals(len(q('form input[name=do_action_taken]')), 0) liaison = LiaisonStatement.objects.get(id=liaison.id) self.assertTrue(liaison.action_taken) + def test_approval(self): + make_test_data() + liaison = make_liaison_models() + # has to come from WG to need approval + liaison.from_group = Group.objects.get(acronym="mars") + liaison.approved = None + liaison.save() + + url = urlreverse('liaison_approval_detail', kwargs=dict(object_id=liaison.pk)) + # this liaison is for a WG so we need the AD for the area + login_testing_unauthorized(self, "ad", url) + + # normal get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('form input[name=do_approval]')), 1) + + # approve + mailbox_before = len(mail_outbox) + r = self.client.post(url, dict(do_approval="1")) + self.assertEquals(r.status_code, 302) + + liaison = LiaisonStatement.objects.get(id=liaison.id) + self.assertTrue(liaison.approved) + from django.core import mail + self.assertEqual(len(mail.outbox), 1) + self.assertTrue("Liaison Statement" in mail.outbox[-1].subject) + #self.assertEquals(len(mail_outbox), mailbox_before + 1) + #self.assertTrue("Liaison Statement" in mail_outbox[-1]["Subject"]) + def test_edit_liaison(self): make_test_data() liaison = make_liaison_models() @@ -117,6 +158,9 @@ class LiaisonManagementTestCase(django.test.TestCase): self.assertEquals(len(q('form input[name=from_field]')), 1) # edit + attachments_before = liaison.attachments.count() + test_file = StringIO("hello world") + test_file.name = "unnamed" r = self.client.post(url, dict(from_field="from", replyto="replyto@example.com", @@ -124,14 +168,17 @@ class LiaisonManagementTestCase(django.test.TestCase): to_poc="to_poc@example.com", response_contact="responce_contact@example.com", technical_contact="technical_contact@example.com", - cc1="cc1@example.com", + cc1="cc@example.com", purpose="4", deadline_date=(liaison.deadline + datetime.timedelta(days=1)).strftime("%Y-%m-%d"), title="title", submitted_date=(liaison.submitted + datetime.timedelta(days=1)).strftime("%Y-%m-%d"), body="body", + attach_file_1=test_file, + attach_title_1="attachment", )) self.assertEquals(r.status_code, 302) + new_liaison = LiaisonStatement.objects.get(id=liaison.id) self.assertEquals(new_liaison.from_name, "from") self.assertEquals(new_liaison.reply_to, "replyto@example.com") @@ -139,7 +186,7 @@ class LiaisonManagementTestCase(django.test.TestCase): self.assertEquals(new_liaison.to_contact, "to_poc@example.com") self.assertEquals(new_liaison.response_contact, "responce_contact@example.com") self.assertEquals(new_liaison.technical_contact, "technical_contact@example.com") - self.assertEquals(new_liaison.cc, "cc1@example.com") + self.assertEquals(new_liaison.cc, "cc@example.com") self.assertEquals(new_liaison.purpose, LiaisonStatementPurposeName.objects.get(order=4)) self.assertEquals(new_liaison.deadline, liaison.deadline + datetime.timedelta(days=1)), self.assertEquals(new_liaison.title, "title") @@ -147,7 +194,174 @@ class LiaisonManagementTestCase(django.test.TestCase): self.assertEquals(new_liaison.body, "body") self.assertTrue(new_liaison.modified > liaison.modified) - # test links and edit button + self.assertEquals(new_liaison.attachments.count(), attachments_before + 1) + attachment = new_liaison.attachments.order_by("-name")[0] + self.assertEquals(attachment.title, "attachment") + with open(os.path.join(self.liaison_dir, attachment.external_url)) as f: + written_content = f.read() + + test_file.seek(0) + self.assertEquals(written_content, test_file.read()) + + def test_add_incoming_liaison(self): + make_test_data() + liaison = make_liaison_models() + + url = urlreverse('add_liaison') + "?incoming=1" + login_testing_unauthorized(self, "secretary", url) + + # get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('form textarea[name=body]')), 1) + + # add new + test_file = StringIO("hello world") + test_file.name = "unnamed" + from_group = Group.objects.filter(type="sdo")[0] + to_group = Group.objects.get(acronym="mars") + submitter = Person.objects.get(user__username="marschairman") + today = datetime.date.today() + related_liaison = liaison + r = self.client.post(url, + dict(from_field="%s_%s" % (from_group.type_id, from_group.pk), + from_fake_user=str(submitter.pk), + replyto="replyto@example.com", + organization="%s_%s" % (to_group.type_id, to_group.pk), + response_contact="responce_contact@example.com", + technical_contact="technical_contact@example.com", + cc1="cc@example.com", + purpose="4", + deadline_date=(today + datetime.timedelta(days=1)).strftime("%Y-%m-%d"), + related_to=str(related_liaison.pk), + title="title", + submitted_date=today.strftime("%Y-%m-%d"), + body="body", + attach_file_1=test_file, + attach_title_1="attachment", + )) + self.assertEquals(r.status_code, 302) + + l = LiaisonStatement.objects.all().order_by("-id")[0] + self.assertEquals(l.from_group, from_group) + self.assertEquals(l.from_contact, submitter.email_address()) + self.assertEquals(l.reply_to, "replyto@example.com") + self.assertEquals(l.to_group, to_group) + self.assertEquals(l.response_contact, "responce_contact@example.com") + self.assertEquals(l.technical_contact, "technical_contact@example.com") + self.assertEquals(l.cc, "cc@example.com") + self.assertEquals(l.purpose, LiaisonStatementPurposeName.objects.get(order=4)) + self.assertEquals(l.deadline, today + datetime.timedelta(days=1)), + self.assertEquals(l.related_to, liaison), + self.assertEquals(l.title, "title") + self.assertEquals(l.submitted.date(), today) + self.assertEquals(l.body, "body") + self.assertTrue(l.approved) + + self.assertEquals(l.attachments.count(), 1) + attachment = l.attachments.all()[0] + self.assertEquals(attachment.title, "attachment") + with open(os.path.join(self.liaison_dir, attachment.external_url)) as f: + written_content = f.read() + + test_file.seek(0) + self.assertEquals(written_content, test_file.read()) + + def test_add_outgoing_liaison(self): + make_test_data() + liaison = make_liaison_models() + + url = urlreverse('add_liaison') + login_testing_unauthorized(self, "secretary", url) + + # get + r = self.client.get(url) + self.assertEquals(r.status_code, 200) + q = PyQuery(r.content) + self.assertEquals(len(q('form textarea[name=body]')), 1) + + # add new + test_file = StringIO("hello world") + test_file.name = "unnamed" + from_group = Group.objects.get(acronym="mars") + to_group = Group.objects.filter(type="sdo")[0] + submitter = Person.objects.get(user__username="marschairman") + today = datetime.date.today() + related_liaison = liaison + r = self.client.post(url, + dict(from_field="%s_%s" % (from_group.type_id, from_group.pk), + from_fake_user=str(submitter.pk), + approved="", + replyto="replyto@example.com", + to_poc="to_poc@example.com", + organization="%s_%s" % (to_group.type_id, to_group.pk), + other_organization="", + response_contact="responce_contact@example.com", + technical_contact="technical_contact@example.com", + cc1="cc@example.com", + purpose="4", + deadline_date=(today + datetime.timedelta(days=1)).strftime("%Y-%m-%d"), + related_to=str(related_liaison.pk), + title="title", + submitted_date=today.strftime("%Y-%m-%d"), + body="body", + attach_file_1=test_file, + attach_title_1="attachment", + )) + self.assertEquals(r.status_code, 302) + + l = LiaisonStatement.objects.all().order_by("-id")[0] + self.assertEquals(l.from_group, from_group) + self.assertEquals(l.from_contact, submitter.email_address()) + self.assertEquals(l.reply_to, "replyto@example.com") + self.assertEquals(l.to_group, to_group) + self.assertEquals(l.to_contact, "to_poc@example.com") + self.assertEquals(l.response_contact, "responce_contact@example.com") + self.assertEquals(l.technical_contact, "technical_contact@example.com") + self.assertEquals(l.cc, "cc@example.com") + self.assertEquals(l.purpose, LiaisonStatementPurposeName.objects.get(order=4)) + self.assertEquals(l.deadline, today + datetime.timedelta(days=1)), + self.assertEquals(l.related_to, liaison), + self.assertEquals(l.title, "title") + self.assertEquals(l.submitted.date(), today) + self.assertEquals(l.body, "body") + self.assertTrue(not l.approved) + + self.assertEquals(l.attachments.count(), 1) + attachment = l.attachments.all()[0] + self.assertEquals(attachment.title, "attachment") + with open(os.path.join(self.liaison_dir, attachment.external_url)) as f: + written_content = f.read() + + test_file.seek(0) + self.assertEquals(written_content, test_file.read()) + + # try adding statement to non-predefined organization + r = self.client.post(url, + dict(from_field="%s_%s" % (from_group.type_id, from_group.pk), + from_fake_user=str(submitter.pk), + approved="1", + replyto="replyto@example.com", + to_poc="to_poc@example.com", + organization="othersdo", + other_organization="Mars Institute", + response_contact="responce_contact@example.com", + technical_contact="technical_contact@example.com", + cc1="cc@example.com", + purpose="4", + deadline_date=(today + datetime.timedelta(days=1)).strftime("%Y-%m-%d"), + related_to=str(related_liaison.pk), + title="new title", + submitted_date=today.strftime("%Y-%m-%d"), + body="body", + )) + self.assertEquals(r.status_code, 302) + + l = LiaisonStatement.objects.all().order_by("-id")[0] + self.assertEquals(l.to_group, None) + self.assertEquals(l.to_name, "Mars Institute") + if not settings.USE_DB_REDESIGN_PROXY_CLASSES: # the above tests only work with the new schema diff --git a/ietf/liaisons/utilsREDESIGN.py b/ietf/liaisons/utilsREDESIGN.py index 366a366a7..ce9cf713e 100644 --- a/ietf/liaisons/utilsREDESIGN.py +++ b/ietf/liaisons/utilsREDESIGN.py @@ -1,3 +1,7 @@ +from redesign.group.models import Group, Role +from redesign.person.models import Person +from ietf.liaisons.proxy import proxy_personify_role + from ietf.liaisons.accounts import (is_ietfchair, is_iabchair, is_iab_executive_director, get_ietf_chair, get_iab_chair, get_iab_executive_director, is_secretariat) @@ -9,10 +13,6 @@ IABCHAIR = {'name': u'The IAB Chair', 'address': u'iab-chair@iab.org'} IABEXECUTIVEDIRECTOR = {'name': u'The IAB Executive Director', 'address': u'execd@iab.org'} -def get_all_sdo_managers(): - return list(Person.objects.filter(email__role__name="liaiman", email__role__group__state="active").distinct()) - - class FakePerson(object): def __init__(self, name, address): @@ -28,15 +28,11 @@ class FakePerson(object): # schema) way - unfortunately, it's never been strong enough to do so # fine-grained enough so the form code also has some rules +def all_sdo_managers(): + return [proxy_personify_role(r) for r in Role.objects.filter(group__type="sdo", name="liaiman").select_related("email").distinct()] + def role_persons_with_fixed_email(group, role_name): - from redesign.group.models import Role - res = [] - for r in Role.objects.filter(group=group, name=role_name).select_related("email"): - p = r.email.person - # proxy hack to make email() return the right address - p.email = (lambda name, address: (lambda: (name, address)))(p.name, r.email.address) - res.append(p) - return res + return [proxy_personify_role(r) for r in Role.objects.filter(group=group, name=role_name).select_related("email").distinct()] class Entity(object): @@ -94,7 +90,7 @@ class IETFEntity(Entity): return [self.poc] def full_user_list(self): - result = get_all_sdo_managers() + result = all_sdo_managers() result.append(get_ietf_chair()) return result @@ -123,7 +119,7 @@ class IABEntity(Entity): return [self.chair] def full_user_list(self): - result = get_all_sdo_managers() + result = all_sdo_managers() result += [get_iab_chair(), get_iab_executive_director()] return result @@ -131,7 +127,7 @@ class IABEntity(Entity): class AreaEntity(Entity): def get_poc(self): - return [i.person for i in self.obj.areadirector_set.all()] + return role_persons_with_fixed_email(self.obj, "ad") def get_cc(self, person=None): return [FakePerson(**IETFCHAIR)] @@ -151,7 +147,7 @@ class AreaEntity(Entity): return self.get_poc() def full_user_list(self): - result = get_all_sdo_managers() + result = all_sdo_managers() result += self.get_poc() return result @@ -189,7 +185,7 @@ class WGEntity(Entity): return role_persons_with_fixed_email(self.obj.parent, "ad") if self.obj.parent else [] def full_user_list(self): - result = get_all_sdo_managers() + result = all_sdo_managers() result += self.get_poc() return result @@ -332,8 +328,7 @@ class WGEntityManager(EntityManager): return WGEntity(name=obj.group_acronym.name, obj=obj) def can_send_on_behalf(self, person): - wgs = set([i.group_acronym.pk for i in person.wgchair_set.all()]) - wgs = wgs.union([i.group_acronym.pk for i in person.wgsecretary_set.all()]) + wgs = Group.objects.filter(role__email__person=person, role__name__in=("chair", "secretary")).values_list('pk', flat=True) query_filter = {'pk__in': wgs} return self.get_managed_list(query_filter) @@ -347,7 +342,6 @@ class SDOEntityManager(EntityManager): def __init__(self, pk=None, name=None, queryset=None): super(SDOEntityManager, self).__init__(pk, name, queryset) if self.queryset == None: - from redesign.group.models import Group self.queryset = Group.objects.filter(type="sdo") def get_managed_list(self): diff --git a/ietf/liaisons/views.py b/ietf/liaisons/views.py index 37a230fc5..62d87db62 100644 --- a/ietf/liaisons/views.py +++ b/ietf/liaisons/views.py @@ -177,19 +177,26 @@ def liaison_approval_list(request): def liaison_approval_detail(request, object_id): person = get_person_for_user(request.user) approval_codes = IETFHM.get_all_can_approve_codes(person) - to_approve = LiaisonDetail.objects.filter(approval__isnull=False, approval__approved=False, from_raw_code__in=approval_codes).order_by("-submitted_date") + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + to_approve = approvable_liaison_statements(approval_codes).order_by("-submitted") + else: + to_approve = LiaisonDetail.objects.filter(approval__isnull=False, approval__approved=False, from_raw_code__in=approval_codes).order_by("-submitted_date") if request.method=='POST' and request.POST.get('do_approval', False): try: liaison = to_approve.get(pk=object_id) - approval = liaison.approval - if not approval: - approval = OutgoingLiaisonApproval.objects.create(approved=True, approval_date=datetime.datetime.now()) - liaison.approval = approval + if settings.USE_DB_REDESIGN_PROXY_CLASSES: + liaison.approved = datetime.datetime.now() liaison.save() else: - approval.approved=True - approval.save() + approval = liaison.approval + if not approval: + approval = OutgoingLiaisonApproval.objects.create(approved=True, approval_date=datetime.datetime.now()) + liaison.approval = approval + liaison.save() + else: + approval.approved=True + approval.save() if not settings.DEBUG: liaison.send_by_email() else: diff --git a/ietf/templates/liaisons/liaison_deadline_mail.txt b/ietf/templates/liaisons/liaison_deadline_mail.txt index 2b9fabd14..fe5f054dd 100644 --- a/ietf/templates/liaisons/liaison_deadline_mail.txt +++ b/ietf/templates/liaisons/liaison_deadline_mail.txt @@ -14,7 +14,7 @@ Purpose: {% if liaison.purpose_text %}{{ liaison.purpose_text }}{% else %}{{ lia Body: {{ liaison.body }} Attachment(s): {% for file in liaison.uploads_set.all %} - {{ file.file_title }} https://datatracker.ietf.org/documents/LIAISON/file{{ file.file_id }}{{ file.file_extension }} + {{ file.file_title }} https://datatracker.ietf.org/documents/LIAISON/file{{ file.filename }} {% empty %} No document has been attached {% endfor %} diff --git a/ietf/templates/liaisons/liaison_mail.txt b/ietf/templates/liaisons/liaison_mail.txt index 657bceeeb..b280f8f14 100644 --- a/ietf/templates/liaisons/liaison_mail.txt +++ b/ietf/templates/liaisons/liaison_mail.txt @@ -1,18 +1,18 @@ {% autoescape off %}Title: {{ liaison.title }} Submission Date: {{ liaison.submitted_date }} -URL of the IETF Web page: {% url liaison_detail object_id=liaison.pk %} +URL of the IETF Web page: {{ url }} {% if liaison.deadline_date %}Please reply by {{ liaison.deadline_date }}{% endif %} From: {{ liaison.from_body }} ({{ liaison.person }} <{{ liaison.replyto|default:liaison.from_email }}>) To: {{ liaison.to_body }} ({{ liaison.to_poc }}) Cc: {{ liaison.cc1 }} Reponse Contact: {{ liaison.response_contact }} Technical Contact: {{ liaison.technical_contact }} -Purpose: {% if liaison.purpose_text %}{{ liaison.purpose_text }}{% else %}{{ liaison.purpose.purpose_text }}{% endif %} +Purpose: {% if liaison.purpose_text %}{{ liaison.purpose_text }}{% else %}{{ liaison.purpose }}{% endif %} {% if liaison.related_to %}Referenced liaison: {% if liaison.related_to.title %}{{ liaison.related_to.title }}{% else %}Liaison #{{ liaison.related_to.pk }}{% endif %} ({% url liaison_detail object_id=liaison.related_to.pk %}){% endif %} Body: {{ liaison.body }} -Attachment(s): +Attachments: {% for file in liaison.uploads_set.all %} - {{ file.file_title }} https://datatracker.ietf.org/documents/LIAISON/file{{ file.file_id }}{{ file.file_extension }} + {{ file.file_title }} https://datatracker.ietf.org/documents/LIAISON/{{ file.filename }} {% empty %} No document has been attached {% endfor %} diff --git a/ietf/templates/liaisons/liaisondetail_simple_list.html b/ietf/templates/liaisons/liaisondetail_simple_list.html index 41fd04e46..f93088f2a 100644 --- a/ietf/templates/liaisons/liaisondetail_simple_list.html +++ b/ietf/templates/liaisons/liaisondetail_simple_list.html @@ -38,7 +38,7 @@ {% if liaison.by_secretariat %} {% for file in liaison.uploads_set.all %} - {{ file.file_title|escape }}
+ {{ file.file_title|escape }}
{% endfor %} {% else %} {{ liaison.title|escape }} diff --git a/ietf/templates/liaisons/pending_liaison_mail.txt b/ietf/templates/liaisons/pending_liaison_mail.txt index b21676619..cbe27f2c3 100644 --- a/ietf/templates/liaisons/pending_liaison_mail.txt +++ b/ietf/templates/liaisons/pending_liaison_mail.txt @@ -2,22 +2,22 @@ The following liaison statement will remain pending (and not public available) i {% load ietf_filters %}{% autoescape off %} Title: {{ liaison.title|clean_whitespace }} Submission Date: {{ liaison.submitted_date }} -URL of the IETF Web page: {% url liaison_approval_detail liaison.pk %} +URL of the IETF Web page: {{ url }} {% if liaison.deadline_date %}Please reply by {{ liaison.deadline_date }}{% endif %} From: {{ liaison.from_body }} ({{ liaison.person }} <{{ liaison.replyto|default:liaison.from_email }}>) To: {{ liaison.to_body }} ({{ liaison.to_poc }}) Cc: {{ liaison.cc1 }} Reponse Contact: {{ liaison.response_contact }} Technical Contact: {{ liaison.technical_contact }} -Purpose: {% if liaison.purpose_text %}{{ liaison.purpose_text }}{% else %}{{ liaison.purpose.purpose_text }}{% endif %} +Purpose: {% if liaison.purpose_text %}{{ liaison.purpose_text }}{% else %}{{ liaison.purpose }}{% endif %} {% if liaison.related_to %}Referenced liaison: {% if liaison.related_to.title %}{{ liaison.related_to.title }}{% else %}Liaison #{{ liaison.related_to.pk }}{% endif %} ({% url liaison_detail object_id=liaison.related_to.pk %}){% endif %} Body: {{ liaison.body }} -Attachment(s): +Attachments: {% for file in liaison.uploads_set.all %} - {{ file.file_title }} https://datatracker.ietf.org/documents/LIAISON/file{{ file.file_id }}{{ file.file_extension }} + {{ file.file_title }} https://datatracker.ietf.org/documents/LIAISON/{{ file.filename }} {% empty %} No document has been attached {% endfor %} {% endautoescape %} -Please visit {% url liaison_approval_detail liaison.pk %} in order to approve the liaison statement. +Please visit {{ approve_url }} in order to approve the liaison statement. diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py index 4e2f5fa41..b16935277 100644 --- a/ietf/utils/test_data.py +++ b/ietf/utils/test_data.py @@ -76,9 +76,11 @@ def make_test_data(): email=email) # group chair + u = User.objects.create(username="marschairman") p = Person.objects.create( name="WG Chair Man", ascii="WG Chair Man", + user=u ) wgchair = Email.objects.create( address="wgchairman@ietf.org", diff --git a/redesign/group/proxy.py b/redesign/group/proxy.py index f97786513..1084c7e2f 100644 --- a/redesign/group/proxy.py +++ b/redesign/group/proxy.py @@ -17,7 +17,7 @@ class Acronym(Group): #acronym_id = models.AutoField(primary_key=True) @property def acronym_id(self): - raise NotImplemented + return self.id #acronym = models.CharField(max_length=12) # same name #name = models.CharField(max_length=100) # same name #name_key = models.CharField(max_length=50, editable=False) diff --git a/redesign/importing/import-liaison.py b/redesign/importing/import-liaison.py index 3ebb3ec3b..ceb42a4da 100755 --- a/redesign/importing/import-liaison.py +++ b/redesign/importing/import-liaison.py @@ -83,9 +83,11 @@ def get_body(name, raw_code): t = raw_code.split("_") if len(t) == 2: if t[0] == "area": - b = lookup_group(acronym=Acronym.objects.get(pk=t[1]), type="area") - elif t[0] == "group": - b = lookup_group(acronym=Acronym.objects.get(pk=t[1]), type="wg") + b = lookup_group(acronym=Acronym.objects.get(pk=t[1]).acronym, type="area") + elif t[0] == "wg": + b = lookup_group(acronym=Acronym.objects.get(pk=t[1]).acronym, type="wg") + elif t[0] == "sdo": + b = lookup_group(name=SDOs.objects.get(pk=t[1]).name, type="sdo") if not b: b = lookup_group(acronym=raw_code) @@ -190,7 +192,7 @@ for o in LiaisonDetail.objects.all().order_by("pk"): attachment.name = l.name() + ("-attachment-%s" % (i + 1)) attachment.time = l.submitted # we should fixup the filenames, but meanwhile, store it here - attachment.external_url = "%s%s" % (u.file_id, u.file_extension) + attachment.external_url = "file%s%s" % (u.file_id, u.file_extension) attachment.save() DocAlias.objects.get_or_create(document=attachment, name=attachment.name)