From 4250a95556dbd42cb691b67ffaf9339b370ef093 Mon Sep 17 00:00:00 2001 From: Ole Laursen Date: Thu, 5 Dec 2013 13:54:48 +0000 Subject: [PATCH] Remove proxy layer from liaisons tool, do some minor cleanups of duplicated code, make sure the custom form widgets escape their input. There's still a bit of custom proxying going on in the IETFHM class hierarchy. - Legacy-Id: 6794 --- ietf/liaisons/__init__.py | 2 +- ietf/liaisons/accounts.py | 114 ++-- ietf/liaisons/accountsREDESIGN.py | 140 ----- ietf/liaisons/admin.py | 7 - ietf/liaisons/decorators.py | 5 - ietf/liaisons/feeds.py | 113 ++-- ietf/liaisons/forms.py | 229 ++++---- ietf/liaisons/formsREDESIGN.py | 474 ---------------- ietf/liaisons/mails.py | 11 +- .../commands/check_liaison_deadlines.py | 75 +-- .../commands/remind_update_sdo_list.py | 48 +- ietf/liaisons/models.py | 391 ++----------- ietf/liaisons/proxy.py | 188 ------- ietf/liaisons/sitemaps.py | 13 +- ietf/liaisons/tests.py | 250 +++++---- ietf/liaisons/testurl.list | 33 -- ietf/liaisons/urls.py | 11 +- ietf/liaisons/utils.py | 233 +++++--- ietf/liaisons/utilsREDESIGN.py | 524 ------------------ ietf/liaisons/views.py | 271 +++------ ietf/liaisons/widgets.py | 59 +- ietf/templates/liaisons/approval_detail.html | 10 + ..._approval_list.html => approval_list.html} | 2 +- ietf/templates/liaisons/detail.html | 120 ++++ ietf/templates/liaisons/edit.html | 32 ++ ietf/templates/liaisons/liaison_mail.txt | 20 +- .../liaisons/liaison_mail_detail.html | 31 -- .../liaisons/liaison_main_management.html | 62 --- ietf/templates/liaisons/liaison_table.html | 39 ++ ietf/templates/liaisons/liaison_title.html | 6 +- .../liaisondetail_approval_detail.html | 10 - .../liaisons/liaisondetail_detail.html | 110 ---- .../liaisons/liaisondetail_edit.html | 31 -- .../liaisons/liaisondetail_simple_list.html | 51 -- ...{liaisondetail_list.html => overview.html} | 4 +- static/css/liaisons.css | 29 +- 36 files changed, 940 insertions(+), 2808 deletions(-) delete mode 100644 ietf/liaisons/accountsREDESIGN.py delete mode 100644 ietf/liaisons/decorators.py delete mode 100644 ietf/liaisons/formsREDESIGN.py delete mode 100644 ietf/liaisons/proxy.py delete mode 100644 ietf/liaisons/testurl.list delete mode 100644 ietf/liaisons/utilsREDESIGN.py create mode 100644 ietf/templates/liaisons/approval_detail.html rename ietf/templates/liaisons/{liaisondetail_approval_list.html => approval_list.html} (86%) create mode 100644 ietf/templates/liaisons/detail.html create mode 100644 ietf/templates/liaisons/edit.html delete mode 100644 ietf/templates/liaisons/liaison_mail_detail.html delete mode 100644 ietf/templates/liaisons/liaison_main_management.html create mode 100644 ietf/templates/liaisons/liaison_table.html delete mode 100644 ietf/templates/liaisons/liaisondetail_approval_detail.html delete mode 100644 ietf/templates/liaisons/liaisondetail_detail.html delete mode 100644 ietf/templates/liaisons/liaisondetail_edit.html delete mode 100644 ietf/templates/liaisons/liaisondetail_simple_list.html rename ietf/templates/liaisons/{liaisondetail_list.html => overview.html} (72%) diff --git a/ietf/liaisons/__init__.py b/ietf/liaisons/__init__.py index 3f64c6e2b..aeccae32c 100644 --- a/ietf/liaisons/__init__.py +++ b/ietf/liaisons/__init__.py @@ -2,7 +2,7 @@ # coding: latin-1 from types import ModuleType -import urls, models, views, forms, accounts, admin, utils, widgets, decorators, sitemaps, feeds +import urls, models, views, forms, admin, utils, widgets, sitemaps, feeds # These people will be sent a stack trace if there's an uncaught exception in # code any of the modules imported above: diff --git a/ietf/liaisons/accounts.py b/ietf/liaisons/accounts.py index 144c0bd53..fc3fffa07 100644 --- a/ietf/liaisons/accounts.py +++ b/ietf/liaisons/accounts.py @@ -1,14 +1,15 @@ -from django.conf import settings +from ietf.person.models import Person +from ietf.group.models import Role +from ietf.utils.proxy import proxy_personify_role -from ietf.idtracker.models import Role, PersonOrOrgInfo - - -LIAISON_EDIT_GROUPS = ['Secretariat'] +LIAISON_EDIT_GROUPS = ['Secretariat'] # this is not working anymore, refers to old auth model def get_ietf_chair(): - person = PersonOrOrgInfo.objects.filter(role=Role.IETF_CHAIR) - return person and person[0] or None + try: + return proxy_personify_role(Role.objects.get(name="chair", group__acronym="ietf")) + except Role.DoesNotExist: + return None def get_iesg_chair(): @@ -16,48 +17,62 @@ def get_iesg_chair(): def get_iab_chair(): - person = PersonOrOrgInfo.objects.filter(role=Role.IAB_CHAIR) - return person and person[0] or None - - -def get_iab_executive_director(): - person = PersonOrOrgInfo.objects.filter(role=Role.IAB_EXCUTIVE_DIRECTOR) - return person and person[0] or None - - -def get_person_for_user(user): try: - return user.get_profile().person() - except: + return proxy_personify_role(Role.objects.get(name="chair", group__acronym="iab")) + except Role.DoesNotExist: return None +def get_irtf_chair(): + try: + return proxy_personify_role(Role.objects.get(name="chair", group__acronym="irtf")) + except Role.DoesNotExist: + return None + + +def get_iab_executive_director(): + try: + return proxy_personify_role(Role.objects.get(name="execdir", group__acronym="iab")) + except Person.DoesNotExist: + return None + + +def get_person_for_user(user): + if not user.is_authenticated(): + return None + try: + p = user.get_profile() + p.email = lambda: (p.plain_name(), p.email_address()) + return p + except Person.DoesNotExist: + return None + def is_areadirector(person): - return bool(person.areadirector_set.all()) + return bool(Role.objects.filter(person=person, name="ad", group__state="active", group__type="area")) def is_wgchair(person): - return bool(person.wgchair_set.all()) + return bool(Role.objects.filter(person=person, name="chair", group__state="active", group__type="wg")) def is_wgsecretary(person): - return bool(person.wgsecretary_set.all()) - - -def has_role(person, role): - return bool(person.role_set.filter(pk=role)) + return bool(Role.objects.filter(person=person, name="sec", group__state="active", group__type="wg")) def is_ietfchair(person): - return has_role(person, Role.IETF_CHAIR) + return bool(Role.objects.filter(person=person, name="chair", group__acronym="ietf")) def is_iabchair(person): - return has_role(person, Role.IAB_CHAIR) + return bool(Role.objects.filter(person=person, name="chair", group__acronym="iab")) def is_iab_executive_director(person): - return has_role(person, Role.IAB_EXCUTIVE_DIRECTOR) + return bool(Role.objects.filter(person=person, name="execdir", group__acronym="iab")) + + +def is_irtfchair(person): + return bool(Role.objects.filter(person=person, name="chair", group__acronym="irtf")) def can_add_outgoing_liaison(user): @@ -74,15 +89,17 @@ def can_add_outgoing_liaison(user): def is_sdo_liaison_manager(person): - return bool(person.liaisonmanagers_set.all()) + return bool(Role.objects.filter(person=person, name="liaiman", group__type="sdo")) def is_sdo_authorized_individual(person): - return bool(person.sdoauthorizedindividual_set.all()) + return bool(Role.objects.filter(person=person, name="auth", group__type="sdo")) def is_secretariat(user): - return bool(user.groups.filter(name='Secretariat')) + if isinstance(user, basestring): + return False + return user.is_authenticated() and bool(Role.objects.filter(person__user=user, name="secr", group__acronym="secretariat")) def can_add_incoming_liaison(user): @@ -102,36 +119,14 @@ def can_add_liaison(user): def is_sdo_manager_for_outgoing_liaison(person, liaison): - from ietf.liaisons.utils import IETFHM, SDOEntity - from ietf.liaisons.models import SDOs - from_entity = IETFHM.get_entity_by_key(liaison.from_raw_code) - sdo = None - if not from_entity: - try: - sdo = SDOs.objects.get(sdo_name=liaison.from_body()) - except SDOs.DoesNotExist: - pass - elif isinstance(from_entity, SDOEntity): - sdo = from_entity.obj - if sdo: - return bool(sdo.liaisonmanagers_set.filter(person=person)) + if liaison.from_group and liaison.from_group.type_id == "sdo": + return bool(liaison.from_group.role_set.filter(name="liaiman", person=person)) return False def is_sdo_manager_for_incoming_liaison(person, liaison): - from ietf.liaisons.utils import IETFHM, SDOEntity - from ietf.liaisons.models import SDOs - to_entity = IETFHM.get_entity_by_key(liaison.to_raw_code) - sdo = None - if not to_entity: - try: - sdo = SDOs.objects.get(sdo_name=liaison.to_body) - except SDOs.DoesNotExist: - pass - elif isinstance(to_entity, SDOEntity): - sdo = to_entity.obj - if sdo: - return bool(sdo.liaisonmanagers_set.filter(person=person)) + if liaison.to_group and liaison.to_group.type_id == "sdo": + return bool(liaison.to_group.role_set.filter(name="liaiman", person=person)) return False @@ -143,6 +138,3 @@ def can_edit_liaison(user, liaison): return (is_sdo_manager_for_outgoing_liaison(person, liaison) or is_sdo_manager_for_incoming_liaison(person, liaison)) return False - -if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from accountsREDESIGN import * diff --git a/ietf/liaisons/accountsREDESIGN.py b/ietf/liaisons/accountsREDESIGN.py deleted file mode 100644 index fc3fffa07..000000000 --- a/ietf/liaisons/accountsREDESIGN.py +++ /dev/null @@ -1,140 +0,0 @@ -from ietf.person.models import Person -from ietf.group.models import Role -from ietf.utils.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 proxy_personify_role(Role.objects.get(name="chair", group__acronym="ietf")) - except Role.DoesNotExist: - return None - - -def get_iesg_chair(): - return get_ietf_chair() - - -def get_iab_chair(): - try: - return proxy_personify_role(Role.objects.get(name="chair", group__acronym="iab")) - except Role.DoesNotExist: - return None - - -def get_irtf_chair(): - try: - return proxy_personify_role(Role.objects.get(name="chair", group__acronym="irtf")) - except Role.DoesNotExist: - return None - - -def get_iab_executive_director(): - try: - return proxy_personify_role(Role.objects.get(name="execdir", group__acronym="iab")) - except Person.DoesNotExist: - return None - - -def get_person_for_user(user): - if not user.is_authenticated(): - return None - try: - p = user.get_profile() - p.email = lambda: (p.plain_name(), p.email_address()) - return p - except Person.DoesNotExist: - return None - -def is_areadirector(person): - return bool(Role.objects.filter(person=person, name="ad", group__state="active", group__type="area")) - - -def is_wgchair(person): - return bool(Role.objects.filter(person=person, name="chair", group__state="active", group__type="wg")) - - -def is_wgsecretary(person): - return bool(Role.objects.filter(person=person, name="sec", group__state="active", group__type="wg")) - - -def is_ietfchair(person): - return bool(Role.objects.filter(person=person, name="chair", group__acronym="ietf")) - - -def is_iabchair(person): - return bool(Role.objects.filter(person=person, name="chair", group__acronym="iab")) - - -def is_iab_executive_director(person): - return bool(Role.objects.filter(person=person, name="execdir", group__acronym="iab")) - - -def is_irtfchair(person): - return bool(Role.objects.filter(person=person, name="chair", group__acronym="irtf")) - - -def can_add_outgoing_liaison(user): - person = get_person_for_user(user) - if not person: - return False - - if (is_areadirector(person) or is_wgchair(person) or - is_wgsecretary(person) or is_ietfchair(person) or - is_iabchair(person) or is_iab_executive_director(person) or - is_sdo_liaison_manager(person) or is_secretariat(user)): - return True - return False - - -def is_sdo_liaison_manager(person): - return bool(Role.objects.filter(person=person, name="liaiman", group__type="sdo")) - - -def is_sdo_authorized_individual(person): - return bool(Role.objects.filter(person=person, name="auth", group__type="sdo")) - - -def is_secretariat(user): - if isinstance(user, basestring): - return False - return user.is_authenticated() and bool(Role.objects.filter(person__user=user, name="secr", group__acronym="secretariat")) - - -def can_add_incoming_liaison(user): - person = get_person_for_user(user) - if not person: - return False - - if (is_sdo_liaison_manager(person) or - is_sdo_authorized_individual(person) or - is_secretariat(user)): - return True - return False - - -def can_add_liaison(user): - return can_add_incoming_liaison(user) or can_add_outgoing_liaison(user) - - -def is_sdo_manager_for_outgoing_liaison(person, liaison): - if liaison.from_group and liaison.from_group.type_id == "sdo": - return bool(liaison.from_group.role_set.filter(name="liaiman", person=person)) - return False - - -def is_sdo_manager_for_incoming_liaison(person, liaison): - if liaison.to_group and liaison.to_group.type_id == "sdo": - return bool(liaison.to_group.role_set.filter(name="liaiman", person=person)) - return False - - -def can_edit_liaison(user, liaison): - if is_secretariat(user): - return True - person = get_person_for_user(user) - if is_sdo_liaison_manager(person): - return (is_sdo_manager_for_outgoing_liaison(person, liaison) or - is_sdo_manager_for_incoming_liaison(person, liaison)) - return False diff --git a/ietf/liaisons/admin.py b/ietf/liaisons/admin.py index f34ea433b..21385011e 100644 --- a/ietf/liaisons/admin.py +++ b/ietf/liaisons/admin.py @@ -8,10 +8,3 @@ class LiaisonStatementAdmin(admin.ModelAdmin): ordering = ('title', ) raw_id_fields = ('from_contact', 'related_to', 'from_group', 'to_group', 'attachments') admin.site.register(LiaisonStatement, LiaisonStatementAdmin) - -class LiaisonDetailAdmin(admin.ModelAdmin): - list_display = ['pk', 'title', 'from_id', 'to_body', 'submitted_date', 'purpose', 'related_to' ] - list_display_links = ['pk', 'title'] - ordering = ('title', ) -admin.site.register(LiaisonDetail, LiaisonDetailAdmin) - \ No newline at end of file diff --git a/ietf/liaisons/decorators.py b/ietf/liaisons/decorators.py deleted file mode 100644 index 744082211..000000000 --- a/ietf/liaisons/decorators.py +++ /dev/null @@ -1,5 +0,0 @@ -from ietf.ietfauth.decorators import passes_test_decorator -from ietf.liaisons.accounts import can_add_liaison - -can_submit_liaison = passes_test_decorator(lambda u, *args, **kwargs: can_add_liaison(u), - "Restricted to participants who are authorized to submit liaison statements on behalf of the various IETF entities") diff --git a/ietf/liaisons/feeds.py b/ietf/liaisons/feeds.py index 5b5e38716..4e62bb5cb 100644 --- a/ietf/liaisons/feeds.py +++ b/ietf/liaisons/feeds.py @@ -1,24 +1,22 @@ # Copyright The IETF Trust 2007, All Rights Reserved +import re, datetime + 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 -from ietf.liaisons.models import LiaisonDetail, FromBodies -from ietf.idtracker.models import Acronym -from datetime import datetime, time -import re +from django.core.urlresolvers import reverse as urlreverse -if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.group.models import Group - from ietf.liaisons.proxy import LiaisonDetailProxy as LiaisonDetail - from ietf.liaisons.models import LiaisonStatement +from ietf.group.models import Group +from ietf.liaisons.models import LiaisonStatement # A slightly funny feed class, the 'object' is really # just a dict with some parameters that items() uses # to construct a queryset. class Liaisons(Feed): feed_type = Atom1Feed + def get_object(self, bits): obj = {} if bits[0] == 'recent': @@ -27,77 +25,50 @@ class Liaisons(Feed): obj['title'] = 'Recent Liaison Statements' obj['limit'] = 15 return obj - if bits[0] == 'from': + + if bits[0] == 'from': if len(bits) != 2: raise FeedDoesNotExist - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - try: - group = Group.objects.get(acronym=bits[1]) - obj['filter'] = { 'from_group': group } - obj['title'] = u'Liaison Statements from %s' % group.name + 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: + # turn all-nonword characters into one-character + # wildcards to make it easier to construct a URL that + # matches + search_string = re.sub(r"[^a-zA-Z1-9]", ".", bits[1]) + statements = LiaisonStatement.objects.filter(from_name__iregex=search_string) + if statements: + name = statements[0].from_name + obj['filter'] = { 'from_name': name } + obj['title'] = u'Liaison Statements from %s' % name return obj - except Group.DoesNotExist: - # turn all-nonword characters into one-character - # wildcards to make it easier to construct the URL - search_string = re.sub(r"[^a-zA-Z1-9]", ".", bits[1]) - statements = LiaisonStatement.objects.filter(from_name__iregex=search_string) - if statements: - name = statements[0].from_name - obj['filter'] = { 'from_name': name } - obj['title'] = u'Liaison Statements from %s' % name - return obj - else: - raise FeedDoesNotExist - try: - acronym = Acronym.objects.get(acronym=bits[1]) - obj['filter'] = {'from_id': acronym.acronym_id} - body = bits[1] - except Acronym.DoesNotExist: - # Find body matches. Turn all non-word characters - # into wildcards for the like search. - # Note that supplying sql here means that this is - # mysql-specific (e.g., postgresql wants 'ilike' for - # the same operation) - body_list = FromBodies.objects.values('from_id','body_name').extra(where=['body_name like "%s"' % re.sub('\W', '_', bits[1])]) - if not body_list: - raise FeedDoesNotExist - frmlist = [b['from_id'] for b in body_list] - # Assume that all of the matches have the same name. - # This is not guaranteed (e.g., a url like '-----------' - # will match several bodies) but is true of well-formed - # inputs. - body = body_list[0]['body_name'] - obj['filter'] = {'from_id__in': frmlist} - obj['title'] = 'Liaison Statements from %s' % body - return obj - if bits[0] == 'to': + else: + raise FeedDoesNotExist + + if bits[0] == 'to': if len(bits) != 2: raise FeedDoesNotExist - 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 + obj['filter'] = dict(to_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 - 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] + + obj['q'] = [ Q(title__icontains=bits[1]) | Q(attachments__title__icontains=bits[1]) ] + obj['title'] = 'Liaison Statements where subject matches %s' % bits[1] return obj raise FeedDoesNotExist def get_feed(self, url=None): - if not url: - raise FeedDoesNotExist - else: + if url: return Feed.get_feed(self, url=url) + else: + raise FeedDoesNotExist def title(self, obj): return obj['title'] @@ -106,12 +77,16 @@ class Liaisons(Feed): # no real equivalent for any objects return '/liaison/' + def item_link(self, obj): + # no real equivalent for any objects + return urlreverse("liaison_detail", kwargs={ "object_id": obj.pk }) + def description(self, obj): return self.title(obj) def items(self, obj): # Start with the common queryset - qs = LiaisonDetail.objects.all().order_by("-submitted_date") + qs = LiaisonStatement.objects.all().order_by("-submitted") if obj.has_key('q'): qs = qs.filter(*obj['q']) if obj.has_key('filter'): @@ -123,7 +98,7 @@ class Liaisons(Feed): def item_pubdate(self, item): # this method needs to return a datetime instance, even # though the database has only date, not time - return datetime.combine(item.submitted_date, time(0,0,0)) + return item.submitted def item_author_name(self, item): - return item.from_body() + return item.from_name diff --git a/ietf/liaisons/forms.py b/ietf/liaisons/forms.py index 0cfc391f7..8c895fc1f 100644 --- a/ietf/liaisons/forms.py +++ b/ietf/liaisons/forms.py @@ -1,4 +1,4 @@ -import datetime +import datetime, os from email.utils import parseaddr from django import forms @@ -8,26 +8,31 @@ from django.forms.util import ErrorList from django.core.validators import email_re from django.template.loader import render_to_string -from ietf.idtracker.models import PersonOrOrgInfo from ietf.liaisons.accounts import (can_add_outgoing_liaison, can_add_incoming_liaison, get_person_for_user, is_secretariat, is_sdo_liaison_manager) -from ietf.liaisons.models import LiaisonDetail, Uploads, OutgoingLiaisonApproval, SDOs 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.group.models import Group, Role +from ietf.person.models import Person, Email +from ietf.doc.models import Document -class LiaisonForm(forms.ModelForm): - +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() to_poc = forms.CharField(widget=ReadOnlyWidget, label="POC", required=False) + response_contact = forms.CharField(required=False, max_length=255) + technical_contact = forms.CharField(required=False, max_length=255) cc1 = forms.CharField(widget=forms.Textarea, label="CC", required=False, help_text='Please insert one email address per line') - purpose_text = forms.CharField(widget=forms.Textarea, label='Other purpose') + purpose = forms.ChoiceField() deadline_date = forms.DateField(label='Deadline') submitted_date = forms.DateField(label='Submission date', initial=datetime.date.today()) title = forms.CharField(label=u'Title') + body = forms.CharField(widget=forms.Textarea, required=False) attachments = forms.CharField(label='Attachments', widget=ShowAttachmentsWidget, required=False) attach_title = forms.CharField(label='Title', required=False) attach_file = forms.FileField(label='File', required=False) @@ -36,38 +41,51 @@ class LiaisonForm(forms.ModelForm): require=['id_attach_title', 'id_attach_file'], required_label='title and file'), required=False) - related_to = forms.ModelChoiceField(LiaisonDetail.objects.all(), label=u'Related Liaison', widget=RelatedLiaisonWidget, required=False) + related_to = forms.ModelChoiceField(LiaisonStatement.objects.all(), label=u'Related Liaison', widget=RelatedLiaisonWidget, required=False) fieldsets = [('From', ('from_field', 'replyto')), ('To', ('organization', 'to_poc')), ('Other email addresses', ('response_contact', 'technical_contact', 'cc1')), - ('Purpose', ('purpose', 'purpose_text', 'deadline_date')), + ('Purpose', ('purpose', 'deadline_date')), ('References', ('related_to', )), ('Liaison Statement', ('title', 'submitted_date', 'body', 'attachments')), ('Add attachment', ('attach_title', 'attach_file', 'attach_button')), ] - class Meta: - model = LiaisonDetail - - class Media: - js = ("/js/jquery-1.5.1.min.js", - "/js/jquery-ui-1.8.11.custom.min.js", - "/js/liaisons.js", ) - - css = {'all': ("/css/liaisons.css", - "/css/jquery-ui-themes/jquery-ui-1.8.11.custom.css")} - def __init__(self, user, *args, **kwargs): self.user = user 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 = PersonOrOrgInfo.objects.get(pk=kwargs['data']['from_fake_user']) + 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: + self.initial["person"] = self.instance.from_contact.person_id if self.instance.from_contact else None + self.initial["replyto"] = self.instance.reply_to + self.initial["to_poc"] = self.instance.to_contact + self.initial["response_contact"] = self.instance.response_contact + self.initial["technical_contact"] = self.instance.technical_contact + self.initial["cc1"] = self.instance.cc + self.initial["purpose"] = self.instance.purpose.order + self.initial["deadline_date"] = self.instance.deadline + self.initial["submitted_date"] = self.instance.submitted.date() if self.instance.submitted else None + self.initial["title"] = self.instance.title + self.initial["body"] = self.instance.body + self.initial["attachments"] = self.instance.attachments.all() + self.initial["related_to"] = self.instance.related_to_id + if "approved" in self.fields: + self.initial["approved"] = bool(self.instance.approved) + + self.fields["purpose"].choices = [("", "---------")] + [(str(l.order), l.name) for l in LiaisonStatementPurposeName.objects.all()] self.hm = IETFHM self.set_from_field() self.set_replyto_field() @@ -81,25 +99,19 @@ class LiaisonForm(forms.ModelForm): def set_required_fields(self): purpose = self.data.get('purpose', None) - if purpose == '5': - self.fields['purpose_text'].required=True - else: - self.fields['purpose_text'].required=False if purpose in ['1', '2']: - self.fields['deadline_date'].required=True + self.fields['deadline_date'].required = True else: - self.fields['deadline_date'].required=False + self.fields['deadline_date'].required = False def reset_required_fields(self): - self.fields['purpose_text'].required=True - self.fields['deadline_date'].required=True + self.fields['deadline_date'].required = True def set_from_field(self): assert NotImplemented def set_replyto_field(self): - email = self.person.email() - self.fields['replyto'].initial = email and email[1] + self.fields['replyto'].initial = self.person.email()[1] def set_organization_field(self): assert NotImplemented @@ -171,7 +183,7 @@ class LiaisonForm(forms.ModelForm): return self.hm.get_entity_by_key(organization_key) def get_poc(self, organization): - return ', '.join([i.email()[1] 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', '') @@ -191,34 +203,53 @@ class LiaisonForm(forms.ModelForm): return ','.join(result) def get_cc(self, from_entity, to_entity): - #Old automatic Cc code, now we retrive it from cleaned_data - #persons = to_entity.get_cc(self.person) - #persons += from_entity.get_from_cc(self.person) - #return ', '.join(['%s <%s>' % i.email() for i in persons]) - cc = self.cleaned_data.get('cc1', '') - return cc + return self.cleaned_data.get('cc1', '') def save(self, *args, **kwargs): - liaison = super(LiaisonForm, self).save(*args, **kwargs) - self.save_extra_fields(liaison) - self.save_attachments(liaison) - return liaison + l = self.instance + if not l: + l = LiaisonStatement() + + l.title = self.cleaned_data["title"] + l.purpose = LiaisonStatementPurposeName.objects.get(order=self.cleaned_data["purpose"]) + l.body = self.cleaned_data["body"].strip() + l.deadline = self.cleaned_data["deadline_date"] + l.related_to = self.cleaned_data["related_to"] + l.reply_to = self.cleaned_data["replyto"] + l.response_contact = self.cleaned_data["response_contact"] + l.technical_contact = self.cleaned_data["technical_contact"] + + now = datetime.datetime.now() + + l.modified = now + l.submitted = datetime.datetime.combine(self.cleaned_data["submitted_date"], now.time()) + 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) + + return l def save_extra_fields(self, liaison): - now = datetime.datetime.now() - liaison.last_modified_date = now from_entity = self.get_from_entity() - liaison.from_raw_body = from_entity.name - liaison.from_raw_code = self.cleaned_data.get('from_field') + liaison.from_name = from_entity.name + liaison.from_group = from_entity.obj + e = self.cleaned_data["person"].email_set.order_by('-active', '-time') + if e: + liaison.from_contact = e[0] + organization = self.get_to_entity() - liaison.to_raw_code = self.cleaned_data.get('organization') - liaison.to_body = organization.name - liaison.to_poc = self.get_poc(organization) - liaison.submitter_name, liaison.submitter_email = self.person.email() - liaison.cc1 = self.get_cc(from_entity, organization) - liaison.save() + 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): + 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(): @@ -229,13 +260,18 @@ class LiaisonForm(forms.ModelForm): 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.get_or_create( + name = name, + defaults=dict( + title = self.data.get(title_key), + type_id = "liai-att", + 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() @@ -245,8 +281,7 @@ class LiaisonForm(forms.ModelForm): exclude_filter = {'pk': self.instance.pk} else: exclude_filter = {} - exists = bool(LiaisonDetail.objects.exclude(**exclude_filter).filter(title__iexact=title).count()) - if exists: + if LiaisonStatement.objects.exclude(**exclude_filter).filter(title__iexact=title).exists(): raise forms.ValidationError('A liaison statement with the same title has previously been submitted.') return title @@ -255,20 +290,26 @@ class IncomingLiaisonForm(LiaisonForm): def set_from_field(self): if is_secretariat(self.user): - sdos = SDOs.objects.all() + sdos = Group.objects.filter(type="sdo", state="active") else: - sdo_managed = [i.sdo for i in self.person.liaisonmanagers_set.all()] - sdo_authorized = [i.sdo for i in self.person.sdoauthorizedindividual_set.all()] - sdos = set(sdo_managed).union(sdo_authorized) - self.fields['from_field'].choices = [('sdo_%s' % i.pk, i.sdo_name) for i in sdos] + sdos = Group.objects.filter(type="sdo", state="active", role__person=self.person, role__name__in=("liaiman", "auth")).distinct() + self.fields['from_field'].choices = [('sdo_%s' % i.pk, i.name) for i in sdos.order_by("name")] self.fields['from_field'].widget.submitter = unicode(self.person) + def set_replyto_field(self): + e = Email.objects.filter(person=self.person, role__group__state="active", role__name__in=["liaiman", "auth"]) + if e: + addr = e[0].address + else: + addr = self.person.email_address() + self.fields['replyto'].initial = addr + def set_organization_field(self): self.fields['organization'].choices = self.hm.get_all_incoming_entities() def get_post_only(self): from_entity = self.get_from_entity() - if is_secretariat(self.user) or self.person.sdoauthorizedindividual_set.filter(sdo=from_entity.obj): + if is_secretariat(self.user) or Role.objects.filter(person=self.person, group=from_entity.obj, name="auth"): return False return True @@ -278,6 +319,9 @@ class IncomingLiaisonForm(LiaisonForm): return super(IncomingLiaisonForm, self).clean() +def liaison_manager_sdos(person): + return Group.objects.filter(type="sdo", state="active", role__person=person, role__name="liaiman").distinct() + class OutgoingLiaisonForm(LiaisonForm): to_poc = forms.CharField(label="POC", required=True) @@ -301,17 +345,24 @@ class OutgoingLiaisonForm(LiaisonForm): all_entities += i[1] if all_entities: self.fields['from_field'].widget.full_power_on = [i[0] for i in all_entities] - self.fields['from_field'].widget.reduced_to_set = ['sdo_%s' % i.sdo.pk for i in self.person.liaisonmanagers_set.all().distinct()] + self.fields['from_field'].widget.reduced_to_set = ['sdo_%s' % i.pk for i in liaison_manager_sdos(self.person)] else: self.fields['from_field'].choices = self.hm.get_entities_for_person(self.person) self.fields['from_field'].widget.submitter = unicode(self.person) self.fieldsets[0] = ('From', ('from_field', 'replyto', 'approved')) + def set_replyto_field(self): + e = Email.objects.filter(person=self.person, role__group__state="active", role__name__in=["ad", "chair"]) + if e: + addr = e[0].address + else: + addr = self.person.email_address() + self.fields['replyto'].initial = addr + def set_organization_field(self): # If the user is a liaison manager and is nothing more, reduce the To field to his SDOs if not self.hm.get_entities_for_person(self.person) and is_sdo_liaison_manager(self.person): - sdos = [i.sdo for i in self.person.liaisonmanagers_set.all().distinct()] - self.fields['organization'].choices = [('sdo_%s' % i.pk, i.sdo_name) for i in sdos] + self.fields['organization'].choices = [('sdo_%s' % i.pk, i.name) for i in liaison_manager_sdos(self.person)] else: self.fields['organization'].choices = self.hm.get_all_outgoing_entities() self.fieldsets[1] = ('To', ('organization', 'other_organization', 'to_poc')) @@ -336,16 +387,9 @@ class OutgoingLiaisonForm(LiaisonForm): 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) @@ -361,10 +405,10 @@ class OutgoingLiaisonForm(LiaisonForm): person = self.fake_person or self.person for i in self.hm.get_entities_for_person(person): all_entities += i[1] - # If the from entity is one in which the user has full privileges the to entity could be anyone + # If the from entity is one in wich the user has full privileges the to entity could be anyone if from_code in [i[0] for i in all_entities]: return to_code - sdo_codes = ['sdo_%s' % i.sdo.pk for i in person.liaisonmanagers_set.all().distinct()] + sdo_codes = ['sdo_%s' % i.pk for i in liaison_manager_sdos(person)] if to_code in sdo_codes: return to_code entity = self.get_to_entity() @@ -384,35 +428,28 @@ class EditLiaisonForm(LiaisonForm): cc1 = forms.CharField(widget=forms.TextInput, label="CC", required=False) class Meta: - model = LiaisonDetail fields = ('from_raw_body', 'to_body', 'to_poc', 'cc1', 'last_modified_date', 'title', - 'response_contact', 'technical_contact', 'purpose_text', 'body', + 'response_contact', 'technical_contact', 'body', 'deadline_date', 'purpose', 'replyto', 'related_to') def __init__(self, *args, **kwargs): super(EditLiaisonForm, self).__init__(*args, **kwargs) self.edit = True - self.initial.update({'attachments': self.instance.uploads_set.all()}) - self.fields['submitted_date'].initial = self.instance.submitted_date def set_from_field(self): - self.fields['from_field'].initial = self.instance.from_body + self.fields['from_field'].initial = self.instance.from_name def set_replyto_field(self): - self.fields['replyto'].initial = self.instance.replyto + self.fields['replyto'].initial = self.instance.reply_to def set_organization_field(self): - self.fields['organization'].initial = self.instance.to_body + self.fields['organization'].initial = self.instance.to_name def save_extra_fields(self, liaison): - now = datetime.datetime.now() - liaison.last_modified_date = now - liaison.from_raw_body = self.cleaned_data.get('from_field') - liaison.to_body = self.cleaned_data.get('organization') - liaison.to_poc = self.cleaned_data['to_poc'] - liaison.cc1 = self.cleaned_data['cc1'] - liaison.save() - + liaison.from_name = self.cleaned_data.get('from_field') + liaison.to_name = self.cleaned_data.get('organization') + liaison.to_contact = self.cleaned_data['to_poc'] + liaison.cc = self.cleaned_data['cc1'] def liaison_form_factory(request, **kwargs): user = request.user @@ -426,5 +463,3 @@ def liaison_form_factory(request, **kwargs): return IncomingLiaisonForm(user, **kwargs) return None -if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.liaisons.formsREDESIGN import * diff --git a/ietf/liaisons/formsREDESIGN.py b/ietf/liaisons/formsREDESIGN.py deleted file mode 100644 index efe2c6eb4..000000000 --- a/ietf/liaisons/formsREDESIGN.py +++ /dev/null @@ -1,474 +0,0 @@ -import datetime, os -from email.utils import parseaddr - -from django import forms -from django.conf import settings -from django.db.models import Q -from django.forms.util import ErrorList -from django.core.validators import email_re -from django.template.loader import render_to_string - -from ietf.liaisons.accounts import (can_add_outgoing_liaison, can_add_incoming_liaison, - get_person_for_user, is_secretariat, is_sdo_liaison_manager) -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 ietf.group.models import Group, Role -from ietf.person.models import Person, Email -from ietf.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() - to_poc = forms.CharField(widget=ReadOnlyWidget, label="POC", required=False) - response_contact = forms.CharField(required=False, max_length=255) - technical_contact = forms.CharField(required=False, max_length=255) - cc1 = forms.CharField(widget=forms.Textarea, label="CC", required=False, help_text='Please insert one email address per line') - purpose = forms.ChoiceField() - purpose_text = forms.CharField(widget=forms.Textarea, label='Other purpose') - deadline_date = forms.DateField(label='Deadline') - submitted_date = forms.DateField(label='Submission date', initial=datetime.date.today()) - title = forms.CharField(label=u'Title') - body = forms.CharField(widget=forms.Textarea, required=False) - attachments = forms.CharField(label='Attachments', widget=ShowAttachmentsWidget, required=False) - attach_title = forms.CharField(label='Title', required=False) - attach_file = forms.FileField(label='File', required=False) - attach_button = forms.CharField(label='', - widget=ButtonWidget(label='Attach', show_on='id_attachments', - require=['id_attach_title', 'id_attach_file'], - required_label='title and file'), - required=False) - related_to = forms.ModelChoiceField(LiaisonStatement.objects.all(), label=u'Related Liaison', widget=RelatedLiaisonWidget, required=False) - - fieldsets = [('From', ('from_field', 'replyto')), - ('To', ('organization', 'to_poc')), - ('Other email addresses', ('response_contact', 'technical_contact', 'cc1')), - ('Purpose', ('purpose', 'purpose_text', 'deadline_date')), - ('References', ('related_to', )), - ('Liaison Statement', ('title', 'submitted_date', 'body', 'attachments')), - ('Add attachment', ('attach_title', 'attach_file', 'attach_button')), - ] - - class Media: - js = ("/js/jquery-1.5.1.min.js", - "/js/jquery-ui-1.8.11.custom.min.js", - "/js/liaisons.js", ) - - css = {'all': ("/css/liaisons.css", - "/css/jquery-ui-themes/jquery-ui-1.8.11.custom.css")} - - def __init__(self, user, *args, **kwargs): - self.user = user - self.fake_person = None - self.person = get_person_for_user(user) - if kwargs.get('data', None): - 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(): - try: - x = getattr(self.instance, name) - if name == "purpose": # proxy has a name-clash on purpose so help it - x = x.order - - try: - x = x.pk # foreign keys need the .pk, not the actual object - except AttributeError: - pass - self.initial[name] = x - except AttributeError: - # we have some fields on the form that aren't in the model - pass - self.fields["purpose"].choices = [("", "---------")] + [(str(l.order), l.name) for l in LiaisonStatementPurposeName.objects.all()] - self.hm = IETFHM - self.set_from_field() - self.set_replyto_field() - self.set_organization_field() - - def __unicode__(self): - return self.as_div() - - def get_post_only(self): - return False - - def set_required_fields(self): - purpose = self.data.get('purpose', None) - if purpose == '5': - self.fields['purpose_text'].required=True - else: - self.fields['purpose_text'].required=False - if purpose in ['1', '2']: - self.fields['deadline_date'].required=True - else: - self.fields['deadline_date'].required=False - - def reset_required_fields(self): - self.fields['purpose_text'].required=True - self.fields['deadline_date'].required=True - - def set_from_field(self): - assert NotImplemented - - def set_replyto_field(self): - self.fields['replyto'].initial = self.person.email()[1] - - def set_organization_field(self): - assert NotImplemented - - def as_div(self): - return render_to_string('liaisons/liaisonform.html', {'form': self}) - - def get_fieldsets(self): - if not self.fieldsets: - yield dict(name=None, fields=self) - else: - for fieldset, fields in self.fieldsets: - fieldset_dict = dict(name=fieldset, fields=[]) - for field_name in fields: - if field_name in self.fields.keyOrder: - fieldset_dict['fields'].append(self[field_name]) - if not fieldset_dict['fields']: - # if there is no fields in this fieldset, we continue to next fieldset - continue - yield fieldset_dict - - def full_clean(self): - self.set_required_fields() - super(LiaisonForm, self).full_clean() - self.reset_required_fields() - - def has_attachments(self): - for key in self.files.keys(): - if key.startswith('attach_file_') and key.replace('file', 'title') in self.data.keys(): - return True - return False - - def check_email(self, value): - if not value: - return - emails = value.split(',') - for email in emails: - name, addr = parseaddr(email) - if not email_re.search(addr): - raise forms.ValidationError('Invalid email address: %s' % addr) - - def clean_response_contact(self): - value = self.cleaned_data.get('response_contact', None) - self.check_email(value) - return value - - def clean_technical_contact(self): - value = self.cleaned_data.get('technical_contact', None) - self.check_email(value) - return value - - def clean_reply_to(self): - value = self.cleaned_data.get('reply_to', None) - self.check_email(value) - return value - - def clean(self): - if not self.cleaned_data.get('body', None) and not self.has_attachments(): - self._errors['body'] = ErrorList([u'You must provide a body or attachment files']) - self._errors['attachments'] = ErrorList([u'You must provide a body or attachment files']) - return self.cleaned_data - - def get_from_entity(self): - organization_key = self.cleaned_data.get('from_field') - return self.hm.get_entity_by_key(organization_key) - - def get_to_entity(self): - organization_key = self.cleaned_data.get('organization') - return self.hm.get_entity_by_key(organization_key) - - def get_poc(self, organization): - return ', '.join(u"%s <%s>" % i.email() for i in organization.get_poc()) - - def clean_cc1(self): - value = self.cleaned_data.get('cc1', '') - result = [] - errors = [] - for address in value.split('\n'): - address = address.strip(); - if not address: - continue - try: - self.check_email(address) - except forms.ValidationError: - errors.append(address) - result.append(address) - if errors: - raise forms.ValidationError('Invalid email addresses: %s' % ', '.join(errors)) - return ','.join(result) - - def get_cc(self, from_entity, to_entity): - #Old automatic Cc code, now we retrive it from cleaned_data - #persons = to_entity.get_cc(self.person) - #persons += from_entity.get_from_cc(self.person) - #return ', '.join(['%s <%s>' % i.email() for i in persons]) - cc = self.cleaned_data.get('cc1', '') - return cc - - def save(self, *args, **kwargs): - l = self.instance - if not l: - l = LiaisonDetailProxy() - - l.title = self.cleaned_data["title"] - l.purpose = LiaisonStatementPurposeName.objects.get(order=self.cleaned_data["purpose"]) - l.body = self.cleaned_data["body"].strip() - l.deadline = self.cleaned_data["deadline_date"] - l.related_to = self.cleaned_data["related_to"] - l.reply_to = self.cleaned_data["replyto"] - l.response_contact = self.cleaned_data["response_contact"] - l.technical_contact = self.cleaned_data["technical_contact"] - - now = datetime.datetime.now() - - l.modified = now - l.submitted = datetime.datetime.combine(self.cleaned_data["submitted_date"], now.time()) - 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) - - return l - - def save_extra_fields(self, liaison): - from_entity = self.get_from_entity() - liaison.from_name = from_entity.name - liaison.from_group = from_entity.obj - e = self.cleaned_data["person"].email_set.order_by('-active') - if e: - liaison.from_contact = e[0] - - organization = self.get_to_entity() - 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): - 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(): - continue - attached_file = self.files.get(key) - extension=attached_file.name.rsplit('.', 1) - if len(extension) > 1: - extension = '.' + extension[1] - else: - extension = '' - written += 1 - name = instance.name() + ("-attachment-%s" % written) - attach, _ = Document.objects.get_or_create( - name = name, - defaults=dict( - title = self.data.get(title_key), - type_id = "liai-att", - external_url = name + extension, # strictly speaking not necessary, but just for the time being ... - ) - ) - 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() - - def clean_title(self): - title = self.cleaned_data.get('title', None) - if self.instance and self.instance.pk: - exclude_filter = {'pk': self.instance.pk} - else: - exclude_filter = {} - exists = bool(LiaisonStatement.objects.exclude(**exclude_filter).filter(title__iexact=title).count()) - if exists: - raise forms.ValidationError('A liaison statement with the same title has previously been submitted.') - return title - - -class IncomingLiaisonForm(LiaisonForm): - - def set_from_field(self): - if is_secretariat(self.user): - sdos = Group.objects.filter(type="sdo", state="active") - else: - sdos = Group.objects.filter(type="sdo", state="active", role__person=self.person, role__name__in=("liaiman", "auth")).distinct() - self.fields['from_field'].choices = [('sdo_%s' % i.pk, i.name) for i in sdos.order_by("name")] - self.fields['from_field'].widget.submitter = unicode(self.person) - - def set_replyto_field(self): - e = Email.objects.filter(person=self.person, role__group__state="active", role__name__in=["liaiman", "auth"]) - if e: - addr = e[0].address - else: - addr = self.person.email_address() - self.fields['replyto'].initial = addr - - def set_organization_field(self): - self.fields['organization'].choices = self.hm.get_all_incoming_entities() - - def get_post_only(self): - from_entity = self.get_from_entity() - if is_secretariat(self.user) or Role.objects.filter(person=self.person, group=from_entity.obj, name="auth"): - return False - return True - - def clean(self): - if 'send' in self.data.keys() and self.get_post_only(): - self._errors['from_field'] = ErrorList([u'As an IETF Liaison Manager you can not send an incoming liaison statements, you only can post them']) - return super(IncomingLiaisonForm, self).clean() - - -def liaison_manager_sdos(person): - return Group.objects.filter(type="sdo", state="active", role__person=person, role__name="liaiman").distinct() - -class OutgoingLiaisonForm(LiaisonForm): - - to_poc = forms.CharField(label="POC", required=True) - approved = forms.BooleanField(label="Obtained prior approval", required=False) - other_organization = forms.CharField(label="Other SDO", required=True) - - def get_to_entity(self): - organization_key = self.cleaned_data.get('organization') - organization = self.hm.get_entity_by_key(organization_key) - if organization_key == 'othersdo' and self.cleaned_data.get('other_organization', None): - organization.name=self.cleaned_data['other_organization'] - return organization - - def set_from_field(self): - if is_secretariat(self.user): - self.fields['from_field'].choices = self.hm.get_all_incoming_entities() - elif is_sdo_liaison_manager(self.person): - self.fields['from_field'].choices = self.hm.get_all_incoming_entities() - all_entities = [] - for i in self.hm.get_entities_for_person(self.person): - all_entities += i[1] - if all_entities: - self.fields['from_field'].widget.full_power_on = [i[0] for i in all_entities] - self.fields['from_field'].widget.reduced_to_set = ['sdo_%s' % i.pk for i in liaison_manager_sdos(self.person)] - else: - self.fields['from_field'].choices = self.hm.get_entities_for_person(self.person) - self.fields['from_field'].widget.submitter = unicode(self.person) - self.fieldsets[0] = ('From', ('from_field', 'replyto', 'approved')) - - def set_replyto_field(self): - e = Email.objects.filter(person=self.person, role__group__state="active", role__name__in=["ad", "chair"]) - if e: - addr = e[0].address - else: - addr = self.person.email_address() - self.fields['replyto'].initial = addr - - def set_organization_field(self): - # If the user is a liaison manager and is nothing more, reduce the To field to his SDOs - if not self.hm.get_entities_for_person(self.person) and is_sdo_liaison_manager(self.person): - self.fields['organization'].choices = [('sdo_%s' % i.pk, i.name) for i in liaison_manager_sdos(self.person)] - else: - self.fields['organization'].choices = self.hm.get_all_outgoing_entities() - self.fieldsets[1] = ('To', ('organization', 'other_organization', 'to_poc')) - - def set_required_fields(self): - super(OutgoingLiaisonForm, self).set_required_fields() - organization = self.data.get('organization', None) - if organization == 'othersdo': - self.fields['other_organization'].required=True - else: - self.fields['other_organization'].required=False - - def reset_required_fields(self): - super(OutgoingLiaisonForm, self).reset_required_fields() - self.fields['other_organization'].required=True - - def get_poc(self, organization): - return self.cleaned_data['to_poc'] - - def save_extra_fields(self, liaison): - 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): - liaison.approved = datetime.datetime.now() - else: - liaison.approved = None - - def clean_to_poc(self): - value = self.cleaned_data.get('to_poc', None) - self.check_email(value) - return value - - def clean_organization(self): - to_code = self.cleaned_data.get('organization', None) - from_code = self.cleaned_data.get('from_field', None) - if not to_code or not from_code: - return to_code - all_entities = [] - person = self.fake_person or self.person - for i in self.hm.get_entities_for_person(person): - all_entities += i[1] - # If the from entity is one in wich the user has full privileges the to entity could be anyone - if from_code in [i[0] for i in all_entities]: - return to_code - sdo_codes = ['sdo_%s' % i.pk for i in liaison_manager_sdos(person)] - if to_code in sdo_codes: - return to_code - entity = self.get_to_entity() - entity_name = entity and entity.name or to_code - if self.fake_person: - raise forms.ValidationError('%s is not allowed to send a liaison to: %s' % (self.fake_person, entity_name)) - else: - raise forms.ValidationError('You are not allowed to send a liaison to: %s' % entity_name) - - -class EditLiaisonForm(LiaisonForm): - - from_field = forms.CharField(widget=forms.TextInput, label=u'From') - replyto = forms.CharField(label=u'Reply to', widget=forms.TextInput) - organization = forms.CharField(widget=forms.TextInput) - to_poc = forms.CharField(widget=forms.TextInput, label="POC", required=False) - cc1 = forms.CharField(widget=forms.TextInput, label="CC", required=False) - - class Meta: - fields = ('from_raw_body', 'to_body', 'to_poc', 'cc1', 'last_modified_date', 'title', - 'response_contact', 'technical_contact', 'purpose_text', 'body', - 'deadline_date', 'purpose', 'replyto', 'related_to') - - def __init__(self, *args, **kwargs): - super(EditLiaisonForm, self).__init__(*args, **kwargs) - self.edit = True - self.initial.update({'attachments': self.instance.uploads_set.all()}) - self.fields['submitted_date'].initial = self.instance.submitted_date - - def set_from_field(self): - self.fields['from_field'].initial = self.instance.from_body - - def set_replyto_field(self): - self.fields['replyto'].initial = self.instance.replyto - - def set_organization_field(self): - self.fields['organization'].initial = self.instance.to_body - - def save_extra_fields(self, liaison): - liaison.from_name = self.cleaned_data.get('from_field') - liaison.to_name = self.cleaned_data.get('organization') - liaison.to_contact = self.cleaned_data['to_poc'] - liaison.cc = self.cleaned_data['cc1'] - diff --git a/ietf/liaisons/mails.py b/ietf/liaisons/mails.py index db80b9d3c..3af748db5 100644 --- a/ietf/liaisons/mails.py +++ b/ietf/liaisons/mails.py @@ -8,14 +8,11 @@ from ietf.utils.mail import send_mail_text from ietf.liaisons.utils import role_persons_with_fixed_email from ietf.group.models import Role -def send_liaison_by_email(request, liaison, fake=False): - if liaison.is_pending(): # this conditional should definitely be at the caller, not here - return notify_pending_by_email(request, liaison, fake) - +def send_liaison_by_email(request, liaison): subject = u'New Liaison Statement, "%s"' % (liaison.title) from_email = settings.LIAISON_UNIVERSAL_FROM - to_email = liaison.to_poc.split(',') - cc = liaison.cc1.split(',') + to_email = liaison.to_contact.split(',') + cc = liaison.cc.split(',') if liaison.technical_contact: cc += liaison.technical_contact.split(',') if liaison.response_contact: @@ -29,7 +26,7 @@ def send_liaison_by_email(request, liaison, fake=False): send_mail_text(request, to_email, from_email, subject, body, cc=", ".join(cc), bcc=", ".join(bcc)) -def notify_pending_by_email(request, liaison, fake): +def notify_pending_by_email(request, liaison): # Broken: this does not find the list of approvers for the sending body # For now, we are sending to statements@ietf.org so the Secretariat can nudge diff --git a/ietf/liaisons/management/commands/check_liaison_deadlines.py b/ietf/liaisons/management/commands/check_liaison_deadlines.py index 7deeb4ecf..73dd585ef 100644 --- a/ietf/liaisons/management/commands/check_liaison_deadlines.py +++ b/ietf/liaisons/management/commands/check_liaison_deadlines.py @@ -5,78 +5,19 @@ from django.core.management.base import BaseCommand from django.template.loader import render_to_string from django.core.urlresolvers import reverse as urlreverse -from ietf.liaisons.models import LiaisonDetail -#from ietf.liaisons.mail import IETFEmailMessage -from ietf.utils.mail import send_mail_text - - -PREVIOUS_DAYS = { - 14: 'in two weeks', - 7: 'in one week', - 4: 'in four days', - 3: 'in three days', - 2: 'in two days', - 1: 'tomorrow', - 0: 'today'} +from ietf.liaisons.models import LiaisonStatement +from ietf.liaisons.mails import possibly_send_deadline_reminder class Command(BaseCommand): help = (u"Check liaison deadlines and send a reminder if we are close to a deadline") - def send_reminder(self, liaison, days_to_go): - if days_to_go < 0: - subject = '[Liaison OUT OF DATE] %s' % liaison.title - days_msg = 'is out of date for %s days' % (-days_to_go) - else: - subject = '[Liaison deadline %s] %s' % (PREVIOUS_DAYS[days_to_go], liaison.title) - days_msg = 'expires %s' % PREVIOUS_DAYS[days_to_go] - - 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_deadline_mail.txt', - {'liaison': liaison, - 'days_msg': days_msg, - 'url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_approval_detail", kwargs=dict(object_id=liaison.pk)), - 'referenced_url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=liaison.related_to.pk)) if liaison.related_to else None, - }) - send_mail_text(context=None,to=to_email,frm=from_email,cc=cc,subject=subject,bcc=bcc,txt=body) - print 'Liaison %05s#: Deadline reminder Sent!' % liaison.pk - - #mail = IETFEmailMessage(subject=subject, - # to=to_email, - # from_email=from_email, - # cc=cc, - # bcc=bcc, - # body=body) - #if not settings.DEBUG: - # mail.send() - # print 'Liaison %05s#: Deadline reminder Sent!' % liaison.pk - #else: - # print 'Liaison %05s#: Deadline reminder Not Sent because in DEBUG mode!' % liaison.pk - def handle(self, *args, **options): today = datetime.date.today() - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.liaisons.mails import possibly_send_deadline_reminder - from ietf.liaisons.proxy import LiaisonDetailProxy as LiaisonDetail - - cutoff = today - datetime.timedelta(14) - - for l in LiaisonDetail.objects.filter(action_taken=False, deadline__gte=cutoff).exclude(deadline=None): - r = possibly_send_deadline_reminder(l) - if r: - print 'Liaison %05s#: Deadline reminder sent!' % liaison.pk - return - - query = LiaisonDetail.objects.filter(deadline_date__isnull=False, action_taken=False, deadline_date__gte=today - datetime.timedelta(14)) - for liaison in query: - delta = liaison.deadline_date - today - if delta.days < 0 or delta.days in PREVIOUS_DAYS.keys(): - self.send_reminder(liaison, delta.days) + cutoff = today - datetime.timedelta(14) + + for l in LiaisonStatement.objects.filter(action_taken=False, deadline__gte=cutoff).exclude(deadline=None): + r = possibly_send_deadline_reminder(l) + if r: + print 'Liaison %05s#: Deadline reminder sent!' % liaison.pk diff --git a/ietf/liaisons/management/commands/remind_update_sdo_list.py b/ietf/liaisons/management/commands/remind_update_sdo_list.py index 8c69093b8..fb37be74f 100644 --- a/ietf/liaisons/management/commands/remind_update_sdo_list.py +++ b/ietf/liaisons/management/commands/remind_update_sdo_list.py @@ -5,7 +5,8 @@ from django.core.mail import EmailMessage from django.core.management.base import BaseCommand from django.template.loader import render_to_string -from ietf.liaisons.models import SDOs +from ietf.group.models import Group +from ietf.liaisons.mails import send_sdo_reminder class Command(BaseCommand): @@ -16,56 +17,15 @@ class Command(BaseCommand): ) - def send_mail_to(self, person, sdo): - subject = 'Request for update list of authorized individuals' - email = person.email()[1] - name = ' '.join([i for i in (person.name_prefix, person.first_name, person.middle_initial, person.last_name, person.name_suffix) if i]) - authorized_list = [i.person for i in sdo.sdoauthorizedindividual_set.all()] - body = render_to_string('liaisons/sdo_reminder.txt', - {'manager_name': name, - 'sdo_name': sdo.sdo_name, - 'individuals': authorized_list, - }) - mail = EmailMessage(subject=subject, - to=[email], - from_email=settings.LIAISON_UNIVERSAL_FROM, - body = body) - if not settings.DEBUG: - mail.send() - msg = '%05s#: %s Mail Sent!' % (sdo.pk, sdo.sdo_name) - else: - msg = '%05s#: %s Mail Not Sent because in DEBUG mode!' % (sdo.pk, sdo.sdo_name) - return msg - def handle(self, *args, **options): sdo_pk = options.get('sdo_pk', None) return_output = options.get('return_output', False) - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - msg_list = send_reminders_to_sdos(sdo_pk=sdo_pk) - return msg_list if return_output else None - - query = SDOs.objects.all().order_by('pk') - if sdo_pk: - query = query.filter(pk=sdo_pk) - - msg_list = [] - for sdo in query: - manager = sdo.liaisonmanager() - if manager: - msg = self.send_mail_to(manager.person, sdo) - else: - msg = '%05s#: %s has no liaison manager' % (sdo.pk, sdo.sdo_name) - print msg - msg_list.append(msg) - if return_output: - return msg_list + msg_list = send_reminders_to_sdos(sdo_pk=sdo_pk) + return msg_list if return_output else None def send_reminders_to_sdos(sdo_pk=None): - from ietf.group.models import Group - from ietf.liaisons.mails import send_sdo_reminder - sdos = Group.objects.filter(type="sdo").order_by('pk') if sdo_pk: sdos = sdos.filter(pk=sdo_pk) diff --git a/ietf/liaisons/models.py b/ietf/liaisons/models.py index d1b2fe832..6f596256c 100644 --- a/ietf/liaisons/models.py +++ b/ietf/liaisons/models.py @@ -1,369 +1,52 @@ # Copyright The IETF Trust 2007, All Rights Reserved -from django.conf import settings -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.utils.mail import send_mail_text -from ietf.ietfauth.models import LegacyLiaisonUser -from ietf.utils.admin import admin_link -class LiaisonPurpose(models.Model): - purpose_id = models.AutoField(primary_key=True) - purpose_text = models.CharField(blank=True, max_length=50) - def __str__(self): - return self.purpose_text - class Meta: - db_table = 'liaison_purpose' - -class FromBodies(models.Model): - from_id = models.AutoField(primary_key=True) - body_name = models.CharField(blank=True, max_length=35) - poc = models.ForeignKey(PersonOrOrgInfo, db_column='poc', null=True) - is_liaison_manager = models.BooleanField() - other_sdo = models.BooleanField() - email_priority = models.IntegerField(null=True, blank=True) - def __str__(self): - return self.body_name - class Meta: - db_table = 'from_bodies' - verbose_name = "From body" - verbose_name_plural = "From bodies" - contact_link = admin_link('poc', label='Contact') +from ietf.name.models import LiaisonStatementPurposeName +from ietf.doc.models import Document +from ietf.person.models import Email +from ietf.group.models import Group +class LiaisonStatement(models.Model): + title = models.CharField(blank=True, max_length=255) + purpose = models.ForeignKey(LiaisonStatementPurposeName) + body = models.TextField(blank=True) + deadline = models.DateField(null=True, blank=True) + related_to = models.ForeignKey('LiaisonStatement', blank=True, null=True) -class OutgoingLiaisonApproval(models.Model): - approved = models.BooleanField(default=True) - approval_date = models.DateField(null=True, blank=True) + 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="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 recipient body") + reply_to = models.CharField(blank=True, max_length=255) -class LiaisonDetail(models.Model): - detail_id = models.AutoField(primary_key=True) - person = models.ForeignKey(PersonOrOrgInfo, null=True, db_column='person_or_org_tag') - submitted_date = models.DateField(null=True, blank=True) - last_modified_date = models.DateField(null=True, blank=True) - from_id = models.IntegerField(null=True, blank=True) - to_body = models.CharField(blank=True, null=True, max_length=255) - title = models.CharField(blank=True, null=True, max_length=255) - response_contact = models.CharField(blank=True, null=True, max_length=255) - technical_contact = models.CharField(blank=True, null=True, max_length=255) - purpose_text = models.TextField(blank=True, null=True, db_column='purpose') - body = models.TextField(blank=True,null=True) - deadline_date = models.DateField(null=True, blank=True) - cc1 = models.TextField(blank=True, null=True) - # unclear why cc2 is a CharField, but it's always - # either NULL or blank. - cc2 = models.CharField(blank=True, null=True, max_length=50) - submitter_name = models.CharField(blank=True, null=True, max_length=255) - submitter_email = models.CharField(blank=True, null=True, max_length=255) - by_secretariat = models.IntegerField(null=True, blank=True) - to_poc = models.CharField(blank=True, null=True, max_length=255) - to_email = models.CharField(blank=True, null=True, max_length=255) - purpose = models.ForeignKey(LiaisonPurpose,null=True) - replyto = models.CharField(blank=True, null=True, max_length=255) - from_raw_body = models.CharField(blank=True, null=True, max_length=255) - from_raw_code = models.CharField(blank=True, null=True, max_length=255) - to_raw_code = models.CharField(blank=True, null=True, max_length=255) - approval = models.ForeignKey(OutgoingLiaisonApproval, blank=True, null=True) - action_taken = models.BooleanField(default=False, db_column='taken_care') - related_to = models.ForeignKey('LiaisonDetail', blank=True, null=True) - def __str__(self): - return self.title or "" - def __unicode__(self): - return self.title or "" - def from_body(self): - """The from_raw_body stores the name of the entity - sending the liaison. - For legacy liaisons (the ones with empty from_raw_body) - the legacy_from_body() is returned.""" - if not self.from_raw_body: - return self.legacy_from_body() - return self.from_raw_body - def from_sdo(self): - try: - name = FromBodies.objects.get(pk=self.from_id).body_name - sdo = SDOs.objects.get(sdo_name=name) - return sdo - except ObjectDoesNotExist: - return None - def legacy_from_body(self): - """The from_id field is a foreign key for either - FromBodies or Acronyms, depending on whether it's - the IETF or not. There is no flag field saying - which, so we just try it. If the index values - overlap, then this function will be ambiguous - and will return the value from FromBodies. Current - acronym IDs start at 925 so the day of reckoning - is not nigh.""" - try: - from_body = FromBodies.objects.get(pk=self.from_id) - return from_body.body_name - except ObjectDoesNotExist: - pass - try: - acronym = Acronym.objects.get(pk=self.from_id) - try: - x = acronym.area - kind = "AREA" - except Area.DoesNotExist: - kind = "WG" - return "IETF %s %s" % (acronym.acronym.upper(), kind) - except ObjectDoesNotExist: - pass - return "" % self.from_id - def from_email(self): - """If there is an entry in from_bodies, it has - the desired email priority. However, if it's from - an IETF WG, there is no entry in from_bodies, so - default to 1.""" - try: - from_body = FromBodies.objects.get(pk=self.from_id) - email_priority = from_body.email_priority - except FromBodies.DoesNotExist: - email_priority = 1 - return self.person.emailaddress_set.all().get(priority=email_priority) - def get_absolute_url(self): - return '/liaison/%d/' % self.detail_id - class Meta: - db_table = 'liaison_detail' + response_contact = models.CharField(blank=True, max_length=255) + technical_contact = models.CharField(blank=True, max_length=255) + cc = models.TextField(blank=True) - def notify_pending_by_email(self, fake): - from ietf.liaisons.utils import IETFHM + submitted = models.DateTimeField(null=True, blank=True) + modified = models.DateTimeField(null=True, blank=True) + approved = models.DateTimeField(null=True, blank=True) - 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, - 'url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_approval_detail", kwargs=dict(object_id=self.pk)), - 'referenced_url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=self.related_to.pk)) if self.related_to else None, - }) - send_mail_text(context=None, to=to_email, frm=from_email, subject=subject, txt = body) - #mail = IETFEmailMessage(subject=subject, - # to=to_email, - # from_email=from_email, - # body = body) - #if not fake: - # mail.send() - #return mail + action_taken = models.BooleanField(default=False) - def send_by_email(self, fake=False): - 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, - 'url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=self.pk)), - 'referenced_url': settings.IDTRACKER_BASE_URL + urlreverse("liaison_detail", kwargs=dict(object_id=self.related_to.pk)) if self.related_to else None, - }) - send_mail_text(context=None,to=to_email,frm=from_email,subject=subject,txt=body,cc=cc,bcc=bcc) - #mail = IETFEmailMessage(subject=subject, - # to=to_email, - # from_email=from_email, - # cc = cc, - # bcc = bcc, - # body = body) - #if not fake: - # mail.send() - #return mail + attachments = models.ManyToManyField(Document, blank=True) - def is_pending(self): - return bool(self.approval and not self.approval.approved) - - -class SDOs(models.Model): - sdo_id = models.AutoField(primary_key=True, verbose_name='ID') - sdo_name = models.CharField(blank=True, max_length=255, verbose_name='SDO Name') - def __str__(self): - return self.sdo_name - def liaisonmanager(self): - try: - return self.liaisonmanagers_set.all()[0] - except: - return None - def sdo_contact(self): - try: - return self.sdoauthorizedindividual_set.all()[0] - except: - return None - class Meta: - verbose_name = 'SDO' - verbose_name_plural = 'SDOs' - db_table = 'sdos' - ordering = ('sdo_name', ) - liaisonmanager_link = admin_link('liaisonmanager', label='Liaison') - sdo_contact_link = admin_link('sdo_contact') - -class LiaisonStatementManager(models.Model): - person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag') - sdo = models.ForeignKey(SDOs, verbose_name='SDO') - def __unicode__(self): - return '%s (%s)' % (self.person, self.sdo) - class Meta: - abstract = True - # Helper functions, for use in the admin interface - def login_name(self): - login_name = None - try: - login_name = IESGLogin.objects.get(person=self.person).login_name - if User.objects.filter(username=login_name).count(): - return login_name - except IESGLogin.DoesNotExist: - pass - try: - login_name = LegacyLiaisonUser.objects.get(person=self.person).login_name - except LegacyLiaisonUser.DoesNotExist: - pass - return login_name - def user(self): - login_name = self.login_name() - user = None - if login_name: - try: - return User.objects.get(username=login_name), login_name - except User.DoesNotExist: - pass - return None, login_name - def user_name(self): - user, login_name = self.user() - if user: - return u'%s' % (user.id, login_name) + def name(self): + from django.template.defaultfilters import slugify + if self.from_group: + frm = self.from_group.acronym or self.from_group.name else: - if login_name: - return u'Add login: %s' % (login_name, login_name) - else: - return u'Add liaison user: %s' % (self.person.pk, self.person.email()[1], self.person, ) - - user_name.allow_tags = True - def groups(self): - user, login_name = self.user() - return ", ".join([ group.name for group in user.groups.all()]) - person_link = admin_link('person') - sdo_link = admin_link('sdo', label='SDO') - -class LiaisonManagers(LiaisonStatementManager): - email_priority = models.IntegerField(null=True, blank=True) - def email(self): - try: - return self.person.emailaddress_set.get(priority=self.email_priority) - except ObjectDoesNotExist: - return None - class Meta: - verbose_name = 'SDO Liaison Manager' - verbose_name_plural = 'SDO Liaison Managers' - db_table = 'liaison_managers' - ordering = ('sdo__sdo_name', ) + frm = self.from_name + if self.to_group: + to = self.to_group.acronym or self.to_group.name + else: + to = self.to_name + return slugify("liaison" + " " + self.submitted.strftime("%Y-%m-%d") + " " + frm[:50] + " " + to[:50] + " " + self.title[:115]) -class SDOAuthorizedIndividual(LiaisonStatementManager): - class Meta: - verbose_name = 'SDO Authorized Individual' - verbose_name_plural = 'SDO Authorized Individuals' - -# This table is not used by any code right now. -#class LiaisonsInterim(models.Model): -# title = models.CharField(blank=True, max_length=255) -# submitter_name = models.CharField(blank=True, max_length=255) -# submitter_email = models.CharField(blank=True, max_length=255) -# submitted_date = models.DateField(null=True, blank=True) -# from_id = models.IntegerField(null=True, blank=True) -# def __str__(self): -# return self.title -# class Meta: -# db_table = 'liaisons_interim' - -class Uploads(models.Model): - file_id = models.AutoField(primary_key=True) - file_title = models.CharField(blank=True, max_length=255) - person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag') - file_extension = models.CharField(blank=True, max_length=10) - 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' - -# empty table -#class SdoChairs(models.Model): -# sdo = models.ForeignKey(SDOs) -# person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag') -# email_priority = models.IntegerField(null=True, blank=True) -# class Meta: -# db_table = 'sdo_chairs' - -# changes done by convert-096.py:changed maxlength to max_length -# removed core -# removed edit_inline -# removed num_in_admin -# removed raw_id_admin - -if settings.USE_DB_REDESIGN_PROXY_CLASSES or hasattr(settings, "IMPORTING_FROM_OLD_SCHEMA"): - from ietf.name.models import LiaisonStatementPurposeName - from ietf.doc.models import Document - from ietf.person.models import Email - from ietf.group.models import Group - - class LiaisonStatement(models.Model): - title = models.CharField(blank=True, max_length=255) - purpose = models.ForeignKey(LiaisonStatementPurposeName) - body = models.TextField(blank=True) - deadline = models.DateField(null=True, blank=True) - - 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="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="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 recipient body") - - reply_to = models.CharField(blank=True, max_length=255) - - response_contact = models.CharField(blank=True, max_length=255) - technical_contact = models.CharField(blank=True, max_length=255) - cc = models.TextField(blank=True) - - submitted = models.DateTimeField(null=True, blank=True) - modified = models.DateTimeField(null=True, blank=True) - approved = models.DateTimeField(null=True, blank=True) - - action_taken = models.BooleanField(default=False) - - attachments = models.ManyToManyField(Document, blank=True) - - def name(self): - from django.template.defaultfilters import slugify - if self.from_group: - frm = self.from_group.acronym or self.from_group.name - else: - frm = self.from_name - if self.to_group: - to = self.to_group.acronym or self.to_group.name - else: - to = self.to_name - return slugify("liaison" + " " + self.submitted.strftime("%Y-%m-%d") + " " + frm[:50] + " " + to[:50] + " " + self.title[:115]) - - def __unicode__(self): - return self.title or "" - - LiaisonDetailOld = LiaisonDetail + def __unicode__(self): + return self.title or u"" diff --git a/ietf/liaisons/proxy.py b/ietf/liaisons/proxy.py deleted file mode 100644 index 9e1ca61bc..000000000 --- a/ietf/liaisons/proxy.py +++ /dev/null @@ -1,188 +0,0 @@ -from ietf.utils.proxy import TranslatingManager -from ietf.liaisons.models import LiaisonStatement -from ietf.doc.models import Document - -class LiaisonDetailProxy(LiaisonStatement): - objects = TranslatingManager(dict(submitted_date="submitted", - deadline_date="deadline", - to_body="to_name", - from_raw_body="from_name")) - - def from_object(self, base): - for f in base._meta.fields: - setattr(self, f.name, getattr(base, f.name)) - return self - - #detail_id = models.AutoField(primary_key=True) - @property - def detail_id(self): - return self.id - #person = models.ForeignKey(PersonOrOrgInfo, null=True, db_column='person_or_org_tag') - @property - def person(self): - return self.from_contact.person if self.from_contact else "" - #submitted_date = models.DateField(null=True, blank=True) - @property - def submitted_date(self): - return self.submitted.date() if self.submitted else None - #last_modified_date = models.DateField(null=True, blank=True) - @property - def last_modified_date(self): - return self.modified.date() if self.modified else None - #from_id = models.IntegerField(null=True, blank=True) - @property - def from_id(self): - return self.from_group_id - #to_body = models.CharField(blank=True, null=True, max_length=255) - @property - def to_body(self): - return self.to_name - #title = models.CharField(blank=True, null=True, max_length=255) # same name - #response_contact = models.CharField(blank=True, null=True, max_length=255) # same name - #technical_contact = models.CharField(blank=True, null=True, max_length=255) # same name - #purpose_text = models.TextField(blank=True, null=True, db_column='purpose') - @property - def purpose_text(self): - return "" - #body = models.TextField(blank=True,null=True) # same name - #deadline_date = models.DateField(null=True, blank=True) - @property - def deadline_date(self): - return self.deadline - #cc1 = models.TextField(blank=True, null=True) - @property - def cc1(self): - return self.cc - #cc2 = models.CharField(blank=True, null=True, max_length=50) # unused - @property - def cc2(self): - return "" - #submitter_name = models.CharField(blank=True, null=True, max_length=255) - @property - def submitter_name(self): - i = self.to_name.find('<') - if i > 0: - return self.to_name[:i - 1] - else: - return self.to_name - #submitter_email = models.CharField(blank=True, null=True, max_length=255) - @property - def submitter_email(self): - import re - re_email = re.compile("<(.*)>") - match = re_email.search(self.to_name) - if match: - return match.group(1) - else: - return "" - #by_secretariat = models.IntegerField(null=True, blank=True) - @property - def by_secretariat(self): - return not self.from_contact - #to_poc = models.CharField(blank=True, null=True, max_length=255) - @property - def to_poc(self): - return self.to_contact - #to_email = models.CharField(blank=True, null=True, max_length=255) - @property - def to_email(self): - return "" - #purpose = models.ForeignKey(LiaisonPurpose,null=True) - #replyto = models.CharField(blank=True, null=True, max_length=255) - @property - def replyto(self): - return self.reply_to - #from_raw_body = models.CharField(blank=True, null=True, max_length=255) - @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.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.raw_codify(self.to_group) - #approval = models.ForeignKey(OutgoingLiaisonApproval, blank=True, null=True) - @property - def approval(self): - return bool(self.approved) - #action_taken = models.BooleanField(default=False, db_column='taken_care') # same name - #related_to = models.ForeignKey('LiaisonDetail', blank=True, null=True) # same name - - @property - def uploads_set(self): - return UploadsProxy.objects.filter(liaisonstatement=self).order_by("name", "external_url") - - @property - def liaisondetail_set(self): - return self.liaisonstatement_set - - def __str__(self): - return unicode(self) - def __unicode__(self): - return self.title or "" - def from_body(self): - return self.from_name - def from_sdo(self): - return self.from_group if self.from_group and self.from_group.type_id == "sdo" else None - def from_email(self): - return self.from_contact.address - def get_absolute_url(self): - return '/liaison/%d/' % self.detail_id - class Meta: - proxy = True - - def send_by_email(self, fake=False): - # grab this from module instead of stuffing in on the model - from ietf.liaisons.mails import send_liaison_by_email - # we don't have a request so just pass None for the time being - return send_liaison_by_email(None, self, fake) - - def notify_pending_by_email(self, fake=False): - # grab this from module instead of stuffing in on the model - from ietf.liaisons.mails import notify_pending_by_email - # we don't have a request so just pass None for the time being - return notify_pending_by_email(None, self, fake) - - def is_pending(self): - return not self.approved - -class UploadsProxy(Document): - #file_id = models.AutoField(primary_key=True) - @property - def file_id(self): - if not self.external_url or 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): - return self.title - #person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag') - #file_extension = models.CharField(blank=True, max_length=10) - @property - def file_extension(self): - t = self.external_url.split(".") - if len(t) > 1: - return "." + t[1] - else: - return "" - #detail = models.ForeignKey(LiaisonDetail) - @property - def detail(self): - return self.liaisonstatement_set.all()[0] - def filename(self): - return self.external_url - class Meta: - proxy = True diff --git a/ietf/liaisons/sitemaps.py b/ietf/liaisons/sitemaps.py index 156ffed4c..161fed1c3 100644 --- a/ietf/liaisons/sitemaps.py +++ b/ietf/liaisons/sitemaps.py @@ -2,16 +2,17 @@ # from django.contrib.sitemaps import Sitemap from django.conf import settings -from ietf.liaisons.models import LiaisonDetail -if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.liaisons.proxy import LiaisonDetailProxy as LiaisonDetail +from ietf.liaisons.models import LiaisonStatement class LiaisonMap(Sitemap): changefreq = "never" + def items(self): - return LiaisonDetail.objects.all() + return LiaisonStatement.objects.all() + def location(self, obj): - return "/liaison/%d/" % obj.detail_id + return "/liaison/%s/" % obj.pk + def lastmod(self, obj): - return obj.last_modified_date + return obj.modified diff --git a/ietf/liaisons/tests.py b/ietf/liaisons/tests.py index bd7da0995..e358b3176 100644 --- a/ietf/liaisons/tests.py +++ b/ietf/liaisons/tests.py @@ -11,6 +11,11 @@ from ietf.utils.test_data import make_test_data from ietf.utils.mail import outbox from ietf.utils import TestCase +from ietf.liaisons.models import LiaisonStatement, LiaisonStatementPurposeName +from ietf.person.models import Person, Email +from ietf.group.models import Group, Role +from ietf.liaisons.mails import send_sdo_reminder, possibly_send_deadline_reminder + class LiaisonsUrlTestCase(SimpleUrlTestCase): def testUrls(self): self.doTestUrls(__file__) @@ -22,11 +27,6 @@ class LiaisonsUrlTestCase(SimpleUrlTestCase): else: return content -if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.liaisons.models import LiaisonStatement, LiaisonStatementPurposeName - from ietf.person.models import Person, Email - from ietf.group.models import Group, Role - def make_liaison_models(): sdo = Group.objects.create( name="United League of Marsmen", @@ -89,7 +89,58 @@ def make_liaison_models(): action_taken=False, ) return l - + +class LiaisonTests(TestCase): + def test_overview(self): + make_test_data() + liaison = make_liaison_models() + + r = self.client.get(urlreverse('liaison_list')) + self.assertEqual(r.status_code, 200) + self.assertTrue(liaison.title in r.content) + + def test_details(self): + make_test_data() + liaison = make_liaison_models() + + r = self.client.get(urlreverse("liaison_detail", kwargs={ 'object_id': liaison.pk })) + self.assertEqual(r.status_code, 200) + self.assertTrue(liaison.title in r.content) + + def test_feeds(self): + make_test_data() + liaison = make_liaison_models() + + r = self.client.get('/feed/liaison/recent/') + self.assertEqual(r.status_code, 200) + self.assertTrue(liaison.title in r.content) + + r = self.client.get('/feed/liaison/from/%s/' % liaison.from_group.acronym) + self.assertEqual(r.status_code, 200) + self.assertTrue(liaison.title in r.content) + + r = self.client.get('/feed/liaison/to/%s/' % liaison.to_name) + self.assertEqual(r.status_code, 200) + self.assertTrue(liaison.title in r.content) + + r = self.client.get('/feed/liaison/subject/marsmen/') + self.assertEqual(r.status_code, 200) + self.assertTrue(liaison.title in r.content) + + def test_sitemap(self): + make_test_data() + liaison = make_liaison_models() + + r = self.client.get('/sitemap-liaison.xml') + self.assertEqual(r.status_code, 200) + self.assertTrue(urlreverse("liaison_detail", kwargs={ 'object_id': liaison.pk }) in r.content) + + def test_help_pages(self): + self.assertEqual(self.client.get('/liaison/help/').status_code, 200) + self.assertEqual(self.client.get('/liaison/help/fields/').status_code, 200) + self.assertEqual(self.client.get('/liaison/help/from_ietf/').status_code, 200) + self.assertEqual(self.client.get('/liaison/help/to_ietf/').status_code, 200) + class LiaisonManagementTests(TestCase): def setUp(self): @@ -114,52 +165,63 @@ class LiaisonManagementTests(TestCase): url = urlreverse('liaison_detail', kwargs=dict(object_id=liaison.pk)) # normal get r = self.client.get(url) - self.assertEquals(r.status_code, 200) + self.assertEqual(r.status_code, 200) q = PyQuery(r.content) - self.assertEquals(len(q('form input[name=do_action_taken]')), 0) + self.assertEqual(len(q('form input[name=do_action_taken]')), 0) # log in and get self.client.login(remote_user="secretary") r = self.client.get(url) - self.assertEquals(r.status_code, 200) + self.assertEqual(r.status_code, 200) q = PyQuery(r.content) - self.assertEquals(len(q('form input[name=do_action_taken]')), 1) + self.assertEqual(len(q('form input[name=do_action_taken]')), 1) # mark action taken r = self.client.post(url, dict(do_action_taken="1")) - self.assertEquals(r.status_code, 200) + self.assertEqual(r.status_code, 200) q = PyQuery(r.content) - self.assertEquals(len(q('form input[name=do_action_taken]')), 0) + self.assertEqual(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): + def test_approval_process(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)) + + # check the overview page + url = urlreverse('liaison_approval_list') # this liaison is for a WG so we need the AD for the area login_testing_unauthorized(self, "ad", url) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + self.assertTrue(liaison.title in r.content) + + # check detail page + url = urlreverse('liaison_approval_detail', kwargs=dict(object_id=liaison.pk)) + self.client.logout() + login_testing_unauthorized(self, "ad", url) + # normal get r = self.client.get(url) - self.assertEquals(r.status_code, 200) + self.assertEqual(r.status_code, 200) + self.assertTrue(liaison.title in r.content) q = PyQuery(r.content) - self.assertEquals(len(q('form input[name=do_approval]')), 1) + self.assertEqual(len(q('form input[name=do_approval]')), 1) # approve mailbox_before = len(outbox) r = self.client.post(url, dict(do_approval="1")) - self.assertEquals(r.status_code, 302) + self.assertEqual(r.status_code, 302) liaison = LiaisonStatement.objects.get(id=liaison.id) self.assertTrue(liaison.approved) - self.assertEquals(len(outbox), mailbox_before + 1) + self.assertEqual(len(outbox), mailbox_before + 1) self.assertTrue("Liaison Statement" in outbox[-1]["Subject"]) def test_edit_liaison(self): @@ -171,9 +233,9 @@ class LiaisonManagementTests(TestCase): # get r = self.client.get(url) - self.assertEquals(r.status_code, 200) + self.assertEqual(r.status_code, 200) q = PyQuery(r.content) - self.assertEquals(len(q('form input[name=from_field]')), 1) + self.assertEqual(len(q('form input[name=from_field]')), 1) # edit attachments_before = liaison.attachments.count() @@ -195,30 +257,30 @@ class LiaisonManagementTests(TestCase): attach_file_1=test_file, attach_title_1="attachment", )) - self.assertEquals(r.status_code, 302) + self.assertEqual(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") - self.assertEquals(new_liaison.to_name, "org") - 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, "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") - self.assertEquals(new_liaison.submitted.date(), (liaison.submitted + datetime.timedelta(days=1)).date()) - self.assertEquals(new_liaison.body, "body") + self.assertEqual(new_liaison.from_name, "from") + self.assertEqual(new_liaison.reply_to, "replyto@example.com") + self.assertEqual(new_liaison.to_name, "org") + self.assertEqual(new_liaison.to_contact, "to_poc@example.com") + self.assertEqual(new_liaison.response_contact, "responce_contact@example.com") + self.assertEqual(new_liaison.technical_contact, "technical_contact@example.com") + self.assertEqual(new_liaison.cc, "cc@example.com") + self.assertEqual(new_liaison.purpose, LiaisonStatementPurposeName.objects.get(order=4)) + self.assertEqual(new_liaison.deadline, liaison.deadline + datetime.timedelta(days=1)), + self.assertEqual(new_liaison.title, "title") + self.assertEqual(new_liaison.submitted.date(), (liaison.submitted + datetime.timedelta(days=1)).date()) + self.assertEqual(new_liaison.body, "body") - self.assertEquals(new_liaison.attachments.count(), attachments_before + 1) + self.assertEqual(new_liaison.attachments.count(), attachments_before + 1) attachment = new_liaison.attachments.order_by("-name")[0] - self.assertEquals(attachment.title, "attachment") + self.assertEqual(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()) + self.assertEqual(written_content, test_file.read()) def test_add_incoming_liaison(self): make_test_data() @@ -229,9 +291,9 @@ class LiaisonManagementTests(TestCase): # get r = self.client.get(url) - self.assertEquals(r.status_code, 200) + self.assertEqual(r.status_code, 200) q = PyQuery(r.content) - self.assertEquals(len(q('form textarea[name=body]')), 1) + self.assertEqual(len(q('form textarea[name=body]')), 1) # add new mailbox_before = len(outbox) @@ -260,34 +322,34 @@ class LiaisonManagementTests(TestCase): attach_title_1="attachment", send="1", )) - self.assertEquals(r.status_code, 302) + self.assertEqual(r.status_code, 302) l = LiaisonStatement.objects.all().order_by("-id")[0] - self.assertEquals(l.from_group, from_group) - self.assertEquals(l.from_contact.address, 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.assertEqual(l.from_group, from_group) + self.assertEqual(l.from_contact.address, submitter.email_address()) + self.assertEqual(l.reply_to, "replyto@example.com") + self.assertEqual(l.to_group, to_group) + self.assertEqual(l.response_contact, "responce_contact@example.com") + self.assertEqual(l.technical_contact, "technical_contact@example.com") + self.assertEqual(l.cc, "cc@example.com") + self.assertEqual(l.purpose, LiaisonStatementPurposeName.objects.get(order=4)) + self.assertEqual(l.deadline, today + datetime.timedelta(days=1)), + self.assertEqual(l.related_to, liaison), + self.assertEqual(l.title, "title") + self.assertEqual(l.submitted.date(), today) + self.assertEqual(l.body, "body") self.assertTrue(l.approved) - self.assertEquals(l.attachments.count(), 1) + self.assertEqual(l.attachments.count(), 1) attachment = l.attachments.all()[0] - self.assertEquals(attachment.title, "attachment") + self.assertEqual(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()) + self.assertEqual(written_content, test_file.read()) - self.assertEquals(len(outbox), mailbox_before + 1) + self.assertEqual(len(outbox), mailbox_before + 1) self.assertTrue("Liaison Statement" in outbox[-1]["Subject"]) def test_add_outgoing_liaison(self): @@ -299,9 +361,9 @@ class LiaisonManagementTests(TestCase): # get r = self.client.get(url) - self.assertEquals(r.status_code, 200) + self.assertEqual(r.status_code, 200) q = PyQuery(r.content) - self.assertEquals(len(q('form textarea[name=body]')), 1) + self.assertEqual(len(q('form textarea[name=body]')), 1) # add new mailbox_before = len(outbox) @@ -333,35 +395,35 @@ class LiaisonManagementTests(TestCase): attach_title_1="attachment", send="1", )) - self.assertEquals(r.status_code, 302) + self.assertEqual(r.status_code, 302) l = LiaisonStatement.objects.all().order_by("-id")[0] - self.assertEquals(l.from_group, from_group) - self.assertEquals(l.from_contact.address, 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.assertEqual(l.from_group, from_group) + self.assertEqual(l.from_contact.address, submitter.email_address()) + self.assertEqual(l.reply_to, "replyto@example.com") + self.assertEqual(l.to_group, to_group) + self.assertEqual(l.to_contact, "to_poc@example.com") + self.assertEqual(l.response_contact, "responce_contact@example.com") + self.assertEqual(l.technical_contact, "technical_contact@example.com") + self.assertEqual(l.cc, "cc@example.com") + self.assertEqual(l.purpose, LiaisonStatementPurposeName.objects.get(order=4)) + self.assertEqual(l.deadline, today + datetime.timedelta(days=1)), + self.assertEqual(l.related_to, liaison), + self.assertEqual(l.title, "title") + self.assertEqual(l.submitted.date(), today) + self.assertEqual(l.body, "body") self.assertTrue(not l.approved) - self.assertEquals(l.attachments.count(), 1) + self.assertEqual(l.attachments.count(), 1) attachment = l.attachments.all()[0] - self.assertEquals(attachment.title, "attachment") + self.assertEqual(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()) + self.assertEqual(written_content, test_file.read()) - self.assertEquals(len(outbox), mailbox_before + 1) + self.assertEqual(len(outbox), mailbox_before + 1) self.assertTrue("Liaison Statement" in outbox[-1]["Subject"]) # try adding statement to non-predefined organization @@ -383,46 +445,34 @@ class LiaisonManagementTests(TestCase): submitted_date=today.strftime("%Y-%m-%d"), body="body", )) - self.assertEquals(r.status_code, 302) + self.assertEqual(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") + self.assertEqual(l.to_group, None) + self.assertEqual(l.to_name, "Mars Institute") def test_send_sdo_reminder(self): make_test_data() liaison = make_liaison_models() - from ietf.liaisons.mails import send_sdo_reminder - mailbox_before = len(outbox) send_sdo_reminder(Group.objects.filter(type="sdo")[0]) - self.assertEquals(len(outbox), mailbox_before + 1) + self.assertEqual(len(outbox), mailbox_before + 1) self.assertTrue("authorized individuals" in outbox[-1]["Subject"]) def test_send_liaison_deadline_reminder(self): make_test_data() liaison = make_liaison_models() - from ietf.liaisons.mails import possibly_send_deadline_reminder - from ietf.liaisons.proxy import LiaisonDetailProxy as LiaisonDetail - - l = LiaisonDetail.objects.all()[0] - mailbox_before = len(outbox) - possibly_send_deadline_reminder(l) - self.assertEquals(len(outbox), mailbox_before + 1) + possibly_send_deadline_reminder(liaison) + self.assertEqual(len(outbox), mailbox_before + 1) self.assertTrue("deadline" in outbox[-1]["Subject"]) # try pushing the deadline - l.deadline = l.deadline + datetime.timedelta(days=30) - l.save() + liaison.deadline = liaison.deadline + datetime.timedelta(days=30) + liaison.save() mailbox_before = len(outbox) - possibly_send_deadline_reminder(l) - self.assertEquals(len(outbox), mailbox_before) - - -if not settings.USE_DB_REDESIGN_PROXY_CLASSES: - # the above tests only work with the new schema - del LiaisonManagementTestCase + possibly_send_deadline_reminder(liaison) + self.assertEqual(len(outbox), mailbox_before) diff --git a/ietf/liaisons/testurl.list b/ietf/liaisons/testurl.list deleted file mode 100644 index 59ca172cd..000000000 --- a/ietf/liaisons/testurl.list +++ /dev/null @@ -1,33 +0,0 @@ -200 /liaison/ -200 /liaison/321/ -200 /liaison/553/ # submitted by email -200 /liaison/337/ # test case for ticket #182 -200 /liaison/458/ # non-ASCII title -200 /liaison/471/ # non-ASCII body and submitter name - -301 /liaison/managers/ - -200 /liaison/help/to_ietf/ -200 /liaison/help/from_ietf/ -200 /liaison/help/fields/ -200 /liaison/help/ - -404 /feed/liaison/ -200 /feed/liaison/recent/ -200 /feed/liaison/from/ccamp/ -#200 /feed/liaison/from/MFA%20Forum/ -200 /feed/liaison/from/MFA_Forum/ -200 /feed/liaison/to/ccamp/ -200 /feed/liaison/subject/H.248/ -200 /feed/liaison/subject/2173/ # non-ASCII title - -404 /feed/liaison/recent/foobar/ -404 /feed/liaison/from/ -404 /feed/liaison/from/nosuchorganization/ -404 /feed/liaison/from/foo/bar/ -404 /feed/liaison/to/ -404 /feed/liaison/to/foo/bar/ -404 /feed/liaison/subject/ -404 /feed/liaison/subject/foo/bar/ - -200 /sitemap-liaison.xml diff --git a/ietf/liaisons/urls.py b/ietf/liaisons/urls.py index a9ddb236b..f09523884 100644 --- a/ietf/liaisons/urls.py +++ b/ietf/liaisons/urls.py @@ -2,17 +2,8 @@ from django.conf.urls.defaults import patterns, url from django.db.models import Q -from ietf.liaisons.models import LiaisonDetail -info_dict = { - 'queryset': LiaisonDetail.objects.filter(Q(approval__isnull=True)|Q(approval__approved=True)).order_by("-submitted_date"), -} - -# there's an opportunity for date-based filtering. -urlpatterns = patterns('django.views.generic.list_detail', -) - -urlpatterns += patterns('django.views.generic.simple', +urlpatterns = patterns('django.views.generic.simple', (r'^help/$', 'direct_to_template', {'template': 'liaisons/help.html'}), (r'^help/fields/$', 'direct_to_template', {'template': 'liaisons/field_help.html'}), (r'^help/from_ietf/$', 'direct_to_template', {'template': 'liaisons/guide_from_ietf.html'}), diff --git a/ietf/liaisons/utils.py b/ietf/liaisons/utils.py index 0d8b48c59..ec5d2a2c7 100644 --- a/ietf/liaisons/utils.py +++ b/ietf/liaisons/utils.py @@ -1,10 +1,43 @@ -from django.conf import settings +from django.db.models import Q -from ietf.idtracker.models import Area, IETFWG -from ietf.liaisons.models import SDOs, LiaisonManagers -from ietf.liaisons.accounts import (is_ietfchair, is_iabchair, is_iab_executive_director, is_irtfchair, +from ietf.group.models import Group, Role +from ietf.person.models import Person +from ietf.liaisons.models import LiaisonStatement +from ietf.ietfauth.utils import has_role, passes_test_decorator +from ietf.utils.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) + is_secretariat, can_add_liaison, get_person_for_user) + +can_submit_liaison_required = passes_test_decorator( + lambda u, *args, **kwargs: can_add_liaison(u), + "Restricted to participants who are authorized to submit liaison statements on behalf of the various IETF entities") + +def approvable_liaison_statements(user): + liaisons = LiaisonStatement.objects.filter(approved=None) + if has_role(user, "Secretariat"): + return liaisons + + # this is a bit complicated because IETFHM encodes the + # groups, it should just give us a list of ids or acronyms + group_codes = IETFHM.get_all_can_approve_codes(get_person_for_user(user)) + group_acronyms = [] + group_ids = [] + for x in group_codes: + if "_" in x: + group_ids.append(x.split("_")[1]) + else: + group_acronyms.append(x) + + return liaisons.filter(Q(from_group__acronym__in=group_acronyms) | Q(from_group__pk__in=group_ids)) + + +# the following is a biggish object hierarchy abstracting the entity +# names and auth rules for posting liaison statements in a sort of +# semi-declarational (and perhaps overengineered given the revamped +# schema) way - unfortunately, it's never been strong enough to do so +# fine-grained enough so the form code also has some rules IETFCHAIR = {'name': u'The IETF Chair', 'address': u'chair@ietf.org'} IESG = {'name': u'The IESG', 'address': u'iesg@ietf.org'} @@ -12,10 +45,7 @@ IAB = {'name': u'The IAB', 'address': u'iab@iab.org'} IABCHAIR = {'name': u'The IAB Chair', 'address': u'iab-chair@iab.org'} IABEXECUTIVEDIRECTOR = {'name': u'The IAB Executive Director', 'address': u'execd@iab.org'} IRTFCHAIR = {'name': u'The IRTF Chair', 'address': u'irtf-chair@irtf.org'} - - -def get_all_sdo_managers(): - return [i.person for i in LiaisonManagers.objects.all().distinct()] +IESGANDIAB = {'name': u'The IESG and IAB', 'address': u'iesg-iab@ietf.org'} class FakePerson(object): @@ -27,7 +57,12 @@ class FakePerson(object): def email(self): return (self.name, self.address) +def all_sdo_managers(): + return [proxy_personify_role(r) for r in Role.objects.filter(group__type="sdo", name="liaiman").select_related("person").distinct()] +def role_persons_with_fixed_email(group, role_name): + return [proxy_personify_role(r) for r in Role.objects.filter(group=group, name=role_name).select_related("person").distinct()] + class Entity(object): poc = [] @@ -84,34 +119,11 @@ 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 -class IRTFEntity(Entity): - - poc = FakePerson(**IRTFCHAIR) - - def get_from_cc(self, person): - result = [] - if not is_irtfchair(person): - result.append(self.poc) - return result - - def needs_approval(self, person=None): - if is_irtfchair(person): - return False - return True - - def can_approve(self): - return [self.poc] - - def full_user_list(self): - result.append(get_irtf_chair()) - return result - - class IABEntity(Entity): chair = FakePerson(**IABCHAIR) director = FakePerson(**IABEXECUTIVEDIRECTOR) @@ -136,27 +148,75 @@ 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 +class IRTFEntity(Entity): + chair = FakePerson(**IRTFCHAIR) + poc = [chair,] + + def get_from_cc(self, person): + result = [] + return result + + def needs_approval(self, person=None): + if is_irtfchair(person): + return False + return True + + def can_approve(self): + return [self.chair] + + def full_user_list(self): + result = [get_irtf_chair()] + return result + + +class IAB_IESG_Entity(Entity): + + poc = [IABEntity.chair, IABEntity.director, FakePerson(**IETFCHAIR), FakePerson(**IESGANDIAB), ] + cc = [FakePerson(**IAB), FakePerson(**IESG), FakePerson(**IESGANDIAB)] + + def __init__(self, name, obj=None): + self.name = name + self.obj = obj + self.iab = IABEntity(name, obj) + self.iesg = IETFEntity(name, obj) + + def get_from_cc(self, person): + return list(set(self.iab.get_from_cc(person) + self.iesg.get_from_cc(person))) + + def needs_approval(self, person=None): + if not self.iab.needs_approval(person): + return False + if not self.iesg.needs_approval(person): + return False + return True + + def can_approve(self): + return list(set(self.iab.can_approve() + self.iesg.can_approve())) + + def full_user_list(self): + return [get_ietf_chair(), get_iab_chair(), get_iab_executive_director()] + 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)] def get_from_cc(self, person): - result = [i.person for i in self.obj.areadirector_set.all() if i.person!=person] + result = [p for p in role_persons_with_fixed_email(self.obj, "ad") if p != person] result.append(FakePerson(**IETFCHAIR)) return result def needs_approval(self, person=None): # Check if person is an area director - if self.obj.areadirector_set.filter(person=person): + if self.obj.role_set.filter(person=person, name="ad"): return False return True @@ -164,7 +224,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 @@ -172,34 +232,37 @@ class AreaEntity(Entity): class WGEntity(Entity): def get_poc(self): - return [i.person for i in self.obj.wgchair_set.all()] + return role_persons_with_fixed_email(self.obj, "chair") def get_cc(self, person=None): - result = [i.person for i in self.obj.area_directors()] - if self.obj.email_address: - result.append(FakePerson(name ='%s Discussion List' % self.obj.group_acronym.name, - address = self.obj.email_address)) + if self.obj.parent: + result = [p for p in role_persons_with_fixed_email(self.obj.parent, "ad") if p != person] + else: + result = [] + if self.obj.list_subscribe: + result.append(FakePerson(name ='%s Discussion List' % self.obj.name, + address = self.obj.list_subscribe)) return result def get_from_cc(self, person): - result = [i.person for i in self.obj.wgchair_set.all() if i.person!=person] - result += [i.person for i in self.obj.area_directors()] - if self.obj.email_address: - result.append(FakePerson(name ='%s Discussion List' % self.obj.group_acronym.name, - address = self.obj.email_address)) + result = [p for p in role_persons_with_fixed_email(self.obj, "chair") if p != person] + result += role_persons_with_fixed_email(self.obj.parent, "ad") if self.obj.parent else [] + if self.obj.list_subscribe: + result.append(FakePerson(name ='%s Discussion List' % self.obj.name, + address = self.obj.list_subscribe)) return result def needs_approval(self, person=None): # Check if person is director of this wg area - if self.obj.area.area.areadirector_set.filter(person=person): + if self.obj.parent and self.obj.parent.role_set.filter(person=person, name="ad"): return False return True def can_approve(self): - return [i.person for i in self.obj.area.area.areadirector_set.all()] + 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 @@ -210,25 +273,19 @@ class SDOEntity(Entity): return [] def get_cc(self, person=None): - manager = self.obj.liaisonmanager() - if manager: - return [manager.person] - return [] + return role_persons_with_fixed_email(self.obj, "liaiman") def get_from_cc(self, person=None): - manager = self.obj.liaisonmanager() - if manager and manager.person!=person: - return [manager.person] - return [] + return [p for p in role_persons_with_fixed_email(self.obj, "liaiman") if p != person] def post_only(self, person, user): - if is_secretariat(user) or person.sdoauthorizedindividual_set.filter(sdo=self.obj): + if is_secretariat(user) or self.obj.role_set.filter(person=person, name="auth"): return False return True def full_user_list(self): - result = [i.person for i in self.obj.liaisonmanagers_set.all().distinct()] - result += [i.person for i in self.obj.sdoauthorizedindividual_set.all().distinct()] + result = role_persons_with_fixed_email(self.obj, "liaiman") + result += role_persons_with_fixed_email(self.obj, "auth") return result @@ -314,10 +371,35 @@ class IRTFEntityManager(EntityManager): return [] +class IAB_IESG_EntityManager(EntityManager): + + def __init__(self, *args, **kwargs): + super(IAB_IESG_EntityManager, self).__init__(*args, **kwargs) + self.entity = IAB_IESG_Entity(name=self.name) + + def get_entity(self, pk=None): + return self.entity + + def can_send_on_behalf(self, person): + if (is_iabchair(person) or + is_iab_executive_director(person) or + is_ietfchair(person)): + return self.get_managed_list() + return [] + + def can_approve_list(self, person): + if (is_iabchair(person) or + is_iab_executive_director(person) or + is_ietfchair(person)): + return self.get_managed_list() + return [] + + class AreaEntityManager(EntityManager): def __init__(self, pk=None, name=None, queryset=None): super(AreaEntityManager, self).__init__(pk, name, queryset) + from ietf.group.proxy import Area if self.queryset == None: self.queryset = Area.active_areas() @@ -336,11 +418,11 @@ class AreaEntityManager(EntityManager): return AreaEntity(name=obj.area_acronym.name, obj=obj) def can_send_on_behalf(self, person): - query_filter = {'areadirector__in': person.areadirector_set.all()} + query_filter = dict(role__person=person, role__name="ad") return self.get_managed_list(query_filter) def can_approve_list(self, person): - query_filter = {'areadirector__in': person.areadirector_set.all()} + query_filter = dict(role__person=person, role__name="ad") return self.get_managed_list(query_filter) @@ -349,6 +431,7 @@ class WGEntityManager(EntityManager): def __init__(self, pk=None, name=None, queryset=None): super(WGEntityManager, self).__init__(pk, name, queryset) if self.queryset == None: + from ietf.group.proxy import IETFWG, Area self.queryset = IETFWG.objects.filter(group_type=1, status=IETFWG.ACTIVE, areagroup__area__status=Area.ACTIVE) def get_managed_list(self, query_filter=None): @@ -366,13 +449,12 @@ 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__person=person, role__name__in=("chair", "secretary")).values_list('pk', flat=True) query_filter = {'pk__in': wgs} return self.get_managed_list(query_filter) def can_approve_list(self, person): - query_filter = {'areagroup__area__areadirector__in': person.areadirector_set.all()} + query_filter = dict(parent__role__person=person, parent__role__name="ad") return self.get_managed_list(query_filter) @@ -381,10 +463,10 @@ class SDOEntityManager(EntityManager): def __init__(self, pk=None, name=None, queryset=None): super(SDOEntityManager, self).__init__(pk, name, queryset) if self.queryset == None: - self.queryset = SDOs.objects.all() + self.queryset = Group.objects.filter(type="sdo") def get_managed_list(self): - return [(u'%s_%s' % (self.pk, i.pk), i.sdo_name) for i in self.queryset.order_by('sdo_name')] + return [(u'%s_%s' % (self.pk, i.pk), i.name) for i in self.queryset.order_by('name')] def get_entity(self, pk=None): if not pk: @@ -393,7 +475,7 @@ class SDOEntityManager(EntityManager): obj = self.queryset.get(pk=pk) except self.queryset.model.DoesNotExist: return None - return SDOEntity(name=obj.sdo_name, obj=obj) + return SDOEntity(name=obj.name, obj=obj) class IETFHierarchyManager(object): @@ -402,7 +484,7 @@ class IETFHierarchyManager(object): self.managers = {'ietf': IETFEntityManager(pk='ietf', name=u'The IETF'), 'iesg': IETFEntityManager(pk='iesg', name=u'The IESG'), 'iab': IABEntityManager(pk='iab', name=u'The IAB'), - 'irtf': IRTFEntityManager(pk='irtf', name=u'The IAB'), + 'iabiesg': IAB_IESG_EntityManager(pk='iabiesg', name=u'The IESG and the IAB'), 'area': AreaEntityManager(pk='area', name=u'IETF Areas'), 'wg': WGEntityManager(pk='wg', name=u'IETF Working Groups'), 'sdo': SDOEntityManager(pk='sdo', name=u'Standards Development Organizations'), @@ -430,7 +512,7 @@ class IETFHierarchyManager(object): def get_all_incoming_entities(self): entities = [] results = [] - for key in ['ietf', 'iesg', 'iab']: + for key in ['ietf', 'iesg', 'iab', 'iabiesg']: results += self.managers[key].get_managed_list() entities.append(('Main IETF Entities', results)) entities.append(('IETF Areas', self.managers['area'].get_managed_list())) @@ -445,7 +527,7 @@ class IETFHierarchyManager(object): def get_entities_for_person(self, person): entities = [] results = [] - for key in ['ietf', 'iesg', 'iab']: + for key in ['ietf', 'iesg', 'iab', 'iabiesg']: results += self.managers[key].can_send_on_behalf(person) if results: entities.append(('Main IETF Entities', results)) @@ -459,7 +541,7 @@ class IETFHierarchyManager(object): def get_all_can_approve_codes(self, person): entities = [] - for key in ['ietf', 'iesg', 'iab']: + for key in ['ietf', 'iesg', 'iab', 'iabiesg']: entities += self.managers[key].can_approve_list(person) entities += self.managers['area'].can_approve_list(person) entities += self.managers['wg'].can_approve_list(person) @@ -467,6 +549,3 @@ class IETFHierarchyManager(object): IETFHM = IETFHierarchyManager() - -if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from utilsREDESIGN import * diff --git a/ietf/liaisons/utilsREDESIGN.py b/ietf/liaisons/utilsREDESIGN.py deleted file mode 100644 index 53668003c..000000000 --- a/ietf/liaisons/utilsREDESIGN.py +++ /dev/null @@ -1,524 +0,0 @@ -from ietf.group.models import Group, Role -from ietf.person.models import Person -from ietf.utils.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) - -IETFCHAIR = {'name': u'The IETF Chair', 'address': u'chair@ietf.org'} -IESG = {'name': u'The IESG', 'address': u'iesg@ietf.org'} -IAB = {'name': u'The IAB', 'address': u'iab@iab.org'} -IABCHAIR = {'name': u'The IAB Chair', 'address': u'iab-chair@iab.org'} -IABEXECUTIVEDIRECTOR = {'name': u'The IAB Executive Director', 'address': u'execd@iab.org'} -IRTFCHAIR = {'name': u'The IRTF Chair', 'address': u'irtf-chair@irtf.org'} -IESGANDIAB = {'name': u'The IESG and IAB', 'address': u'iesg-iab@ietf.org'} - - -class FakePerson(object): - - def __init__(self, name, address): - self.name = name - self.address = address - - def email(self): - return (self.name, self.address) - -# the following is a biggish object hierarchy abstracting the entity -# names and auth rules for posting liaison statements in a sort of -# semi-declarational (and perhaps overengineered given the revamped -# 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("person").distinct()] - -def role_persons_with_fixed_email(group, role_name): - return [proxy_personify_role(r) for r in Role.objects.filter(group=group, name=role_name).select_related("person").distinct()] - -class Entity(object): - - poc = [] - cc = [] - - def __init__(self, name, obj=None): - self.name = name - self.obj = obj - - def get_poc(self): - if not isinstance(self.poc, list): - return [self.poc] - return self.poc - - def get_cc(self, person=None): - if not isinstance(self.cc, list): - return [self.cc] - return self.cc - - def get_from_cc(self, person=None): - return [] - - def needs_approval(self, person=None): - return False - - def can_approve(self): - return [] - - def post_only(self, person, user): - return False - - def full_user_list(self): - return False - - -class IETFEntity(Entity): - - poc = FakePerson(**IETFCHAIR) - cc = FakePerson(**IESG) - - def get_from_cc(self, person): - result = [] - if not is_ietfchair(person): - result.append(self.poc) - result.append(self.cc) - return result - - def needs_approval(self, person=None): - if is_ietfchair(person): - return False - return True - - def can_approve(self): - return [self.poc] - - def full_user_list(self): - result = all_sdo_managers() - result.append(get_ietf_chair()) - return result - - -class IABEntity(Entity): - chair = FakePerson(**IABCHAIR) - director = FakePerson(**IABEXECUTIVEDIRECTOR) - poc = [chair, director] - cc = FakePerson(**IAB) - - def get_from_cc(self, person): - result = [] - if not is_iabchair(person): - result.append(self.chair) - result.append(self.cc) - if not is_iab_executive_director(person): - result.append(self.director) - return result - - def needs_approval(self, person=None): - if is_iabchair(person) or is_iab_executive_director(person): - return False - return True - - def can_approve(self): - return [self.chair] - - def full_user_list(self): - result = all_sdo_managers() - result += [get_iab_chair(), get_iab_executive_director()] - return result - - -class IRTFEntity(Entity): - chair = FakePerson(**IRTFCHAIR) - poc = [chair,] - - def get_from_cc(self, person): - result = [] - return result - - def needs_approval(self, person=None): - if is_irtfchair(person): - return False - return True - - def can_approve(self): - return [self.chair] - - def full_user_list(self): - result = [get_irtf_chair()] - return result - - -class IAB_IESG_Entity(Entity): - - poc = [IABEntity.chair, IABEntity.director, FakePerson(**IETFCHAIR), FakePerson(**IESGANDIAB), ] - cc = [FakePerson(**IAB), FakePerson(**IESG), FakePerson(**IESGANDIAB)] - - def __init__(self, name, obj=None): - self.name = name - self.obj = obj - self.iab = IABEntity(name, obj) - self.iesg = IETFEntity(name, obj) - - def get_from_cc(self, person): - return list(set(self.iab.get_from_cc(person) + self.iesg.get_from_cc(person))) - - def needs_approval(self, person=None): - if not self.iab.needs_approval(person): - return False - if not self.iesg.needs_approval(person): - return False - return True - - def can_approve(self): - return list(set(self.iab.can_approve() + self.iesg.can_approve())) - - def full_user_list(self): - return [get_ietf_chair(), get_iab_chair(), get_iab_executive_director()] - -class AreaEntity(Entity): - - def get_poc(self): - return role_persons_with_fixed_email(self.obj, "ad") - - def get_cc(self, person=None): - return [FakePerson(**IETFCHAIR)] - - def get_from_cc(self, person): - result = [p for p in role_persons_with_fixed_email(self.obj, "ad") if p != person] - result.append(FakePerson(**IETFCHAIR)) - return result - - def needs_approval(self, person=None): - # Check if person is an area director - if self.obj.role_set.filter(person=person, name="ad"): - return False - return True - - def can_approve(self): - return self.get_poc() - - def full_user_list(self): - result = all_sdo_managers() - result += self.get_poc() - return result - - -class WGEntity(Entity): - - def get_poc(self): - return role_persons_with_fixed_email(self.obj, "chair") - - def get_cc(self, person=None): - if self.obj.parent: - result = [p for p in role_persons_with_fixed_email(self.obj.parent, "ad") if p != person] - else: - result = [] - if self.obj.list_subscribe: - result.append(FakePerson(name ='%s Discussion List' % self.obj.name, - address = self.obj.list_subscribe)) - return result - - def get_from_cc(self, person): - result = [p for p in role_persons_with_fixed_email(self.obj, "chair") if p != person] - result += role_persons_with_fixed_email(self.obj.parent, "ad") if self.obj.parent else [] - if self.obj.list_subscribe: - result.append(FakePerson(name ='%s Discussion List' % self.obj.name, - address = self.obj.list_subscribe)) - return result - - def needs_approval(self, person=None): - # Check if person is director of this wg area - if self.obj.parent and self.obj.parent.role_set.filter(person=person, name="ad"): - return False - return True - - def can_approve(self): - return role_persons_with_fixed_email(self.obj.parent, "ad") if self.obj.parent else [] - - def full_user_list(self): - result = all_sdo_managers() - result += self.get_poc() - return result - - -class SDOEntity(Entity): - - def get_poc(self): - return [] - - def get_cc(self, person=None): - return role_persons_with_fixed_email(self.obj, "liaiman") - - def get_from_cc(self, person=None): - return [p for p in role_persons_with_fixed_email(self.obj, "liaiman") if p != person] - - def post_only(self, person, user): - if is_secretariat(user) or self.obj.role_set.filter(person=person, name="auth"): - return False - return True - - def full_user_list(self): - result = role_persons_with_fixed_email(self.obj, "liaiman") - result += role_persons_with_fixed_email(self.obj, "auth") - return result - - -class EntityManager(object): - - def __init__(self, pk=None, name=None, queryset=None): - self.pk = pk - self.name = name - self.queryset = queryset - - def get_entity(self, pk=None): - return Entity(name=self.name) - - def get_managed_list(self): - return [(self.pk, self.name)] - - def can_send_on_behalf(self, person): - return [] - - def can_approve_list(self, person): - return [] - - -class IETFEntityManager(EntityManager): - - def __init__(self, *args, **kwargs): - super(IETFEntityManager, self).__init__(*args, **kwargs) - self.entity = IETFEntity(name=self.name) - - def get_entity(self, pk=None): - return self.entity - - def can_send_on_behalf(self, person): - if is_ietfchair(person): - return self.get_managed_list() - return [] - - def can_approve_list(self, person): - if is_ietfchair(person): - return self.get_managed_list() - return [] - - -class IABEntityManager(EntityManager): - - def __init__(self, *args, **kwargs): - super(IABEntityManager, self).__init__(*args, **kwargs) - self.entity = IABEntity(name=self.name) - - def get_entity(self, pk=None): - return self.entity - - def can_send_on_behalf(self, person): - if (is_iabchair(person) or - is_iab_executive_director(person)): - return self.get_managed_list() - return [] - - def can_approve_list(self, person): - if (is_iabchair(person) or - is_iab_executive_director(person)): - return self.get_managed_list() - return [] - - -class IRTFEntityManager(EntityManager): - - def __init__(self, *args, **kwargs): - super(IRTFEntityManager, self).__init__(*args, **kwargs) - self.entity = IRTFEntity(name=self.name) - - def get_entity(self, pk=None): - return self.entity - - def can_send_on_behalf(self, person): - if is_irtfchair(person): - return self.get_managed_list() - return [] - - def can_approve_list(self, person): - if is_irtfchair(person): - return self.get_managed_list() - return [] - - -class IAB_IESG_EntityManager(EntityManager): - - def __init__(self, *args, **kwargs): - super(IAB_IESG_EntityManager, self).__init__(*args, **kwargs) - self.entity = IAB_IESG_Entity(name=self.name) - - def get_entity(self, pk=None): - return self.entity - - def can_send_on_behalf(self, person): - if (is_iabchair(person) or - is_iab_executive_director(person) or - is_ietfchair(person)): - return self.get_managed_list() - return [] - - def can_approve_list(self, person): - if (is_iabchair(person) or - is_iab_executive_director(person) or - is_ietfchair(person)): - return self.get_managed_list() - return [] - - -class AreaEntityManager(EntityManager): - - def __init__(self, pk=None, name=None, queryset=None): - super(AreaEntityManager, self).__init__(pk, name, queryset) - from ietf.group.proxy import Area - if self.queryset == None: - self.queryset = Area.active_areas() - - def get_managed_list(self, query_filter=None): - if not query_filter: - query_filter = {} - return [(u'%s_%s' % (self.pk, i.pk), i.area_acronym.name) for i in self.queryset.filter(**query_filter).order_by('area_acronym__name')] - - def get_entity(self, pk=None): - if not pk: - return None - try: - obj = self.queryset.get(pk=pk) - except self.queryset.model.DoesNotExist: - return None - return AreaEntity(name=obj.area_acronym.name, obj=obj) - - def can_send_on_behalf(self, person): - query_filter = dict(role__person=person, role__name="ad") - return self.get_managed_list(query_filter) - - def can_approve_list(self, person): - query_filter = dict(role__person=person, role__name="ad") - return self.get_managed_list(query_filter) - - -class WGEntityManager(EntityManager): - - def __init__(self, pk=None, name=None, queryset=None): - super(WGEntityManager, self).__init__(pk, name, queryset) - if self.queryset == None: - from ietf.group.proxy import IETFWG, Area - self.queryset = IETFWG.objects.filter(group_type=1, status=IETFWG.ACTIVE, areagroup__area__status=Area.ACTIVE) - - def get_managed_list(self, query_filter=None): - if not query_filter: - query_filter = {} - return [(u'%s_%s' % (self.pk, i.pk), '%s - %s' % (i.group_acronym.acronym, i.group_acronym.name)) for i in self.queryset.filter(**query_filter).order_by('group_acronym__acronym')] - - def get_entity(self, pk=None): - if not pk: - return None - try: - obj = self.queryset.get(pk=pk) - except self.queryset.model.DoesNotExist: - return None - return WGEntity(name=obj.group_acronym.name, obj=obj) - - def can_send_on_behalf(self, person): - wgs = Group.objects.filter(role__person=person, role__name__in=("chair", "secretary")).values_list('pk', flat=True) - query_filter = {'pk__in': wgs} - return self.get_managed_list(query_filter) - - def can_approve_list(self, person): - query_filter = dict(parent__role__person=person, parent__role__name="ad") - return self.get_managed_list(query_filter) - - -class SDOEntityManager(EntityManager): - - def __init__(self, pk=None, name=None, queryset=None): - super(SDOEntityManager, self).__init__(pk, name, queryset) - if self.queryset == None: - self.queryset = Group.objects.filter(type="sdo") - - def get_managed_list(self): - return [(u'%s_%s' % (self.pk, i.pk), i.name) for i in self.queryset.order_by('name')] - - def get_entity(self, pk=None): - if not pk: - return None - try: - obj = self.queryset.get(pk=pk) - except self.queryset.model.DoesNotExist: - return None - return SDOEntity(name=obj.name, obj=obj) - - -class IETFHierarchyManager(object): - - def __init__(self): - self.managers = {'ietf': IETFEntityManager(pk='ietf', name=u'The IETF'), - 'iesg': IETFEntityManager(pk='iesg', name=u'The IESG'), - 'iab': IABEntityManager(pk='iab', name=u'The IAB'), - 'iabiesg': IAB_IESG_EntityManager(pk='iabiesg', name=u'The IESG and the IAB'), - 'area': AreaEntityManager(pk='area', name=u'IETF Areas'), - 'wg': WGEntityManager(pk='wg', name=u'IETF Working Groups'), - 'sdo': SDOEntityManager(pk='sdo', name=u'Standards Development Organizations'), - 'othersdo': EntityManager(pk='othersdo', name=u'Other SDOs'), - } - - def get_entity_by_key(self, entity_id): - if not entity_id: - return None - id_list = entity_id.split('_', 1) - key = id_list[0] - pk = None - if len(id_list)==2: - pk = id_list[1] - if key not in self.managers.keys(): - return None - return self.managers[key].get_entity(pk) - - def get_all_entities(self): - entities = [] - for manager in self.managers.values(): - entities += manager.get_managed_list() - return entities - - def get_all_incoming_entities(self): - entities = [] - results = [] - for key in ['ietf', 'iesg', 'iab', 'iabiesg']: - results += self.managers[key].get_managed_list() - entities.append(('Main IETF Entities', results)) - entities.append(('IETF Areas', self.managers['area'].get_managed_list())) - entities.append(('IETF Working Groups', self.managers['wg'].get_managed_list())) - return entities - - def get_all_outgoing_entities(self): - entities = [(self.managers['sdo'].name, self.managers['sdo'].get_managed_list())] - entities += [(self.managers['othersdo'].name, self.managers['othersdo'].get_managed_list())] - return entities - - def get_entities_for_person(self, person): - entities = [] - results = [] - for key in ['ietf', 'iesg', 'iab', 'iabiesg']: - results += self.managers[key].can_send_on_behalf(person) - if results: - entities.append(('Main IETF Entities', results)) - areas = self.managers['area'].can_send_on_behalf(person) - if areas: - entities.append(('IETF Areas', areas)) - wgs = self.managers['wg'].can_send_on_behalf(person) - if wgs: - entities.append(('IETF Working Groups', wgs)) - return entities - - def get_all_can_approve_codes(self, person): - entities = [] - for key in ['ietf', 'iesg', 'iab', 'iabiesg']: - entities += self.managers[key].can_approve_list(person) - entities += self.managers['area'].can_approve_list(person) - entities += self.managers['wg'].can_approve_list(person) - return [i[0] for i in entities] - - -IETFHM = IETFHierarchyManager() diff --git a/ietf/liaisons/views.py b/ietf/liaisons/views.py index b935d86a9..1b8325973 100644 --- a/ietf/liaisons/views.py +++ b/ietf/liaisons/views.py @@ -4,52 +4,49 @@ from email.utils import parseaddr from django.conf import settings from django.core.urlresolvers import reverse -from django.db.models import Q -from django.core.validators import email_re +from django.core.validators import validate_email, ValidationError from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden -from django.shortcuts import render_to_response, get_object_or_404 +from django.shortcuts import render_to_response, get_object_or_404, redirect from django.template import RequestContext from django.utils import simplejson from django.views.generic.list_detail import object_list, object_detail +from ietf.liaisons.models import LiaisonStatement from ietf.liaisons.accounts import (get_person_for_user, can_add_outgoing_liaison, can_add_incoming_liaison, LIAISON_EDIT_GROUPS, is_ietfchair, is_iabchair, is_iab_executive_director, can_edit_liaison, is_secretariat) -from ietf.liaisons.decorators import can_submit_liaison from ietf.liaisons.forms import liaison_form_factory -from ietf.liaisons.models import LiaisonDetail, OutgoingLiaisonApproval -from ietf.liaisons.utils import IETFHM - -if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.liaisons.proxy import LiaisonDetailProxy as LiaisonDetail +from ietf.liaisons.utils import IETFHM, can_submit_liaison_required, approvable_liaison_statements +from ietf.liaisons.mails import notify_pending_by_email, send_liaison_by_email -@can_submit_liaison + +@can_submit_liaison_required def add_liaison(request, liaison=None): if request.method == 'POST': form = liaison_form_factory(request, data=request.POST.copy(), files = request.FILES, liaison=liaison) if form.is_valid(): liaison = form.save() - if request.POST.get('send', None): - if liaison.is_pending(): - liaison.notify_pending_by_email() + if request.POST.get('send', False): + if not liaison.approved: + notify_pending_by_email(request, liaison) else: - liaison.send_by_email() + send_liaison_by_email(request, liaison) return HttpResponseRedirect(reverse('liaison_list')) else: form = liaison_form_factory(request, liaison=liaison) return render_to_response( - 'liaisons/liaisondetail_edit.html', + 'liaisons/edit.html', {'form': form, 'liaison': liaison}, context_instance=RequestContext(request), ) -@can_submit_liaison +@can_submit_liaison_required def get_info(request): person = get_person_for_user(request.user) @@ -74,143 +71,84 @@ def get_info(request): result.update({'error': '\n'.join([to_error, from_error])}) else: result.update({'error': False, - 'cc': [i.email() for i in to_entity.get_cc(person=person)] +\ - [i.email() for i in from_entity.get_from_cc(person=person)], + 'cc': ([i.email() for i in to_entity.get_cc(person=person)] + + [i.email() for i in from_entity.get_from_cc(person=person)]), 'poc': [i.email() for i in to_entity.get_poc()], 'needs_approval': from_entity.needs_approval(person=person), 'post_only': from_entity.post_only(person=person, user=request.user)}) if is_secretariat(request.user): full_list = [(i.pk, i.email()) for i in set(from_entity.full_user_list())] - full_list.sort(lambda x,y: cmp(x[1], y[1])) + full_list.sort(key=lambda x: x[1]) full_list = [(person.pk, person.email())] + full_list result.update({'full_list': full_list}) json_result = simplejson.dumps(result) return HttpResponse(json_result, mimetype='text/javascript') +def normalize_sort(request): + sort = request.GET.get('sort', "") + if sort not in ('submitted', 'deadline', 'title', 'to_name', 'from_name'): + sort = "submitted" -if settings.USE_DB_REDESIGN_PROXY_CLASSES: - def approvable_liaison_statements(group_codes): - # this is a bit complicated because IETFHM encodes the - # groups, it should just give us a list of ids or acronyms - group_acronyms = [] - group_ids = [] - for x in group_codes: - if "_" in x: - group_ids.append(x.split("_")[1]) - else: - group_acronyms.append(x) + # reverse dates + order_by = "-" + sort if sort in ("submitted", "deadline") else sort - return LiaisonDetail.objects.filter(approved=None).filter(Q(from_group__acronym__in=group_acronyms) | Q (from_group__pk__in=group_ids)) + return sort, order_by def liaison_list(request): - user = request.user - can_send_outgoing = can_add_outgoing_liaison(user) - can_send_incoming = can_add_incoming_liaison(user) - can_approve = False - can_edit = False + sort, order_by = normalize_sort(request) + liaisons = LiaisonStatement.objects.exclude(approved=None).order_by(order_by) - person = get_person_for_user(request.user) - if person: - approval_codes = IETFHM.get_all_can_approve_codes(person) - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - if is_secretariat(request.user): - can_approve = LiaisonDetail.objects.filter(approved=None).order_by("-submitted").count() - else: - can_approve = approvable_liaison_statements(approval_codes).count() - else: - can_approve = LiaisonDetail.objects.filter(approval__isnull=False, approval__approved=False, from_raw_code__in=approval_codes).count() + can_send_outgoing = can_add_outgoing_liaison(request.user) + can_send_incoming = can_add_incoming_liaison(request.user) - order = request.GET.get('order_by', 'submitted_date') - plain_order = order - reverse_order = order.startswith('-') - if reverse_order: - plain_order = order[1:] - if plain_order not in ('submitted_date', 'deadline_date', 'title', 'to_body', 'from_raw_body'): - order = 'submitted_date' - reverse_order = True - plain_order = 'submitted_date' - elif plain_order in ('submitted_date', 'deadline_date'): - # Reverse order for date fields, humans find it more natural - if reverse_order: - order = plain_order - else: - order = '-%s' % plain_order - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - public_liaisons = LiaisonDetail.objects.exclude(approved=None).order_by(order) - else: - public_liaisons = LiaisonDetail.objects.filter(Q(approval__isnull=True)|Q(approval__approved=True)).order_by(order) + approvable = approvable_liaison_statements(request.user).count() - return object_list(request, public_liaisons, - allow_empty=True, - template_name='liaisons/liaisondetail_list.html', - extra_context={'can_manage': can_approve or can_send_incoming or can_send_outgoing, - 'can_approve': can_approve, - 'can_edit': can_edit, - 'can_send_incoming': can_send_incoming, - 'can_send_outgoing': can_send_outgoing, - plain_order: not reverse_order and '-' or None}, - ) + return render_to_response('liaisons/overview.html', { + "liaisons": liaisons, + "can_manage": approvable or can_send_incoming or can_send_outgoing, + "approvable": approvable, + "can_send_incoming": can_send_incoming, + "can_send_outgoing": can_send_outgoing, + "sort": sort, + }, context_instance=RequestContext(request)) +def ajax_liaison_list(request): + sort, order_by = normalize_sort(request) + liaisons = LiaisonStatement.objects.exclude(approved=None).order_by(order_by) -@can_submit_liaison + return render_to_response('liaisons/liaison_table.html', { + "liaisons": liaisons, + "sort": sort, + }, context_instance=RequestContext(request)) + +@can_submit_liaison_required def liaison_approval_list(request): - if is_secretariat(request.user): - to_approve = LiaisonDetail.objects.filter(approved=None).order_by("-submitted") - else: - person = get_person_for_user(request.user) - approval_codes = IETFHM.get_all_can_approve_codes(person) - 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") + liaisons = approvable_liaison_statements(request.user).order_by("-submitted") - return object_list(request, to_approve, - allow_empty=True, - template_name='liaisons/liaisondetail_approval_list.html', - ) + return render_to_response('liaisons/approval_list.html', { + "liaisons": liaisons, + }, context_instance=RequestContext(request)) -@can_submit_liaison +@can_submit_liaison_required def liaison_approval_detail(request, object_id): - if is_secretariat(request.user): - to_approve = LiaisonDetail.objects.filter(approved=None).order_by("-submitted") - else: - person = get_person_for_user(request.user) - approval_codes = IETFHM.get_all_can_approve_codes(person) - 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") + liaison = get_object_or_404(approvable_liaison_statements(request.user), pk=object_id) if request.method=='POST' and request.POST.get('do_approval', False): - try: - liaison = to_approve.get(pk=object_id) - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - liaison.approved = datetime.datetime.now() - liaison.save() - else: - 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() - liaison.send_by_email() - except LiaisonDetail.DoesNotExist: - pass - return HttpResponseRedirect(reverse('liaison_list')) - return object_detail(request, - to_approve, - object_id=object_id, - template_name='liaisons/liaisondetail_approval_detail.html', - ) + liaison.approved = datetime.datetime.now() + liaison.save() + + send_liaison_by_email(request, liaison) + return redirect('liaison_list') + + return render_to_response('liaisons/approval_detail.html', { + "liaison": liaison, + }, context_instance=RequestContext(request)) def _can_take_care(liaison, user): - if not liaison.deadline_date or liaison.action_taken: + if not liaison.deadline or liaison.action_taken: return False if user.is_authenticated(): @@ -224,15 +162,18 @@ def _can_take_care(liaison, user): def _find_person_in_emails(liaison, person): if not person: return False - emails = ','.join([ e for e in [liaison.cc1, liaison.cc2, liaison.to_email, - liaison.to_poc, liaison.submitter_email, - liaison.replyto, liaison.response_contact, - liaison.technical_contact] if e ]) + + emails = ','.join(e for e in [liaison.cc, liaison.to_contact, liaison.to_name, + liaison.reply_to, liaison.response_contact, + liaison.technical_contact] if e) for email in emails.split(','): name, addr = parseaddr(email) - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - person.emailaddress_set = person.email_set - if email_re.search(addr) and person.emailaddress_set.filter(address=addr): + try: + validate_email(addr) + except ValidationError: + continue + + if person.email_set.filter(address=addr): return True elif addr in ('chair@ietf.org', 'iesg@ietf.org') and is_ietfchair(person): return True @@ -240,67 +181,31 @@ def _find_person_in_emails(liaison, person): return True elif addr in ('execd@iab.org', ) and is_iab_executive_director(person): return True + return False def liaison_detail(request, object_id): - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - qfilter = Q() - public_liaisons = LiaisonDetail.objects.exclude(approved=None).order_by("-submitted_date") - else: - qfilter = Q(approval__isnull=True)|Q(approval__approved=True) - public_liaisons = LiaisonDetail.objects.filter(qfilter).order_by("-submitted_date") - liaison = get_object_or_404(public_liaisons, pk=object_id) - can_edit = False - user = request.user - can_take_care = _can_take_care(liaison, user) - if user.is_authenticated() and can_edit_liaison(user, liaison): - can_edit = True + liaison = get_object_or_404(LiaisonStatement.objects.exclude(approved=None), pk=object_id) + can_edit = request.user.is_authenticated() and can_edit_liaison(request.user, liaison) + can_take_care = _can_take_care(liaison, request.user) + if request.method == 'POST' and request.POST.get('do_action_taken', None) and can_take_care: liaison.action_taken = True liaison.save() can_take_care = False - relations = liaison.liaisondetail_set.filter(qfilter) - return object_detail(request, - public_liaisons, - template_name="liaisons/liaisondetail_detail.html", - object_id=object_id, - extra_context = {'can_edit': can_edit, - 'relations': relations, - 'can_take_care': can_take_care} - ) + + relations = liaison.liaisonstatement_set.all() + + return render_to_response("liaisons/detail.html", { + "liaison": liaison, + "can_edit": can_edit, + "can_take_care": can_take_care, + "relations": relations, + }, context_instance=RequestContext(request)) def liaison_edit(request, object_id): - liaison = get_object_or_404(LiaisonDetail, pk=object_id) - user = request.user - if not (user.is_authenticated() and can_edit_liaison(user, liaison)): - return HttpResponseForbidden('You have no permission to edit this liaison') + liaison = get_object_or_404(LiaisonStatement, pk=object_id) + if not (request.user.is_authenticated() and can_edit_liaison(request.user, liaison)): + return HttpResponseForbidden('You do not have permission to edit this liaison statement') return add_liaison(request, liaison=liaison) - -def ajax_liaison_list(request): - order = request.GET.get('order_by', 'submitted_date') - plain_order = order - reverse_order = order.startswith('-') - if reverse_order: - plain_order = order[1:] - if plain_order not in ('submitted_date', 'deadline_date', 'title', 'to_body', 'from_raw_body'): - order = 'submitted_date' - reverse_order = True - plain_order = 'submitted_date' - elif plain_order in ('submitted_date', 'deadline_date'): - # Reverse order for date fields, humans find it more natural - if reverse_order: - order = plain_order - else: - order = '-%s' % plain_order - - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - public_liaisons = LiaisonDetail.objects.exclude(approved=None).order_by(order) - else: - public_liaisons = LiaisonDetail.objects.filter(Q(approval__isnull=True)|Q(approval__approved=True)).order_by(order) - - return object_list(request, public_liaisons, - allow_empty=True, - template_name='liaisons/liaisondetail_simple_list.html', - extra_context={plain_order: not reverse_order and '-' or None} - ) diff --git a/ietf/liaisons/widgets.py b/ietf/liaisons/widgets.py index db5366eef..cd872a5e9 100644 --- a/ietf/liaisons/widgets.py +++ b/ietf/liaisons/widgets.py @@ -1,8 +1,11 @@ from django.conf import settings -from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse as urlreverse from django.db.models.query import QuerySet from django.forms.widgets import Select, Widget, TextInput from django.utils.safestring import mark_safe +from django.utils.html import conditional_escape + +from ietf.liaisons.models import LiaisonStatement class FromWidget(Select): @@ -14,9 +17,9 @@ class FromWidget(Select): def render(self, name, value, attrs=None, choices=()): all_choices = list(self.choices) + list(choices) - if len(all_choices)!=1 or \ - (isinstance(all_choices[0][1], (list, tuple)) and \ - len(all_choices[0][1])!=1): + if (len(all_choices) != 1 or + (isinstance(all_choices[0][1], (list, tuple)) and + len(all_choices[0][1]) != 1)): base = super(FromWidget, self).render(name, value, attrs, choices) else: option = all_choices[0] @@ -24,14 +27,14 @@ class FromWidget(Select): option = option[1][0] value = option[0] text = option[1] - base = u'%s' % (value, name, name, text) - base += u' (' + self.submitter + u')' + base = u'%s' % (conditional_escape(value), conditional_escape(name), conditional_escape(name), conditional_escape(text)) + base += u' (' + conditional_escape(self.submitter) + u')' if self.full_power_on: base += '' return mark_safe(base) @@ -39,7 +42,7 @@ class FromWidget(Select): class ReadOnlyWidget(Widget): def render(self, name, value, attrs=None): - html = u'
%s
' % (name, value or '') + html = u'
%s
' % (conditional_escape(name), conditional_escape(value or '')) return mark_safe(html) @@ -53,14 +56,14 @@ class ButtonWidget(Widget): super(ButtonWidget, self).__init__(*args, **kwargs) def render(self, name, value, attrs=None): - html = u'' % self.show_on - html += u'' % self.label + html = u'' % conditional_escape(self.show_on) + html += u'' % conditional_escape(self.label) if self.require: for i in self.require: - html += u'' % i - required_str = u'Please fill %s to attach a new file' % self.required_label - html += u'' % required_str - html += u'' % self.label + html += u'' % conditional_escape(i) + required_str = u'Please fill in %s to attach a new file' % conditional_escape(self.required_label) + html += u'' % conditional_escape(required_str) + html += u'' % conditional_escape(self.label) return mark_safe(html) @@ -71,8 +74,8 @@ class ShowAttachmentsWidget(Widget): html += u'' html += u'
' if value and isinstance(value, QuerySet): - for attach in value: - html += u'%s
' % (settings.LIAISON_ATTACH_URL, attach.file_id, attach.file_extension, attach.file_title, ) + for attachment in value: + html += u'%s
' % (settings.LIAISON_ATTACH_URL, conditional_escape(attachment.external_url), conditional_escape(attachment.title)) else: html += u'No files attached' html += u'
' @@ -88,23 +91,21 @@ class RelatedLiaisonWidget(TextInput): noliaison = 'inline' deselect = 'none' else: - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.liaisons.proxy import LiaisonDetailProxy as LiaisonDetail - liaison = LiaisonDetail.objects.get(pk=value) + liaison = LiaisonStatement.objects.get(pk=value) title = liaison.title if not title: - files = liaison.uploads_set.all() - if files: - title = files[0].file_title + attachments = liaison.attachments.all() + if attachments: + title = attachments[0].title else: title = 'Liaison #%s' % liaison.pk noliaison = 'none' deselect = 'inline' - html = u'No liaison selected' % noliaison - html += u'%s' % title - html += u' ' % (name, value) - html += u' ' % reverse('ajax_liaison_list') + html = u'No liaison selected' % conditional_escape(noliaison) + html += u'%s' % conditional_escape(title) + html += u' ' % (conditional_escape(name), conditional_escape(value)) + html += u' ' % urlreverse('ajax_liaison_list') html += u' ' - html += ' ' % name - html += '' % (deselect, name) + html += ' ' % conditional_escape(name) + html += '' % (conditional_escape(deselect), conditional_escape(name)) return mark_safe(html) diff --git a/ietf/templates/liaisons/approval_detail.html b/ietf/templates/liaisons/approval_detail.html new file mode 100644 index 000000000..6b94c32e2 --- /dev/null +++ b/ietf/templates/liaisons/approval_detail.html @@ -0,0 +1,10 @@ +{% extends "liaisons/detail.html" %} +{# Copyright The IETF Trust 2007, All Rights Reserved #} + +{% block content %} +{{ block.super }} + +
+ +
+{% endblock %} diff --git a/ietf/templates/liaisons/liaisondetail_approval_list.html b/ietf/templates/liaisons/approval_list.html similarity index 86% rename from ietf/templates/liaisons/liaisondetail_approval_list.html rename to ietf/templates/liaisons/approval_list.html index aba7092d4..0e5dba6d7 100644 --- a/ietf/templates/liaisons/liaisondetail_approval_list.html +++ b/ietf/templates/liaisons/approval_list.html @@ -1,4 +1,4 @@ -{% extends "liaisons/liaisondetail_list.html" %} +{% extends "liaisons/overview.html" %} {# Copyright The IETF Trust 2007, All Rights Reserved #} {% block title %}Pending Liaison Statements{% endblock %} diff --git a/ietf/templates/liaisons/detail.html b/ietf/templates/liaisons/detail.html new file mode 100644 index 000000000..a864da1cf --- /dev/null +++ b/ietf/templates/liaisons/detail.html @@ -0,0 +1,120 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2007, All Rights Reserved #} +{% load ietf_filters %} + +{% block title %}Liaison Statement: {% include 'liaisons/liaison_title.html' %}{% endblock %} + +{% block pagehead %} + + + +{% endblock %} + +{% block morecss %} +.ietf-liaison-details tr { vertical-align:top; } +{% endblock morecss %} + +{% block content %} +

Liaison Statement: {% include 'liaisons/liaison_title.html' %}

+ + + + + + + + + + + + + + + +{% if liaison.from_contact %} + + + + + + + + + + + + + + + + + + + + {% if liaison.deadline %} + + + + + {% endif %} +{% endif %} + +{% if relations %} + + + + +{% endif %} + +{% if liaison.related_to %} + + + + +{% endif %} + + + + + + +{% if liaison.from_contact and liaison.body %} + + + + +{% endif %} +
Submission Date:{{ liaison.submitted|date:"Y-m-d" }}
From:{{ liaison.from_name }} + {% if liaison.from_contact %}({{ liaison.from_contact.person }}){% endif %} +
To: + {% if liaison.from_contact %} + {{ liaison.to_name }} ({{ liaison.to_contact|parse_email_list }}) + {% else %} + {{ liaison.to_name|urlize }} + {% endif %} +
Cc:{{ liaison.cc|parse_email_list|make_one_per_line|safe|linebreaksbr }}
Response Contact:{{ liaison.response_contact|parse_email_list|make_one_per_line|safe|linebreaksbr }}
Technical Contact:{{ liaison.technical_contact|parse_email_list|make_one_per_line|safe|linebreaksbr }}
Purpose:{{ liaison.purpose.name }}
Deadline:{{ liaison.deadline }} + {% if liaison.action_taken %}Action Taken{% else %}Action Needed{% endif %} + {% if can_take_care %} +
+ +
+ {% endif %} +
Liaisons referring to this one: + {% for rel in relations %} + {% if rel.title %}{{ rel.title }}{% else %}Liaison #{{ rel.pk }}{% endif %}
+ {% endfor %} +
Referenced liaison: + {% if liaison.related_to.title %}{{ liaison.related_to.title }}{% else %}Liaison #{{ liaison.related_to.pk }}{% endif %} +
Attachments: + {% for doc in liaison.attachments.all %} + {{ doc.title }}{% if not forloop.last %}
{% endif %} + {% empty %} + (none) + {% endfor %} +
Body:
{{ liaison.body|wordwrap:"71" }}
+ +{% if can_edit %} +

Edit Liaison

+{% endif %} + +{% endblock %} diff --git a/ietf/templates/liaisons/edit.html b/ietf/templates/liaisons/edit.html new file mode 100644 index 000000000..8286ebf34 --- /dev/null +++ b/ietf/templates/liaisons/edit.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2007, All Rights Reserved #} +{% load ietf_filters %} +{% block title %}{% if liaison %}Edit liaison: {{ liaison }}{% else %}Send Liaison Statement{% endif %}{% endblock %} + +{% block pagehead %} + + +{% endblock %} + +{% block content %} +

{% if liaison %}Edit liaison: {{ liaison }}{% else %}Send Liaison Statement{% endif %}

+ + + +{% if not liaison %} +
    +
  • If you wish to submit your liaison statement by e-mail, then please send it to statements@ietf.org
  • +
  • Fields marked with * are required. For detailed descriptions of the fields see Field help
  • +
+{% endif %} + +{{ form }} + +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/ietf/templates/liaisons/liaison_mail.txt b/ietf/templates/liaisons/liaison_mail.txt index a79a1a437..c9c2369da 100644 --- a/ietf/templates/liaisons/liaison_mail.txt +++ b/ietf/templates/liaisons/liaison_mail.txt @@ -1,19 +1,19 @@ {% load ietf_filters %}{% autoescape off %}Title: {{ liaison.title|clean_whitespace }} -Submission Date: {{ liaison.submitted_date }} +Submission Date: {{ liaison.submitted|date:"Y-m-d" }} 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 }} +{% if liaison.deadline %}Please reply by {{ liaison.deadline }}{% endif %} +From: {{ liaison.from_name }} ({{ liaison.from_contact.person }} <{{ liaison.reply_to|default:liaison.from_contact.address }}>) +To: {{ liaison.to_name }} ({{ liaison.to_contact }}) +Cc: {{ liaison.cc }} +Response Contact: {{ liaison.response_contact }} Technical Contact: {{ liaison.technical_contact }} -Purpose: {% if liaison.purpose_text %}{{ liaison.purpose_text }}{% else %}{{ liaison.purpose }}{% endif %} +Purpose: {{ liaison.purpose.name }} {% if liaison.related_to %}Referenced liaison: {% if liaison.related_to.title %}{{ liaison.related_to.title }}{% else %}Liaison #{{ liaison.related_to.pk }}{% endif %} ({{ referenced_url }}){% endif %} Body: {{ liaison.body }} Attachments: -{% for file in liaison.uploads_set.all %} - {{ file.file_title }} - https://datatracker.ietf.org/documents/LIAISON/{{ file.filename }} +{% for doc in liaison.attachments.all %} + {{ doc.title }} + https://datatracker.ietf.org/documents/LIAISON/{{ doc.external_url }} {% empty %} No document has been attached {% endfor %}{% endautoescape %} diff --git a/ietf/templates/liaisons/liaison_mail_detail.html b/ietf/templates/liaisons/liaison_mail_detail.html deleted file mode 100644 index 43ad036ae..000000000 --- a/ietf/templates/liaisons/liaison_mail_detail.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends "base.html" %} -{# Copyright The IETF Trust 2007, All Rights Reserved #} -{% load ietf_filters %} -{% block title %}{{ liaison.title }}{% endblock %} - -{% block content %} -

Simulated Mail for Liaison Statement

- -

-Demo version of this tool does NOT actually send the liaison statement to the recipients. -

-

-Rather, the actual email body (including mail header) is displayed below. -

-

-In production mode, you will not see this screen. -

- -
-From: {{ message.From }}
-To: {{ message.To }}
-Cc: {{ message.Cc }}
-Bcc: {{ message.Bcc }}
-Subject: {{ message.Subject }}
-
-{{ mail.body }}
-
- -Return to liaison list - -{% endblock %} diff --git a/ietf/templates/liaisons/liaison_main_management.html b/ietf/templates/liaisons/liaison_main_management.html deleted file mode 100644 index edc259a6f..000000000 --- a/ietf/templates/liaisons/liaison_main_management.html +++ /dev/null @@ -1,62 +0,0 @@ -{% extends "base.html" %} -{# Copyright The IETF Trust 2007, All Rights Reserved #} -{% load ietf_filters %} -{% block title %}Liaison Management{% endblock %} - -{% block content %} -

Liaison Management

- -{% if can_send_incoming or can_send_outgoing %} -
-

Add new liaison

-
-{% endif %} - -{% if to_aprove %} -
-

Liaisons that need your approval

- {% for liaison in to_aprove %} - {{ liaison }} - {% endfor %} -
-{% endif %} - -{% if to_edit %} -
-

Liaisons you can edit

-{% for liaison in to_edit %} - -{{ liaison.submitted_date|date:"Y-m-d" }} -{{ liaison.from_body|escape }} - -{% if liaison.by_secretariat %} - {% if liaison.submitter_email %} - {{ liaison.submitter_name|escape }} - {% else %} - {{ liaison.submitter_name|escape }} - {% endif %} -{% else %} - {{ liaison.to_body|escape }} -{% endif %} - - -{% if liaison.by_secretariat %} - {% for file in liaison.uploads_set.all %} - {{ file.file_title|escape }}
- {% endfor %} -{% else %} - {{ liaison.title|escape }} -{% endif %} - - -{% endfor %} - - - -
-{% endif %} - -{% endblock %} diff --git a/ietf/templates/liaisons/liaison_table.html b/ietf/templates/liaisons/liaison_table.html new file mode 100644 index 000000000..60fc22f9a --- /dev/null +++ b/ietf/templates/liaisons/liaison_table.html @@ -0,0 +1,39 @@ +{% load ietf_filters %} + + + + + + + + + + +{% for liaison in liaisons %} + + + + + + + +{% endfor %} + +
DateFromToDeadlineTitle
{{ liaison.submitted|date:"Y-m-d" }}{{ liaison.from_name }} + {% if liaison.from_contact_id %} + {{ liaison.to_name }} + {% else %} + {{ liaison.to_name|strip_email }} + {% endif %} + + {{ liaison.deadline|default:"-"|date:"Y-m-d" }} + + {% if not liaison.from_contact_id %} + {% for doc in liaison.attachments.all %} + {{ doc.title }}
+ {% endfor %} + {% else %} + {{ liaison.title }} + {% endif %} + +
diff --git a/ietf/templates/liaisons/liaison_title.html b/ietf/templates/liaisons/liaison_title.html index dbf83f37c..5556aaafa 100644 --- a/ietf/templates/liaisons/liaison_title.html +++ b/ietf/templates/liaisons/liaison_title.html @@ -1,5 +1,5 @@ -{% if object.by_secretariat %} -Liaison statement submitted by email from {{ object.from_body|escape }} to {{ object.submitter_name|escape }} on {{ object.submitted_date }} +{% load ietf_filters %}{% if not liaison.from_contact %} + Liaison statement submitted by email from {{ liaison.from_name }} to {{ liaison.to_name|strip_email }} on {{ liaison.submitted|date:"Y-m-d" }} {% else %} -{{ object.title }} + {{ liaison.title }} {% endif %} diff --git a/ietf/templates/liaisons/liaisondetail_approval_detail.html b/ietf/templates/liaisons/liaisondetail_approval_detail.html deleted file mode 100644 index 2391dc956..000000000 --- a/ietf/templates/liaisons/liaisondetail_approval_detail.html +++ /dev/null @@ -1,10 +0,0 @@ -{% extends "liaisons/liaisondetail_detail.html" %} -{# Copyright The IETF Trust 2007, All Rights Reserved #} - -{% block content %} -{{ block.super }} - -
- -
-{% endblock %} diff --git a/ietf/templates/liaisons/liaisondetail_detail.html b/ietf/templates/liaisons/liaisondetail_detail.html deleted file mode 100644 index cc151b62e..000000000 --- a/ietf/templates/liaisons/liaisondetail_detail.html +++ /dev/null @@ -1,110 +0,0 @@ -{% extends "base.html" %} -{# Copyright The IETF Trust 2007, All Rights Reserved #} -{% load ietf_filters %} -{% block title %}Liaison Statement: {% include 'liaisons/liaison_title.html' %}{% endblock %} - -{% block pagehead %} - - - -{% endblock %} - -{% block morecss %} -.ietf-liaison-details tr { vertical-align:top; } -{% endblock morecss %} - -{% block content %} -

Liaison Statement: {% include 'liaisons/liaison_title.html' %}

- - - - - - - - -{% endif %} - -{% if not object.by_secretariat %} - - - - - - - - - - - - -{% if object.deadline_date %} - - -{% if can_take_care %} - -{% else %} - -{% endif %} - -{% endif %} - -{% endif %} - -{% if relations %} - - - -{% endif %} - -{% if object.related_to %} - - - -{% endif %} - - - - - -{% if not object.by_secretariat and object.body %} - - -{% endif %} -
Submission Date:{{ object.submitted_date }}
From:{{ object.from_body }} ({{ object.person }})
To: -{% if object.by_secretariat %} - {% if object.submitter_email %} - {{ object.submitter_name }} - {% else %} - {{ object.submitter_name }} - {% endif %} -{% else %} -{{ object.to_body }} ({{ object.to_poc|parse_email_list|safe }})
Cc:{{ object.cc1|parse_email_list|make_one_per_line|safe|linebreaksbr }}
Response Contact: -{{ object.response_contact|parse_email_list|make_one_per_line|safe|linebreaksbr }} -
Technical Contact:{{ object.technical_contact|parse_email_list|make_one_per_line|safe|linebreaksbr }}
Purpose:{% if object.purpose_text %}{{ object.purpose_text }}{% else %}{{ object.purpose }}{% endif %}
Deadline:
-{{ object.deadline_date }} -{% if object.action_taken %}Action Taken{% else %}Action Required{% endif %} - -
{{ object.deadline_date }} -{% if object.action_taken %}Action Taken{% else %}Action Needed{% endif %} -
Liaisons referring to this one: -{% for liaison in relations %} -{% if liaison.title %}{{ object.title }}{% else %}Liaison #{{ liaison.pk }}{% endif %}
-{% endfor %} -
Referenced liaison: -{% if object.related_to.title %}{{ object.related_to.title }}{% else %}Liaison #{{ object.related_to.pk }}{% endif %} -
Attachments: -{% for file in object.uploads_set.all %} -{{ file.file_title }}{% if not forloop.last %}
{% endif %} -{% empty %} -(none) -{% endfor %} -
Body:
{{ object.body|wordwrap:"71"|escape }}
- -{% if can_edit %} -
- -
-{% endif %} - -{% endblock %} diff --git a/ietf/templates/liaisons/liaisondetail_edit.html b/ietf/templates/liaisons/liaisondetail_edit.html deleted file mode 100644 index 59757909b..000000000 --- a/ietf/templates/liaisons/liaisondetail_edit.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends "base.html" %} -{# Copyright The IETF Trust 2007, All Rights Reserved #} -{% load ietf_filters %} -{% block title %}{% if liaison %}Edit liaison: {{ liaison }}{% else %}Send Liaison Statement{% endif %}{% endblock %} - -{% block pagehead %} -{{ form.media }} -{% endblock %} - -{% block content %} -

{% if liaison %}Edit liaison: {{ liaison }}{% else %}Send Liaison Statement{% endif %}

- -
-Your browser has Javascript disabled. Please enable javascript and reload the page. - -
- -{% if not liaison %} -
    -
  • If you wish to submit your liaison statement by e-mail, then please send it to statements@ietf.org
  • -
  • Fields marked with * are required. For detailed descriptions of the fields see Field help
  • -
-{% endif %} - -{{ form }} - -{% endblock %} diff --git a/ietf/templates/liaisons/liaisondetail_simple_list.html b/ietf/templates/liaisons/liaisondetail_simple_list.html deleted file mode 100644 index f93088f2a..000000000 --- a/ietf/templates/liaisons/liaisondetail_simple_list.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - -{% for liaison in object_list %} - - - - - - - -{% endfor %} - -
- From - - To - - Deadline - - Title -
{{ liaison.submitted_date|date:"Y-m-d" }}{{ liaison.from_body|escape }} -{% if liaison.by_secretariat %} - {% if liaison.submitter_email %} - {{ liaison.submitter_name|escape }} - {% else %} - {{ liaison.submitter_name|escape }} - {% endif %} -{% else %} - {{ liaison.to_body|escape }} -{% endif %} - - {{ liaison.deadline_date|default:"--" }} - -{% if liaison.by_secretariat %} - {% for file in liaison.uploads_set.all %} - {{ file.file_title|escape }}
- {% endfor %} -{% else %} - {{ liaison.title|escape }} -{% endif %} - -
diff --git a/ietf/templates/liaisons/liaisondetail_list.html b/ietf/templates/liaisons/overview.html similarity index 72% rename from ietf/templates/liaisons/liaisondetail_list.html rename to ietf/templates/liaisons/overview.html index 9eaad8682..16c1d510c 100644 --- a/ietf/templates/liaisons/liaisondetail_list.html +++ b/ietf/templates/liaisons/overview.html @@ -18,12 +18,12 @@ {% endif %} {% endblock %} -{% include "liaisons/liaisondetail_simple_list.html" %} +{% include "liaisons/liaison_table.html" %} {% endblock %} diff --git a/static/css/liaisons.css b/static/css/liaisons.css index c68075d6e..522620357 100644 --- a/static/css/liaisons.css +++ b/static/css/liaisons.css @@ -96,30 +96,17 @@ span.fieldRequired { background-color: #ffdd88; } -th.orderField a { +th.sort a { text-decoration: none; color: white; - display: block; + padding-right: 20px; + background: url(/images/sort-header-clear.png) no-repeat right center; } -th.orderFieldReversed a { - background: #2647A0 url(/images/arrow-down.gif) no-repeat left center; - padding-left: 20px; +th.sorted a { + background: url(/images/sort-header-filled.png) no-repeat right center; } -th.orderFieldActive a { - background: #2647A0 url(/images/arrow-up.gif) no-repeat left center; - padding-left: 20px; -} - -.noActionTaken, -.actionTaken { - border: 1px solid green; - padding: 2px 5px; - background-color: #ccffbb; -} - -.noActionTaken { - border: 1px solid red; - background-color: #ffccbb; -} +.noActionTaken, .actionTaken { padding: 2px 5px; } +.actionTaken { border: 1px solid green; background-color: #ccffbb; } +.noActionTaken { border: 1px solid red; background-color: #ffccbb; }