From 8e7295c99636504c3df934902e649ecf32e20850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20A=2E=20S=C3=A1nchez=20L=C3=B3pez?= Date: Fri, 6 Aug 2010 17:01:23 +0000 Subject: [PATCH] Management of pending liaisons. If an user can approve pending liaisons he/she have access to: - List of pending liaisons he/she can approve - Detailed view of a pending liaison with an approval button Fixes #357 - Legacy-Id: 2466 --- ietf/liaisons/forms.py | 6 +- .../migrations/0004_add_from_raw_code.py | 120 ++++++++++++++++++ ietf/liaisons/models.py | 2 +- ietf/liaisons/urls.py | 4 +- ietf/liaisons/utils.py | 31 +++++ ietf/liaisons/views.py | 90 +++++++++++-- .../liaisons/liaison_main_management.html | 62 +++++++++ .../liaisondetail_approval_detail.html | 10 ++ .../liaisons/liaisondetail_approval_list.html | 13 ++ .../liaisons/liaisondetail_list.html | 12 +- 10 files changed, 336 insertions(+), 14 deletions(-) create mode 100644 ietf/liaisons/migrations/0004_add_from_raw_code.py create mode 100644 ietf/templates/liaisons/liaison_main_management.html create mode 100644 ietf/templates/liaisons/liaisondetail_approval_detail.html create mode 100644 ietf/templates/liaisons/liaisondetail_approval_list.html diff --git a/ietf/liaisons/forms.py b/ietf/liaisons/forms.py index 6b8418e25..632d129cb 100644 --- a/ietf/liaisons/forms.py +++ b/ietf/liaisons/forms.py @@ -1,7 +1,6 @@ import datetime from django import forms -from django.conf import settings from django.forms.util import ErrorList from django.template.loader import render_to_string @@ -151,6 +150,7 @@ class LiaisonForm(forms.ModelForm): 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') organization = self.get_to_entity() liaison.to_body = organization.name liaison.to_poc = self.get_poc(organization) @@ -165,7 +165,6 @@ class LiaisonForm(forms.ModelForm): continue attached_file = self.files.get(key) extension=attached_file.name.rsplit('.', 1) - basename = extension[0] if len(extension) > 1: extension = '.' + extension[1] else: @@ -248,7 +247,8 @@ class OutgoingLiaisonForm(LiaisonForm): def liaison_form_factory(request, **kwargs): user = request.user - if can_add_outgoing_liaison(user): + force_incoming = 'incoming' in request.GET.keys() + if not force_incoming and can_add_outgoing_liaison(user): return OutgoingLiaisonForm(user, **kwargs) elif can_add_incoming_liaison(user): return IncomingLiaisonForm(user, **kwargs) diff --git a/ietf/liaisons/migrations/0004_add_from_raw_code.py b/ietf/liaisons/migrations/0004_add_from_raw_code.py new file mode 100644 index 000000000..6efd4587c --- /dev/null +++ b/ietf/liaisons/migrations/0004_add_from_raw_code.py @@ -0,0 +1,120 @@ + +from south.db import db +from django.db import models +from ietf.liaisons.models import * + +class Migration: + + def forwards(self, orm): + + # Adding field 'LiaisonDetail.from_raw_code' + db.add_column('liaison_detail', 'from_raw_code', orm['liaisons.liaisondetail:from_raw_code']) + + # Deleting field 'OutgoingLiaisonApproval.normalized_entity_code' + db.delete_column('liaisons_outgoingliaisonapproval', 'normalized_entity_code') + + + + def backwards(self, orm): + + # Deleting field 'LiaisonDetail.from_raw_code' + db.delete_column('liaison_detail', 'from_raw_code') + + # Adding field 'OutgoingLiaisonApproval.normalized_entity_code' + db.add_column('liaisons_outgoingliaisonapproval', 'normalized_entity_code', orm['liaisons.outgoingliaisonapproval:normalized_entity_code']) + + + + models = { + 'idtracker.personororginfo': { + 'Meta': {'db_table': "'person_or_org_info'"}, + 'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}), + 'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}), + 'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}), + 'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}), + 'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}), + 'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}), + 'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), + 'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), + 'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}) + }, + 'liaisons.frombodies': { + 'Meta': {'db_table': "'from_bodies'"}, + 'body_name': ('django.db.models.fields.CharField', [], {'max_length': '35', 'blank': 'True'}), + 'email_priority': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'from_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_liaison_manager': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'other_sdo': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'poc': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'null': 'True', 'db_column': "'poc'"}) + }, + 'liaisons.liaisondetail': { + 'Meta': {'db_table': "'liaison_detail'"}, + 'approval': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['liaisons.OutgoingLiaisonApproval']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'by_secretariat': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'cc1': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'cc2': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'deadline_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'detail_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'from_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'from_raw_body': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'from_raw_code': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'last_modified_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'null': 'True', 'db_column': "'person_or_org_tag'"}), + 'purpose': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['liaisons.LiaisonPurpose']", 'null': 'True'}), + 'purpose_text': ('django.db.models.fields.TextField', [], {'null': 'True', 'db_column': "'purpose'", 'blank': 'True'}), + 'replyto': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'response_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'submitted_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'submitter_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'submitter_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'technical_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'to_body': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'to_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'to_poc': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'liaisons.liaisonmanagers': { + 'Meta': {'db_table': "'liaison_managers'"}, + 'email_priority': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}), + 'sdo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['liaisons.SDOs']"}) + }, + 'liaisons.liaisonpurpose': { + 'Meta': {'db_table': "'liaison_purpose'"}, + 'purpose_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'purpose_text': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}) + }, + 'liaisons.outgoingliaisonapproval': { + 'approval_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'liaisons.sdoauthorizedindividual': { + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}), + 'sdo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['liaisons.SDOs']"}) + }, + 'liaisons.sdos': { + 'Meta': {'db_table': "'sdos'"}, + 'sdo_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'sdo_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}) + }, + 'liaisons.uploads': { + 'Meta': {'db_table': "'uploads'"}, + 'detail': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['liaisons.LiaisonDetail']"}), + 'file_extension': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'}), + 'file_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'file_title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'db_column': "'person_or_org_tag'"}) + } + } + + complete_apps = ['liaisons'] diff --git a/ietf/liaisons/models.py b/ietf/liaisons/models.py index f2503a264..e40e78415 100644 --- a/ietf/liaisons/models.py +++ b/ietf/liaisons/models.py @@ -34,7 +34,6 @@ class FromBodies(models.Model): class OutgoingLiaisonApproval(models.Model): approved = models.BooleanField(default=True) approval_date = models.DateField(null=True, blank=True) - normalized_entity_code = models.CharField(max_length=255) class LiaisonDetail(models.Model): @@ -62,6 +61,7 @@ class LiaisonDetail(models.Model): 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) approval = models.ForeignKey(OutgoingLiaisonApproval, blank=True, null=True) def __str__(self): return self.title or "" diff --git a/ietf/liaisons/urls.py b/ietf/liaisons/urls.py index 45fd504ef..9d8e9bf5f 100644 --- a/ietf/liaisons/urls.py +++ b/ietf/liaisons/urls.py @@ -10,7 +10,6 @@ info_dict = { # there's an opportunity for date-based filtering. urlpatterns = patterns('django.views.generic.list_detail', - url(r'^$', 'object_list', info_dict, name='liaison_list'), url(r'^(?P\d+)/$', 'object_detail', info_dict, name='liaison_detail'), ) @@ -23,6 +22,9 @@ urlpatterns += patterns('django.views.generic.simple', ) urlpatterns += patterns('ietf.liaisons.views', + url(r'^$', 'liaison_list', name='liaison_list'), + url(r'^for_approval/$', 'liaison_approval_list', name='liaison_approval_list'), + url(r'^for_approval/(?P\d+)/$', 'liaison_approval_detail', name='liaison_approval_detail'), url(r'^add/$', 'add_liaison', name='add_liaison'), url(r'^ajax/get_info/$', 'get_info', name='get_info'), ) diff --git a/ietf/liaisons/utils.py b/ietf/liaisons/utils.py index da01ee82c..f92ad3958 100644 --- a/ietf/liaisons/utils.py +++ b/ietf/liaisons/utils.py @@ -165,6 +165,9 @@ class EntityManager(object): def can_send_on_behalf(self, person): return [] + def can_approve_list(self, person): + return [] + class IETFEntityManager(EntityManager): @@ -180,6 +183,11 @@ class IETFEntityManager(EntityManager): 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): @@ -196,6 +204,12 @@ class IABEntityManager(EntityManager): 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 AreaEntityManager(EntityManager): @@ -222,6 +236,10 @@ class AreaEntityManager(EntityManager): query_filter = {'areadirector__in': person.areadirector_set.all()} return self.get_managed_list(query_filter) + def can_approve_list(self, person): + query_filter = {'areadirector__in': person.areadirector_set.all()} + return self.get_managed_list(query_filter) + class WGEntityManager(EntityManager): @@ -250,6 +268,10 @@ class WGEntityManager(EntityManager): 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()} + return self.get_managed_list(query_filter) + class SDOEntityManager(EntityManager): @@ -329,4 +351,13 @@ class IETFHierarchyManager(object): entities.append(('IETF Working Groups', wgs)) return entities + def get_all_can_approve_codes(self, person): + entities = [] + for key in ['ietf', 'iesg', 'iab']: + 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 e6b79da62..d3a595f7b 100644 --- a/ietf/liaisons/views.py +++ b/ietf/liaisons/views.py @@ -1,15 +1,20 @@ # Copyright The IETF Trust 2007, All Rights Reserved +import datetime + from django.conf import settings from django.core.urlresolvers import reverse +from django.db.models import Q from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import render_to_response from django.template import RequestContext from django.utils import simplejson +from django.views.generic.list_detail import object_list, object_detail -from ietf.liaisons.accounts import get_person_for_user +from ietf.liaisons.accounts import (get_person_for_user, can_add_outgoing_liaison, + can_add_incoming_liaison) from ietf.liaisons.decorators import can_submit_liaison from ietf.liaisons.forms import liaison_form_factory -from ietf.liaisons.models import SDOs +from ietf.liaisons.models import LiaisonDetail, OutgoingLiaisonApproval from ietf.liaisons.utils import IETFHM @@ -24,12 +29,7 @@ def add_liaison(request): if not settings.DEBUG: liaison.send_by_mail() else: - mail = liaison.send_by_email(fake=True) - return render_to_response('liaisons/liaison_mail_detail.html', - {'mail': mail, - 'message': mail.message(), - 'liaison': liaison}, - context_instance=RequestContext(request)) + return _fake_email_view(request, liaison) return HttpResponseRedirect(reverse('liaison_list')) else: form = liaison_form_factory(request) @@ -41,6 +41,7 @@ def add_liaison(request): ) +@can_submit_liaison def get_info(request): person = get_person_for_user(request.user) @@ -71,3 +72,76 @@ def get_info(request): 'needs_approval': from_entity.needs_approval(person=person)}) json_result = simplejson.dumps(result) return HttpResponse(json_result, mimetype='text/javascript') + + +def _fake_email_view(request, liaison): + mail = liaison.send_by_email(fake=True) + return render_to_response('liaisons/liaison_mail_detail.html', + {'mail': mail, + 'message': mail.message(), + 'liaison': liaison}, + context_instance=RequestContext(request)) + + +def liaison_list(request): + user = request.user + can_send_outgoing = can_add_outgoing_liaison(user) + can_send_incoming = can_add_incoming_liaison(user) + + person = get_person_for_user(request.user) + approval_codes = IETFHM.get_all_can_approve_codes(person) + can_approve = LiaisonDetail.objects.filter(approval__isnull=False, approval__approved=False, from_raw_code__in=approval_codes).count() + + public_liaisons = LiaisonDetail.objects.filter(Q(approval__isnull=True)|Q(approval__approved=True)).order_by("-submitted_date") + + 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_send_incoming': can_send_incoming, + 'can_send_outgoing': can_send_outgoing}, + ) + + +@can_submit_liaison +def liaison_approval_list(request): + person = get_person_for_user(request.user) + approval_codes = IETFHM.get_all_can_approve_codes(person) + to_approve = LiaisonDetail.objects.filter(approval__isnull=False, approval__approved=False, from_raw_code__in=approval_codes).order_by("-submitted_date") + + return object_list(request, to_approve, + allow_empty=True, + template_name='liaisons/liaisondetail_approval_list.html', + ) + + +@can_submit_liaison +def liaison_approval_detail(request, object_id): + person = get_person_for_user(request.user) + approval_codes = IETFHM.get_all_can_approve_codes(person) + to_approve = LiaisonDetail.objects.filter(approval__isnull=False, approval__approved=False, from_raw_code__in=approval_codes).order_by("-submitted_date") + + if request.method=='POST' and request.POST.get('do_approval', False): + try: + liaison = to_approve.get(pk=object_id) + approval = liaison.approval + if not approval: + approval = OutgoingLiaisonApproval.objects.create(approved=True, approval_date=datetime.datetime.now()) + liaison.approval = approval + liaison.save() + else: + approval.approved=True + approval.save() + if not settings.DEBUG: + liaison.send_by_mail() + else: + return _fake_email_view(request, liaison) + 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', + ) diff --git a/ietf/templates/liaisons/liaison_main_management.html b/ietf/templates/liaisons/liaison_main_management.html new file mode 100644 index 000000000..edc259a6f --- /dev/null +++ b/ietf/templates/liaisons/liaison_main_management.html @@ -0,0 +1,62 @@ +{% 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/liaisondetail_approval_detail.html b/ietf/templates/liaisons/liaisondetail_approval_detail.html new file mode 100644 index 000000000..2391dc956 --- /dev/null +++ b/ietf/templates/liaisons/liaisondetail_approval_detail.html @@ -0,0 +1,10 @@ +{% 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_approval_list.html b/ietf/templates/liaisons/liaisondetail_approval_list.html new file mode 100644 index 000000000..aba7092d4 --- /dev/null +++ b/ietf/templates/liaisons/liaisondetail_approval_list.html @@ -0,0 +1,13 @@ +{% extends "liaisons/liaisondetail_list.html" %} +{# Copyright The IETF Trust 2007, All Rights Reserved #} +{% block title %}Pending Liaison Statements{% endblock %} + +{% block content-title %} +

Pending Liaison Statements

+{% endblock %} + +{% block management-links %} + +{% endblock %} diff --git a/ietf/templates/liaisons/liaisondetail_list.html b/ietf/templates/liaisons/liaisondetail_list.html index 72cf3f6a4..55ee6f192 100644 --- a/ietf/templates/liaisons/liaisondetail_list.html +++ b/ietf/templates/liaisons/liaisondetail_list.html @@ -8,9 +8,19 @@ {% block content %} +{% block content-title %}

Liaison Statements

+{% endblock %} -

Liaison Statement Manager (for liaison contacts only; password required)

+{% block management-links %} +{% if can_manage %} + +{% endif %} +{% endblock %}
DateFromToTitle