From 0144ef366b6ea4a4bf9a110ae45d802bdb77e25a Mon Sep 17 00:00:00 2001
From: Ole Laursen
").replace("\n"," {{ group.charter_text|escape|format_charter|safe }} {{ description|linebreaks }} + {{ milestones_in_review|length }} new milestone{{ milestones_in_review|pluralize }}
-currently in {{ milestone_reviewer }} review. + {{ milestones_in_review|length }} new milestone{{ milestones_in_review|pluralize }}
+ currently in {{ milestone_reviewer }} review.
\n")
-
@register.filter
def indent(value, numspaces=2):
replacement = "\n" + " " * int(numspaces)
diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py
index 4c5ad4c88..133ab5ea0 100644
--- a/ietf/doc/views_doc.py
+++ b/ietf/doc/views_doc.py
@@ -243,7 +243,7 @@ def document_main(request, name, rev=None):
elif group.type_id in ("rg", "wg"):
submission = "%s %s" % (group.acronym, group.type)
if group.type_id == "wg":
- submission = "%s" % (urlreverse("group_docs", kwargs=dict(group_type=doc.group.type_id, acronym=doc.group.acronym)), submission)
+ submission = "%s" % (urlreverse("group_home", kwargs=dict(group_type=doc.group.type_id, acronym=doc.group.acronym)), submission)
if doc.stream_id and doc.get_state_slug("draft-stream-%s" % doc.stream_id) == "c-adopt":
submission = "candidate for %s" % submission
diff --git a/ietf/group/features.py b/ietf/group/features.py
new file mode 100644
index 000000000..532dfc06a
--- /dev/null
+++ b/ietf/group/features.py
@@ -0,0 +1,19 @@
+class GroupFeatures(object):
+ """Configuration of group pages and processes to have this collected
+ in one place rather than scattered over the group page views."""
+
+ has_milestones = False
+ has_chartering_process = False
+ has_documents = False
+ customize_workflow = False
+ default_tab = "group_charter"
+
+ def __init__(self, group):
+ if group.type_id in ("wg", "rg"):
+ self.has_milestones = True
+ self.has_chartering_process = True
+ self.has_documents = True
+ self.customize_workflow = True
+ self.default_tab = "group_docs"
+ elif group.type_id in ("team",):
+ self.has_documents = True
diff --git a/ietf/group/info.py b/ietf/group/info.py
index 4068873f4..22a17a2d6 100644
--- a/ietf/group/info.py
+++ b/ietf/group/info.py
@@ -38,11 +38,12 @@ from tempfile import mkstemp
from django.shortcuts import get_object_or_404, render
from django.template.loader import render_to_string
-from django.http import HttpResponse, Http404
+from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.conf import settings
from django.core.urlresolvers import reverse as urlreverse
from django.views.decorators.cache import cache_page
from django.db.models import Q
+from django.utils.safestring import mark_safe
from ietf.doc.views_search import SearchForm, retrieve_search_results
from ietf.doc.models import State, DocAlias, RelatedDocument
@@ -58,10 +59,31 @@ def roles(group, role_name):
def fill_in_charter_info(group, include_drafts=False):
group.areadirector = group.ad.role_email("ad", group.parent) if group.ad else None
- group.chairs = roles(group, "chair")
- group.techadvisors = roles(group, "techadv")
- group.editors = roles(group, "editor")
- group.secretaries = roles(group, "secr")
+
+ personnel = {}
+ for r in Role.objects.filter(group=group).select_related("email", "person", "name"):
+ if r.name_id not in personnel:
+ personnel[r.name_id] = []
+ personnel[r.name_id].append(r)
+
+ if group.parent and group.parent.type_id == "area" and group.ad and "ad" not in personnel:
+ ad_roles = list(Role.objects.filter(group=group.parent, name="ad", person=group.ad))
+ if ad_roles:
+ personnel["ad"] = ad_roles
+
+ group.personnel = []
+ for role_name_slug, roles in personnel.iteritems():
+ label = roles[0].name.name
+ if len(roles) > 1:
+ if label.endswith("y"):
+ label = label[:-1] + "ies"
+ else:
+ label += "s"
+
+ group.personnel.append((role_name_slug, label, roles))
+
+ group.personnel.sort(key=lambda t: t[2][0].name.order)
+
milestone_state = "charter" if group.state_id == "proposed" else "active"
group.milestones = group.groupmilestone_set.filter(state=milestone_state).order_by('due')
@@ -70,18 +92,6 @@ def fill_in_charter_info(group, include_drafts=False):
else:
group.charter_text = u"Not chartered yet."
- if include_drafts:
- aliases = DocAlias.objects.filter(document__type="draft", document__group=group).select_related('document').order_by("name")
- group.drafts = []
- group.rfcs = []
- for a in aliases:
- if a.name.startswith("draft"):
- group.drafts.append(a)
- else:
- group.rfcs.append(a)
- a.rel = RelatedDocument.objects.filter(source=a.document).distinct()
- a.invrel = RelatedDocument.objects.filter(target=a).distinct()
-
def extract_last_name(role):
return role.person.name_parts()[3]
@@ -113,6 +123,32 @@ def wg_summary_acronym(request, group_type):
'groups': groups },
content_type='text/plain; charset=UTF-8')
+def fill_in_wg_roles(group):
+ def get_roles(slug, default):
+ for role_slug, label, roles in group.personnel:
+ if slug == role_slug:
+ return roles
+ return default
+
+ group.chairs = get_roles("chair", [])
+ ads = get_roles("ad", [])
+ group.areadirector = ads[0] if ads else None
+ group.techadvisors = get_roles("techadv", [])
+ group.editors = get_roles("editor", [])
+ group.secretaries = get_roles("secr", [])
+
+def fill_in_wg_drafts(group):
+ aliases = DocAlias.objects.filter(document__type="draft", document__group=group).select_related('document').order_by("name")
+ group.drafts = []
+ group.rfcs = []
+ for a in aliases:
+ if a.name.startswith("draft"):
+ group.drafts.append(a)
+ else:
+ group.rfcs.append(a)
+ a.rel = RelatedDocument.objects.filter(source=a.document).distinct()
+ a.invrel = RelatedDocument.objects.filter(target=a).distinct()
+
def wg_charters(request, group_type):
if group_type != "wg":
raise Http404
@@ -121,7 +157,9 @@ def wg_charters(request, group_type):
area.ads = sorted(roles(area, "ad"), key=extract_last_name)
area.groups = Group.objects.filter(parent=area, type="wg", state="active").order_by("name")
for group in area.groups:
- fill_in_charter_info(group, include_drafts=True)
+ fill_in_charter_info(group)
+ fill_in_wg_roles(group)
+ fill_in_wg_drafts(group)
group.area = area
return render(request, 'group/1wg-charters.txt',
{ 'areas': areas },
@@ -137,7 +175,9 @@ def wg_charters_by_acronym(request, group_type):
groups = Group.objects.filter(type="wg", state="active").exclude(parent=None).order_by("acronym")
for group in groups:
- fill_in_charter_info(group, include_drafts=True)
+ fill_in_charter_info(group)
+ fill_in_wg_roles(group)
+ fill_in_wg_drafts(group)
group.area = areas.get(group.parent_id)
return render(request, 'group/1wg-charters-by-acronym.txt',
{ 'groups': groups },
@@ -216,29 +256,52 @@ def concluded_groups(request):
return render(request, 'group/concluded_groups.html',
dict(group_types=group_types))
-def construct_group_menu_context(request, group, selected, others):
+def construct_group_menu_context(request, group, selected, group_type, others):
"""Return context with info for the group menu filled in."""
+ kwargs = dict(acronym=group.acronym)
+ if group_type:
+ kwargs["group_type"] = group_type
+
+ # entries
+ entries = []
+ if group.features().has_documents:
+ entries.append(("Documents", urlreverse("ietf.group.info.group_documents", kwargs=kwargs)))
+ entries.append(("Charter", urlreverse("ietf.group.info.group_charter", kwargs=kwargs)))
+ entries.append(("History", urlreverse("ietf.group.info.history", kwargs=kwargs)))
+ if group.features().has_documents:
+ entries.append(("Dependency Graph", urlreverse("ietf.group.info.dependencies_pdf", kwargs=kwargs)))
+
+ if group.list_archive.startswith("http:") or group.list_archive.startswith("https:") or group.list_archive.startswith("ftp:"):
+ entries.append((mark_safe("List Archive »"), group.list_archive))
+ if group.has_tools_page():
+ entries.append((mark_safe("Tools %s Page »" % group.type.name), "https://tools.ietf.org/%s/%s/" % (group.type_id, group.acronym)))
+
+
+ # actions
actions = []
is_chair = group.has_role(request.user, "chair")
can_manage = can_manage_group_type(request.user, group.type_id)
- if group.state_id != "proposed" and (is_chair or can_manage):
- actions.append((u"Add or edit milestones", urlreverse("group_edit_milestones", kwargs=dict(group_type=group.type_id, acronym=group.acronym))))
+ if group.features().has_milestones:
+ if group.state_id != "proposed" and (is_chair or can_manage):
+ actions.append((u"Add or edit milestones", urlreverse("group_edit_milestones", kwargs=kwargs)))
if group.state_id != "conclude" and can_manage:
- actions.append((u"Edit group", urlreverse("group_edit", kwargs=dict(group_type=group.type_id, acronym=group.acronym))))
+ actions.append((u"Edit group", urlreverse("group_edit", kwargs=kwargs)))
- if is_chair or can_manage:
- actions.append((u"Customize workflow", urlreverse("ietf.group.edit.customize_workflow", kwargs=dict(group_type=group.type_id, acronym=group.acronym))))
+ if group.features().customize_workflow and (is_chair or can_manage):
+ actions.append((u"Customize workflow", urlreverse("ietf.group.edit.customize_workflow", kwargs=kwargs)))
if group.state_id in ("active", "dormant") and can_manage:
- actions.append((u"Request closing group", urlreverse("ietf.group.edit.conclude", kwargs=dict(group_type=group.type_id, acronym=group.acronym))))
+ actions.append((u"Request closing group", urlreverse("ietf.group.edit.conclude", kwargs=kwargs)))
d = {
"group": group,
- "selected": selected,
+ "selected_menu_entry": selected,
+ "menu_entries": entries,
"menu_actions": actions,
+ "group_type": group_type,
}
d.update(others)
@@ -278,22 +341,37 @@ def search_for_group_documents(group):
return docs, meta, docs_related, meta_related
-def group_documents(request, group_type, acronym):
- group = get_object_or_404(Group, type=group_type, acronym=acronym)
+def get_group_or_404(acronym, group_type):
+ """Helper to overcome the schism between group-type prefixed URLs and generic."""
+ possible_groups = Group.objects.all()
+ if group_type:
+ possible_groups = possible_groups.filter(type=group_type)
+
+ return get_object_or_404(possible_groups, acronym=acronym)
+
+def group_home(request, acronym, group_type=None):
+ group = get_group_or_404(acronym, group_type)
+ kwargs = dict(acronym=group.acronym)
+ if group_type:
+ kwargs["group_type"] = group_type
+ return HttpResponseRedirect(urlreverse(group.features().default_tab, kwargs=kwargs))
+
+def group_documents(request, acronym, group_type=None):
+ group = get_group_or_404(acronym, group_type)
docs, meta, docs_related, meta_related = search_for_group_documents(group)
return render(request, 'group/group_documents.html',
- construct_group_menu_context(request, group, "documents", {
+ construct_group_menu_context(request, group, "documents", group_type, {
'docs': docs,
'meta': meta,
'docs_related': docs_related,
'meta_related': meta_related
}))
-def group_documents_txt(request, group_type, acronym):
+def group_documents_txt(request, acronym, group_type=None):
"""Return tabulator-separated rows with documents for group."""
- group = get_object_or_404(Group, type=group_type, acronym=acronym)
+ group = get_group_or_404(acronym, group_type)
docs, meta, docs_related, meta_related = search_for_group_documents(group)
@@ -316,39 +394,45 @@ def group_documents_txt(request, group_type, acronym):
return HttpResponse(u"\n".join(rows), content_type='text/plain; charset=UTF-8')
-def group_charter(request, group_type, acronym):
- group = get_object_or_404(Group, type=group_type, acronym=acronym)
+def group_charter(request, acronym, group_type=None):
+ group = get_group_or_404(acronym, group_type)
- fill_in_charter_info(group, include_drafts=False)
- group.delegates = roles(group, "delegate")
+ fill_in_charter_info(group)
e = group.latest_event(type__in=("changed_state", "requested_close",))
requested_close = group.state_id != "conclude" and e and e.type == "requested_close"
- long_group_types = dict(
- wg="Working Group",
- rg="Research Group",
- )
+ verbose_group_types = {
+ "wg": "Working Group",
+ "rg": "Research Group",
+ "team": "Team",
+ }
can_manage = can_manage_group_type(request.user, group.type_id)
+ if group.features().has_chartering_process:
+ description = group.charter_text
+ else:
+ description = group.description or "No description entered yet."
+
return render(request, 'group/group_charter.html',
- construct_group_menu_context(request, group, "charter", {
+ construct_group_menu_context(request, group, "charter", group_type, {
"milestones_in_review": group.groupmilestone_set.filter(state="review"),
"milestone_reviewer": milestone_reviewer_for_group_type(group_type),
"requested_close": requested_close,
- "long_group_type":long_group_types.get(group_type, "Group"),
+ "verbose_group_type": verbose_group_types.get(group.type_id, "Group"),
"can_manage": can_manage,
+ "description": description,
}))
-def history(request, group_type, acronym):
- group = get_object_or_404(Group, acronym=acronym)
+def history(request, acronym, group_type=None):
+ group = get_group_or_404(acronym, group_type)
events = group.groupevent_set.all().select_related('by').order_by('-time', '-id')
return render(request, 'group/history.html',
- construct_group_menu_context(request, group, "history", {
+ construct_group_menu_context(request, group, "history", group_type, {
"events": events,
}))
@@ -470,19 +554,17 @@ def make_dot(group):
dict( nodes=nodes, edges=edges )
)
-def dependencies_dot(request, group_type, acronym):
-
- group = get_object_or_404(Group, acronym=acronym)
+def dependencies_dot(request, acronym, group_type=None):
+ group = get_group_or_404(acronym, group_type)
return HttpResponse(make_dot(group),
content_type='text/plain; charset=UTF-8'
)
@cache_page ( 60 * 60 )
-def dependencies_pdf(request, group_type, acronym):
+def dependencies_pdf(request, acronym, group_type=None):
+ group = get_group_or_404(acronym, group_type)
- group = get_object_or_404(Group, acronym=acronym)
-
dothandle,dotname = mkstemp()
os.close(dothandle)
dotfile = open(dotname,"w")
diff --git a/ietf/group/migrations/0007_auto__add_field_group_description__add_field_grouphistory_description.py b/ietf/group/migrations/0007_auto__add_field_group_description__add_field_grouphistory_description.py
new file mode 100644
index 000000000..e9266e459
--- /dev/null
+++ b/ietf/group/migrations/0007_auto__add_field_group_description__add_field_grouphistory_description.py
@@ -0,0 +1,317 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding field 'Group.description'
+ db.add_column(u'group_group', 'description',
+ self.gf('django.db.models.fields.TextField')(default='', blank=True),
+ keep_default=False)
+
+ # Adding field 'GroupHistory.description'
+ db.add_column(u'group_grouphistory', 'description',
+ self.gf('django.db.models.fields.TextField')(default='', blank=True),
+ keep_default=False)
+
+
+ def backwards(self, orm):
+ # Deleting field 'Group.description'
+ db.delete_column(u'group_group', 'description')
+
+ # Deleting field 'GroupHistory.description'
+ db.delete_column(u'group_grouphistory', 'description')
+
+
+ models = {
+ u'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ u'auth.permission': {
+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ u'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
+ },
+ u'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ u'doc.document': {
+ 'Meta': {'object_name': 'Document'},
+ 'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': u"orm['person.Person']"}),
+ 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['person.Email']", 'symmetrical': 'False', 'through': u"orm['doc.DocumentAuthor']", 'blank': 'True'}),
+ 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
+ 'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
+ 'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
+ 'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
+ 'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
+ 'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': u"orm['person.Person']"}),
+ 'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
+ 'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
+ 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
+ },
+ u'doc.documentauthor': {
+ 'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']"}),
+ 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
+ },
+ u'doc.state': {
+ 'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
+ 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'previous_states'", 'blank': 'True', 'to': u"orm['doc.State']"}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
+ 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.StateType']"}),
+ 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ u'doc.statetype': {
+ 'Meta': {'object_name': 'StateType'},
+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'})
+ },
+ u'group.changestategroupevent': {
+ 'Meta': {'ordering': "['-time', 'id']", 'object_name': 'ChangeStateGroupEvent', '_ormbases': [u'group.GroupEvent']},
+ u'groupevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['group.GroupEvent']", 'unique': 'True', 'primary_key': 'True'}),
+ 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupStateName']"})
+ },
+ u'group.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40'}),
+ 'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']", 'null': 'True', 'blank': 'True'}),
+ 'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': u"orm['doc.Document']"}),
+ 'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
+ 'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
+ 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupStateName']", 'null': 'True'}),
+ 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupTypeName']", 'null': 'True'}),
+ 'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ u'group.groupevent': {
+ 'Meta': {'ordering': "['-time', 'id']", 'object_name': 'GroupEvent'},
+ 'by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"}),
+ 'desc': ('django.db.models.fields.TextField', [], {}),
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ u'group.grouphistory': {
+ 'Meta': {'object_name': 'GroupHistory'},
+ 'acronym': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+ 'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']", 'null': 'True', 'blank': 'True'}),
+ 'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': u"orm['group.Group']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
+ 'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
+ 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupStateName']", 'null': 'True'}),
+ 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupTypeName']", 'null': 'True'}),
+ 'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ u'group.groupmilestone': {
+ 'Meta': {'ordering': "['due', 'id']", 'object_name': 'GroupMilestone'},
+ 'desc': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
+ 'docs': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.Document']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'due': ('django.db.models.fields.DateField', [], {}),
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'resolved': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+ 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupMilestoneStateName']"}),
+ 'time': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
+ },
+ u'group.groupmilestonehistory': {
+ 'Meta': {'ordering': "['due', 'id']", 'object_name': 'GroupMilestoneHistory'},
+ 'desc': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
+ 'docs': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.Document']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'due': ('django.db.models.fields.DateField', [], {}),
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'milestone': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': u"orm['group.GroupMilestone']"}),
+ 'resolved': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+ 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupMilestoneStateName']"}),
+ 'time': ('django.db.models.fields.DateTimeField', [], {})
+ },
+ u'group.groupstatetransitions': {
+ 'Meta': {'object_name': 'GroupStateTransitions'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'previous_groupstatetransitions_states'", 'symmetrical': 'False', 'to': u"orm['doc.State']"}),
+ 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.State']"})
+ },
+ u'group.groupurl': {
+ 'Meta': {'object_name': 'GroupURL'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
+ },
+ u'group.milestonegroupevent': {
+ 'Meta': {'ordering': "['-time', 'id']", 'object_name': 'MilestoneGroupEvent', '_ormbases': [u'group.GroupEvent']},
+ u'groupevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['group.GroupEvent']", 'unique': 'True', 'primary_key': 'True'}),
+ 'milestone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.GroupMilestone']"})
+ },
+ u'group.role': {
+ 'Meta': {'object_name': 'Role'},
+ 'email': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']"}),
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.RoleName']"}),
+ 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"})
+ },
+ u'group.rolehistory': {
+ 'Meta': {'object_name': 'RoleHistory'},
+ 'email': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']"}),
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.GroupHistory']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.RoleName']"}),
+ 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"})
+ },
+ u'name.doctagname': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'},
+ 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
+ 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ u'name.doctypename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'},
+ 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
+ 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ u'name.groupmilestonestatename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'GroupMilestoneStateName'},
+ 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
+ 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ u'name.groupstatename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'},
+ 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
+ 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ u'name.grouptypename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'},
+ 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
+ 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ u'name.intendedstdlevelname': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'},
+ 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
+ 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ u'name.rolename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'RoleName'},
+ 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
+ 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ u'name.stdlevelname': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'},
+ 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
+ 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ u'name.streamname': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'StreamName'},
+ 'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
+ 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ u'person.email': {
+ 'Meta': {'object_name': 'Email'},
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
+ 'person': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']", 'null': 'True'}),
+ 'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+ },
+ u'person.person': {
+ 'Meta': {'object_name': 'Person'},
+ 'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
+ 'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+ 'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['group']
\ No newline at end of file
diff --git a/ietf/group/models.py b/ietf/group/models.py
index 7ea66a8d1..e66572b37 100644
--- a/ietf/group/models.py
+++ b/ietf/group/models.py
@@ -17,6 +17,7 @@ class GroupInfo(models.Model):
state = models.ForeignKey(GroupStateName, null=True)
type = models.ForeignKey(GroupTypeName, null=True)
parent = models.ForeignKey('Group', blank=True, null=True)
+ description = models.TextField(blank=True)
ad = models.ForeignKey(Person, verbose_name="AD", blank=True, null=True)
list_email = models.CharField(max_length=64, blank=True)
list_subscribe = models.CharField(max_length=255, blank=True)
@@ -79,6 +80,10 @@ class Group(GroupInfo):
def bg_color(self):
return bg_group_colors[self.upcase_acronym]
+ def features(self):
+ from ietf.group.features import GroupFeatures
+ return GroupFeatures(self)
+
def json_url(self):
return "/group/%s.json" % (self.acronym,)
diff --git a/ietf/group/urls.py b/ietf/group/urls.py
index ea049c7e5..f3cf567b3 100644
--- a/ietf/group/urls.py
+++ b/ietf/group/urls.py
@@ -7,6 +7,20 @@ urlpatterns = patterns('',
(r'^chartering/$', 'ietf.group.info.chartering_groups'),
(r'^chartering/create/(?P
{% for group in groups %}
-
diff --git a/ietf/templates/group/active_wgs.html b/ietf/templates/group/active_wgs.html
index d631a2405..a9ce6e530 100644
--- a/ietf/templates/group/active_wgs.html
+++ b/ietf/templates/group/active_wgs.html
@@ -80,7 +80,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{{ group.acronym }}
+ {{ group.acronym }}
{{ group.name }}
{% for chair in group.chairs %}{{ chair.person.plain_name }}{% if not forloop.last %}, {% endif %}{% endfor %}
{% for group in area.groups %}
-
+ {% if group.features.has_chartering_process %}
{{ group.acronym }}
+ {{ group.acronym }}
{% for ad in area.ads %}{% if ad.person_id == group.ad_id %}{% endif %}{% endfor %}
{{ group.name }}
{% for chair in group.chairs %}{{ chair.person.plain_name }}{% if not forloop.last %}, {% endif %}{% endfor %}
diff --git a/ietf/templates/group/group_base.html b/ietf/templates/group/group_base.html
index d605d803a..ec1e0c381 100644
--- a/ietf/templates/group/group_base.html
+++ b/ietf/templates/group/group_base.html
@@ -66,16 +66,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ {% endif %}
Charter:
@@ -52,77 +48,28 @@ is occasionally incorrect.
{% endif %}
+ {% for slug, label, roles in group.personnel %}
Personnel
-
+ {% endfor %}
- {% if group.parent.type_id == "area" %}
- Chair{{ group.chairs|pluralize }}:
+ {{ label }}:
- {% for chair in group.chairs %}
- {{ chair.person.plain_name }} <{{ chair.email.address }}>
+ {% for r in roles %}
+ {{ r.person.plain_name }} <{{ r.email.address }}>
{% endfor %}
- {% endif %}
-
- {% if group.techadvisors %}
- Area Director:
-
- {% if group.areadirector %}
- {{ group.areadirector.person.plain_name }} <{{ group.areadirector.address }}>
- {% else %}?{% endif %}
-
-
-
- {% endif %}
-
- {% if group.editors %}
- Tech Advisor{{ group.techadvisors|pluralize }}:
-
- {% for techadvisor in group.techadvisors %}
- {{ techadvisor.person.plain_name }} <{{ techadvisor.email.address }}>
-
- {% endfor %}
-
-
- {% endif %}
-
- {% if group.secretaries %}
- Editor{{ group.editors|pluralize }}:
-
- {% for editor in group.editors %}
- {{ editor.person.plain_name }} <{{ editor.email.address }}>
-
- {% endfor %}
-
-
- {% endif %}
-
- {% if group.delegates %}
- Secretar{{ group.secretaries|pluralize:"y,ies" }}:
-
- {% for secretary in group.secretaries %}
- {{ secretary.person.plain_name }} <{{ secretary.email.address }}>
-
- {% endfor %}
-
-
- {% endif %}
-
+ {% if group.list_email %}
Delegate{{ group.delegates|pluralize }}:
-
- {% for delegate in group.delegates %}
- {{ delegate.person.plain_name }} <{{ delegate.email.address }}>
-
- {% endfor %}
- Mailing List Address: {{ group.list_email|urlize }} To Subscribe: {{ group.list_subscribe|urlize }}
+ {% endif %}
{% if group.state_id != "conclude" %}
Archive: {{ group.list_archive|urlize }}
@@ -152,17 +99,19 @@ is occasionally incorrect.
{% endif %}
{% endwith %}
-Jabber Chat Charter for {% if group.state_id == "proposed" %}Proposed{% endif %} {{ long_group_type }}
+Charter for {% if group.state_id == "proposed" %}Proposed{% endif %} {{ verbose_group_type }}
-{% if group.state_id == "proposed" %}Proposed{% endif %} Milestones
+{% if group.features.has_milestones %}
+ {% if group.state_id == "proposed" %}Proposed{% endif %} Milestones
-{% include "group/milestones.html" with milestones=group.milestones %}
+ {% include "group/milestones.html" with milestones=group.milestones %}
-{% if milestones_in_review %}
-