From 0144ef366b6ea4a4bf9a110ae45d802bdb77e25a Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Tue, 20 May 2014 10:19:26 +0000
Subject: [PATCH 01/26] Add description field on group, enable most group info
page views to work without a group type to pave the way for /group//
URLs, make charter page more generic, enable it to handle descriptions and
walk over all personnel instead of picking out specific types of roles, add a
redirect view on /group// to lead to either documents/ or charter/,
add simple group.features() class to be able to condition on group features
rather than specific group types, adjust group pages menu accordingly -
Legacy-Id: 7758
---
ietf/community/display.py | 2 +-
ietf/doc/templatetags/ietf_filters.py | 4 -
ietf/doc/views_doc.py | 2 +-
ietf/group/features.py | 19 ++
ietf/group/info.py | 184 +++++++---
...ion__add_field_grouphistory_description.py | 317 ++++++++++++++++++
ietf/group/models.py | 5 +
ietf/group/urls.py | 14 +
ietf/name/migrations/0020_sort_role_names.py | 207 ++++++++++++
ietf/templates/group/active_rgs.html | 2 +-
ietf/templates/group/active_wgs.html | 2 +-
ietf/templates/group/group_base.html | 13 +-
ietf/templates/group/group_charter.html | 87 +----
.../group/group_entry_with_charter.txt | 2 +-
14 files changed, 721 insertions(+), 139 deletions(-)
create mode 100644 ietf/group/features.py
create mode 100644 ietf/group/migrations/0007_auto__add_field_group_description__add_field_grouphistory_description.py
create mode 100644 ietf/name/migrations/0020_sort_role_names.py
diff --git a/ietf/community/display.py b/ietf/community/display.py
index d42769597..75d2ba421 100644
--- a/ietf/community/display.py
+++ b/ietf/community/display.py
@@ -88,7 +88,7 @@ class WGField(DisplayField):
if raw:
return document.group.acronym
else:
- return '%s ' % (urlreverse('group_docs', kwargs=dict(group_type=document.group.type_id, acronym=document.group.acronym)), document.group.acronym) if (document.group and document.group.acronym != 'none') else ''
+ return '%s ' % (urlreverse('group_home', kwargs=dict(group_type=document.group.type_id, acronym=document.group.acronym)), document.group.acronym) if (document.group and document.group.acronym != 'none') else ''
class ADField(DisplayField):
diff --git a/ietf/doc/templatetags/ietf_filters.py b/ietf/doc/templatetags/ietf_filters.py
index 2a0b0e132..2d03d55f4 100644
--- a/ietf/doc/templatetags/ietf_filters.py
+++ b/ietf/doc/templatetags/ietf_filters.py
@@ -22,10 +22,6 @@ def expand_comma(value):
long comma-separated lists."""
return value.replace(",", ", ")
-@register.filter(name='format_charter')
-def format_charter(value):
- return value.replace("\n\n", "
").replace("\n"," \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(wg|rg))/$', 'ietf.group.edit.edit', {'action': "charter"}, "group_create"),
(r'^concluded/$', 'ietf.group.info.concluded_groups'),
+ (r'^(?P[a-zA-Z0-9-._]+)/$', 'ietf.group.info.group_home', None, "group_home"),
+ (r'^(?P[a-zA-Z0-9-._]+)/documents/$', 'ietf.group.info.group_documents', None, "group_docs"),
+ (r'^(?P[a-zA-Z0-9-._]+)/charter/$', 'ietf.group.info.group_charter', None, 'group_charter'),
+ (r'^(?P[a-zA-Z0-9-._]+)/history/$', 'ietf.group.info.history'),
+ (r'^(?P[a-zA-Z0-9-._]+)/deps/dot/$', 'ietf.group.info.dependencies_dot'),
+ (r'^(?P[a-zA-Z0-9-._]+)/deps/pdf/$', 'ietf.group.info.dependencies_pdf'),
+ (r'^(?P[a-zA-Z0-9-._]+)/init-charter/', 'ietf.group.edit.submit_initial_charter'),
+ (r'^(?P[a-zA-Z0-9-._]+)/edit/$', 'ietf.group.edit.edit', {'action': "edit"}, "group_edit"),
+ (r'^(?P[a-zA-Z0-9-._]+)/conclude/$', 'ietf.group.edit.conclude'),
+ (r'^(?P[a-zA-Z0-9-._]+)/milestones/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "current"}, "group_edit_milestones"),
+ (r'^(?P[a-zA-Z0-9-._]+)/milestones/charter/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "charter"}, "group_edit_charter_milestones"),
+ (r'^(?P[a-zA-Z0-9-._]+)/milestones/charter/reset/$', 'ietf.group.milestones.reset_charter_milestones', None, "group_reset_charter_milestones"),
+ (r'^(?P[a-zA-Z0-9-._]+)/ajax/searchdocs/$', 'ietf.group.milestones.ajax_search_docs', None, "group_ajax_search_docs"),
+ (r'^(?P[a-zA-Z0-9-._]+)/workflow/$', 'ietf.group.edit.customize_workflow'),
)
diff --git a/ietf/name/migrations/0020_sort_role_names.py b/ietf/name/migrations/0020_sort_role_names.py
new file mode 100644
index 000000000..e15a11004
--- /dev/null
+++ b/ietf/name/migrations/0020_sort_role_names.py
@@ -0,0 +1,207 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+class Migration(DataMigration):
+
+ def forwards(self, orm):
+ "Write your forwards methods here."
+ orm.RoleName.objects.filter(slug="chair").update(order=1)
+ orm.RoleName.objects.filter(slug="ad").update(order=2)
+ orm.RoleName.objects.filter(slug="pre-ad").update(order=3)
+ orm.RoleName.objects.filter(slug="editor").update(order=5)
+ orm.RoleName.objects.filter(slug="secr").update(order=6)
+ orm.RoleName.objects.filter(slug="techadv").update(order=4)
+ orm.RoleName.objects.filter(slug="execdir").update(order=2)
+ orm.RoleName.objects.filter(slug="admdir").update(order=3)
+ orm.RoleName.objects.filter(slug="liaiman").update(order=4)
+ orm.RoleName.objects.filter(slug="auth").update(order=5)
+ orm.RoleName.objects.filter(slug="delegate").update(order=6)
+ orm.RoleName.objects.filter(slug="atlarge").update(order=10)
+ orm.RoleName.objects.filter(slug="member").update(order=7)
+ orm.RoleName.objects.filter(slug="liaison").update(order=11)
+ orm.RoleName.objects.filter(slug="advisor").update(order=4)
+ orm.RoleName.objects.filter(slug="announce").update(order=12)
+
+ def backwards(self, orm):
+ "Write your backwards methods here."
+
+ models = {
+ u'name.ballotpositionname': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'},
+ 'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ '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.constraintname': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'ConstraintName'},
+ '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'}),
+ 'penalty': ('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.dbtemplatetypename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'DBTemplateTypeName'},
+ '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.docrelationshipname': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'},
+ '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'}),
+ 'revname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
+ 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ u'name.docremindertypename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'},
+ '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.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.draftsubmissionstatename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'DraftSubmissionStateName'},
+ 'desc': ('django.db.models.fields.TextField', [], {'blank': '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['name.DraftSubmissionStateName']"}),
+ '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.feedbacktype': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'FeedbackType'},
+ '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.liaisonstatementpurposename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'LiaisonStatementPurposeName'},
+ '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.meetingtypename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'MeetingTypeName'},
+ '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.nomineepositionstate': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'NomineePositionState'},
+ '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.sessionstatusname': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'SessionStatusName'},
+ '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'name.timeslottypename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'TimeSlotTypeName'},
+ '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'})
+ }
+ }
+
+ complete_apps = ['name']
+ symmetrical = True
diff --git a/ietf/templates/group/active_rgs.html b/ietf/templates/group/active_rgs.html
index 58b8ca7b4..b6f3c6aeb 100644
--- a/ietf/templates/group/active_rgs.html
+++ b/ietf/templates/group/active_rgs.html
@@ -22,7 +22,7 @@
{% for group in groups %}
- {{ group.acronym }}
+ {{ group.acronym }}
{{ group.name }}
{% for chair in group.chairs %}{{ chair.person.plain_name }} {% if not forloop.last %}, {% endif %}{% endfor %}
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.
{% for group in area.groups %}
- {{ 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.
{% if menu_actions %}
diff --git a/ietf/templates/group/group_charter.html b/ietf/templates/group/group_charter.html
index 1481229e3..9fb5ca5ba 100644
--- a/ietf/templates/group/group_charter.html
+++ b/ietf/templates/group/group_charter.html
@@ -3,11 +3,6 @@
{% load ietf_filters %}
{% block group_subtitle %}Charter{% endblock %}
-{% block morecss %}
-{{ block.super }}
-h2 a.button { margin-left: 0.5em; font-size: 13px; }
-{% endblock %}
-
{% block group_content %}
@@ -39,6 +34,7 @@ is occasionally incorrect.
+ {% if group.features.has_chartering_process %}
Charter:
@@ -52,77 +48,28 @@ is occasionally incorrect.
{% endif %}
+ {% endif %}
Personnel
+ {% for slug, label, roles in group.personnel %}
- 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 %}
+ {% endfor %}
- {% if group.parent.type_id == "area" %}
- Area Director:
-
- {% if group.areadirector %}
- {{ group.areadirector.person.plain_name }} <{{ group.areadirector.address }}>
- {% else %}?{% endif %}
-
-
- {% endif %}
-
- {% if group.techadvisors %}
-
- Tech Advisor{{ group.techadvisors|pluralize }}:
-
- {% for techadvisor in group.techadvisors %}
- {{ techadvisor.person.plain_name }} <{{ techadvisor.email.address }}>
- {% endfor %}
-
-
- {% endif %}
-
- {% if group.editors %}
-
- Editor{{ group.editors|pluralize }}:
-
- {% for editor in group.editors %}
- {{ editor.person.plain_name }} <{{ editor.email.address }}>
- {% endfor %}
-
-
- {% endif %}
-
- {% if group.secretaries %}
-
- Secretar{{ group.secretaries|pluralize:"y,ies" }}:
-
- {% for secretary in group.secretaries %}
- {{ secretary.person.plain_name }} <{{ secretary.email.address }}>
- {% endfor %}
-
-
- {% endif %}
-
- {% if group.delegates %}
-
- Delegate{{ group.delegates|pluralize }}:
-
- {% for delegate in group.delegates %}
- {{ delegate.person.plain_name }} <{{ delegate.email.address }}>
- {% endfor %}
-
-
- {% endif %}
-
+ {% if group.list_email %}
Mailing List
Address: {{ group.list_email|urlize }}
To Subscribe: {{ group.list_subscribe|urlize }}
Archive: {{ group.list_archive|urlize }}
+ {% endif %}
{% if group.state_id != "conclude" %}
Jabber Chat
@@ -152,17 +99,19 @@ is occasionally incorrect.
{% endif %}
{% endwith %}
-Charter for {% if group.state_id == "proposed" %}Proposed{% endif %} {{ long_group_type }}
+Charter for {% if group.state_id == "proposed" %}Proposed{% endif %} {{ verbose_group_type }}
-{{ group.charter_text|escape|format_charter|safe }}
+{{ description|linebreaks }}
-{% 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 %}
-+ {{ milestones_in_review|length }} new milestone{{ milestones_in_review|pluralize }}
-currently in {{ milestone_reviewer }} review.
+ {% if milestones_in_review %}
+ + {{ milestones_in_review|length }} new milestone{{ milestones_in_review|pluralize }}
+ currently in {{ milestone_reviewer }} review.
+ {% endif %}
{% endif %}
{% endblock %}
diff --git a/ietf/templates/group/group_entry_with_charter.txt b/ietf/templates/group/group_entry_with_charter.txt
index abde2531a..cfce907fb 100644
--- a/ietf/templates/group/group_entry_with_charter.txt
+++ b/ietf/templates/group/group_entry_with_charter.txt
@@ -13,7 +13,7 @@
{% for ad in group.area.ads %} {{ ad.person.plain_name }} <{{ ad.email.address }}>
{% endfor %}
{% if group.areadirector %} {{ group.area.name }} Advisor:
- {{ group.areadirector.person.plain_name }} <{{ group.areadirector.address }}>
+ {{ group.areadirector.person.plain_name }} <{{ group.areadirector.email.address }}>
{% endif %}{% if group.techadvisors %}
Tech Advisor{{ group.techadvisors|pluralize }}:
{% for techadvisor in group.techadvisors %} {{ techadvisor.person.plain_name }} <{{ techadvisor.email.address }}>
From d16bdb37237ba8dafdeb0b5937e96d1e049109f7 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Thu, 22 May 2014 15:08:37 +0000
Subject: [PATCH 02/26] Add support for editing Group.description in
Secretariat tools - Legacy-Id: 7767
---
ietf/secr/groups/forms.py | 2 +-
ietf/secr/templates/groups/view.html | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/ietf/secr/groups/forms.py b/ietf/secr/groups/forms.py
index 1c5b671cd..075deedc4 100644
--- a/ietf/secr/groups/forms.py
+++ b/ietf/secr/groups/forms.py
@@ -72,7 +72,7 @@ class GroupModelForm(forms.ModelForm):
class Meta:
model = Group
- fields = ('acronym','name','type','state','parent','ad','list_email','list_subscribe','list_archive','comments')
+ fields = ('acronym','name','type','state','parent','ad','list_email','list_subscribe','list_archive','description','comments')
def __init__(self, *args, **kwargs):
super(GroupModelForm, self).__init__(*args, **kwargs)
diff --git a/ietf/secr/templates/groups/view.html b/ietf/secr/templates/groups/view.html
index 575a473aa..fd2acb26b 100644
--- a/ietf/secr/templates/groups/view.html
+++ b/ietf/secr/templates/groups/view.html
@@ -46,6 +46,7 @@
Email Subscription: {{ group.list_subscribe }}
Email Archive: {{ group.list_archive }}
Charter: View Charter
+ Description: {{ group.description }}
Comments: {{ group.comments }}
Last Modified Date: {{ group.time }}
From 9579525ca468df56b50b92b04ba18eddf9316d02 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Thu, 22 May 2014 15:53:20 +0000
Subject: [PATCH 03/26] Add simple materials page, tie up some of the loose
ends in group generalization work - Legacy-Id: 7768
---
ietf/group/edit.py | 34 ++++++++++++++--
ietf/group/features.py | 5 ++-
ietf/group/info.py | 61 +++++++++++++++++++----------
ietf/group/milestones.py | 17 +++++---
ietf/group/models.py | 7 +++-
ietf/group/urls.py | 3 ++
ietf/group/urls_info.py | 3 +-
ietf/group/utils.py | 12 ++++++
ietf/templates/group/materials.html | 27 +++++++++++++
9 files changed, 133 insertions(+), 36 deletions(-)
create mode 100644 ietf/templates/group/materials.html
diff --git a/ietf/group/edit.py b/ietf/group/edit.py
index b77eafd62..9d5477698 100644
--- a/ietf/group/edit.py
+++ b/ietf/group/edit.py
@@ -7,7 +7,7 @@ import shutil
from django import forms
from django.shortcuts import render, get_object_or_404, redirect
-from django.http import HttpResponseForbidden
+from django.http import HttpResponseForbidden, Http404
from django.utils.html import mark_safe
from django.http import Http404, HttpResponse
from django.contrib.auth.decorators import login_required
@@ -18,7 +18,8 @@ from ietf.doc.models import DocAlias, DocTagName, Document, State, save_document
from ietf.doc.utils import get_tags_for_stream_id
from ietf.group.models import ( Group, Role, GroupEvent, GroupHistory, GroupStateName,
GroupStateTransitions, GroupTypeName, GroupURL, ChangeStateGroupEvent )
-from ietf.group.utils import save_group_in_history, can_manage_group_type
+from ietf.group.utils import (save_group_in_history, can_manage_group_type, can_manage_materials,
+ get_group_or_404)
from ietf.ietfauth.utils import has_role
from ietf.person.forms import EmailsField
from ietf.person.models import Person, Email
@@ -166,6 +167,9 @@ def submit_initial_charter(request, group_type, acronym=None):
return HttpResponseForbidden("You don't have permission to access this view")
group = get_object_or_404(Group, acronym=acronym)
+ if not group.features.has_chartering_process:
+ raise Http404
+
if not group.charter:
group.charter = get_or_create_initial_charter(group, group_type)
group.save()
@@ -188,6 +192,9 @@ def edit(request, group_type=None, acronym=None, action="edit"):
else:
raise Http404
+ if not group_type and group:
+ group_type = group.type_id
+
if request.method == 'POST':
form = GroupForm(request.POST, group=group, confirmed=request.POST.get("confirmed", False), group_type=group_type)
if form.is_valid():
@@ -330,7 +337,7 @@ class ConcludeForm(forms.Form):
@login_required
def conclude(request, group_type, acronym):
"""Request the closing of group, prompting for instructions."""
- group = get_object_or_404(Group, type=group_type, acronym=acronym)
+ group = get_group_or_404(acronym, group_type)
if not can_manage_group_type(request.user, group_type):
return HttpResponseForbidden("You don't have permission to access this view")
@@ -357,7 +364,10 @@ def conclude(request, group_type, acronym):
@login_required
def customize_workflow(request, group_type, acronym):
- group = get_object_or_404(Group, type=group_type, acronym=acronym)
+ group = get_group_or_404(acronym, group_type)
+ if not group.features.customize_workflow:
+ raise Http404
+
if (not has_role(request.user, "Secretariat") and
not group.role_set.filter(name="chair", person__user=request.user)):
return HttpResponseForbidden("You don't have permission to access this view")
@@ -445,3 +455,19 @@ def customize_workflow(request, group_type, acronym):
'states': states,
'tags': tags,
})
+
+@login_required
+def upload_materials(request, acronym, group_type=None):
+ group = get_group_or_404(acronym, group_type)
+ if not group.features.has_materials:
+ raise Http404
+
+ if not can_manage_materials(request.user, group):
+ return HttpResponseForbidden("You don't have permission to access this view")
+
+ # FIXME: fill in
+
+ return render(request, 'group/materials.html',
+ construct_group_menu_context(request, group, "materials", group_type, {
+ "materials": materials,
+ }))
diff --git a/ietf/group/features.py b/ietf/group/features.py
index 532dfc06a..e97d06529 100644
--- a/ietf/group/features.py
+++ b/ietf/group/features.py
@@ -4,7 +4,8 @@ class GroupFeatures(object):
has_milestones = False
has_chartering_process = False
- has_documents = False
+ has_documents = False # i.e. drafts/RFCs
+ has_materials = False
customize_workflow = False
default_tab = "group_charter"
@@ -16,4 +17,4 @@ class GroupFeatures(object):
self.customize_workflow = True
self.default_tab = "group_docs"
elif group.type_id in ("team",):
- self.has_documents = True
+ self.has_materials = True
diff --git a/ietf/group/info.py b/ietf/group/info.py
index 22a17a2d6..90a9c633d 100644
--- a/ietf/group/info.py
+++ b/ietf/group/info.py
@@ -46,12 +46,13 @@ 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
+from ietf.doc.models import Document, State, DocAlias, RelatedDocument
from ietf.doc.utils import get_chartering_type
from ietf.doc.templatetags.ietf_filters import clean_whitespace
from ietf.group.models import Group, Role, ChangeStateGroupEvent
from ietf.name.models import GroupTypeName
from ietf.group.utils import get_charter_text, can_manage_group_type, milestone_reviewer_for_group_type
+from ietf.group.utils import can_manage_materials, get_group_or_404
from ietf.utils.pipe import pipe
def roles(group, role_name):
@@ -256,6 +257,9 @@ def concluded_groups(request):
return render(request, 'group/concluded_groups.html',
dict(group_types=group_types))
+def get_group_materials(group):
+ return Document.objects.filter(group=group).exclude(states__slug="deleted")#.exclude(session=None).exclude(type="charter")
+
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)
@@ -264,11 +268,13 @@ def construct_group_menu_context(request, group, selected, group_type, others):
# entries
entries = []
- if group.features().has_documents:
+ 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)))
+ if group.features.has_materials and get_group_materials(group).exists():
+ entries.append(("Materials", urlreverse("ietf.group.info.materials", kwargs=kwargs)))
entries.append(("History", urlreverse("ietf.group.info.history", kwargs=kwargs)))
- if group.features().has_documents:
+ 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:"):
@@ -283,14 +289,17 @@ def construct_group_menu_context(request, group, selected, group_type, others):
is_chair = group.has_role(request.user, "chair")
can_manage = can_manage_group_type(request.user, group.type_id)
- if group.features().has_milestones:
+ 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.features.has_materials and can_manage_materials(request.user, group):
+ actions.append((u"Upload materials", urlreverse("group_upload_materials", kwargs=kwargs)))
+
if group.state_id != "conclude" and can_manage:
actions.append((u"Edit group", urlreverse("group_edit", kwargs=kwargs)))
- if group.features().customize_workflow and (is_chair or can_manage):
+ 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:
@@ -341,23 +350,17 @@ def search_for_group_documents(group):
return docs, meta, docs_related, meta_related
-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))
+ 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)
+ if not group.features.has_documents:
+ raise Http404
docs, meta, docs_related, meta_related = search_for_group_documents(group)
@@ -372,6 +375,8 @@ def group_documents(request, acronym, group_type=None):
def group_documents_txt(request, acronym, group_type=None):
"""Return tabulator-separated rows with documents for group."""
group = get_group_or_404(acronym, group_type)
+ if not group.features.has_documents:
+ raise Http404
docs, meta, docs_related, meta_related = search_for_group_documents(group)
@@ -393,7 +398,6 @@ def group_documents_txt(request, acronym, group_type=None):
return HttpResponse(u"\n".join(rows), content_type='text/plain; charset=UTF-8')
-
def group_charter(request, acronym, group_type=None):
group = get_group_or_404(acronym, group_type)
@@ -410,10 +414,10 @@ def group_charter(request, acronym, group_type=None):
can_manage = can_manage_group_type(request.user, group.type_id)
- if group.features().has_chartering_process:
+ if group.features.has_chartering_process:
description = group.charter_text
else:
- description = group.description or "No description entered yet."
+ description = group.description or "No description yet."
return render(request, 'group/group_charter.html',
construct_group_menu_context(request, group, "charter", group_type, {
@@ -433,10 +437,21 @@ def history(request, acronym, group_type=None):
return render(request, 'group/history.html',
construct_group_menu_context(request, group, "history", group_type, {
- "events": events,
- }))
-
-
+ "events": events,
+ }))
+
+def materials(request, acronym, group_type=None):
+ group = get_group_or_404(acronym, group_type)
+ if not group.features.has_materials:
+ raise Http404
+
+ materials = get_group_materials(group).order_by("-time")
+
+ return render(request, 'group/materials.html',
+ construct_group_menu_context(request, group, "materials", group_type, {
+ "materials": materials,
+ }))
+
def nodename(name):
return name.replace('-','_')
@@ -556,6 +571,8 @@ def make_dot(group):
def dependencies_dot(request, acronym, group_type=None):
group = get_group_or_404(acronym, group_type)
+ if not group.features.has_documents:
+ raise Http404
return HttpResponse(make_dot(group),
content_type='text/plain; charset=UTF-8'
@@ -564,6 +581,8 @@ def dependencies_dot(request, acronym, group_type=None):
@cache_page ( 60 * 60 )
def dependencies_pdf(request, acronym, group_type=None):
group = get_group_or_404(acronym, group_type)
+ if not group.features.has_documents:
+ raise Http404
dothandle,dotname = mkstemp()
os.close(dothandle)
diff --git a/ietf/group/milestones.py b/ietf/group/milestones.py
index 6b4a0083e..44ac4423d 100644
--- a/ietf/group/milestones.py
+++ b/ietf/group/milestones.py
@@ -12,7 +12,8 @@ from django.contrib.auth.decorators import login_required
from ietf.doc.models import Document, DocEvent
from ietf.doc.utils import get_chartering_type
from ietf.group.models import Group, GroupMilestone, MilestoneGroupEvent
-from ietf.group.utils import save_milestone_in_history, can_manage_group_type, milestone_reviewer_for_group_type
+from ietf.group.utils import (save_milestone_in_history, can_manage_group_type, milestone_reviewer_for_group_type,
+ get_group_or_404)
from ietf.name.models import GroupMilestoneStateName
from ietf.group.mails import email_milestones_changed
@@ -108,17 +109,19 @@ class MilestoneForm(forms.Form):
return r
@login_required
-def edit_milestones(request, group_type, acronym, milestone_set="current"):
+def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
# milestones_set + needs_review: we have several paths into this view
# management (IRTF chair/AD/...)/Secr. -> all actions on current + add new
# group chair -> limited actions on current + add new for review
# (re)charter -> all actions on existing in state charter + add new in state charter
#
# For charters we store the history on the charter document to not confuse people.
- group = get_object_or_404(Group, type=group_type, acronym=acronym)
+ group = get_group_or_404(acronym, group_type)
+ if not group.features.has_milestones:
+ raise Http404
needs_review = False
- if not can_manage_group_type(request.user, group_type):
+ if not can_manage_group_type(request.user, group.type_id):
if group.role_set.filter(name="chair", person__user=request.user):
if milestone_set == "current":
needs_review = True
@@ -332,8 +335,10 @@ def edit_milestones(request, group_type, acronym, milestone_set="current"):
@login_required
def reset_charter_milestones(request, group_type, acronym):
"""Reset charter milestones to the currently in-use milestones."""
- group = get_object_or_404(Group, type=group_type, acronym=acronym)
-
+ group = get_group_or_404(acronym, group_type)
+ if not group.features.has_milestones:
+ raise Http404
+
if (not can_manage_group_type(request.user, group_type) and
not group.role_set.filter(name="chair", person__user=request.user)):
return HttpResponseForbidden("You are not chair of this group.")
diff --git a/ietf/group/models.py b/ietf/group/models.py
index e66572b37..64009012e 100644
--- a/ietf/group/models.py
+++ b/ietf/group/models.py
@@ -80,9 +80,12 @@ class Group(GroupInfo):
def bg_color(self):
return bg_group_colors[self.upcase_acronym]
+ @property
def features(self):
- from ietf.group.features import GroupFeatures
- return GroupFeatures(self)
+ if not hasattr(self, "features_cache"):
+ from ietf.group.features import GroupFeatures
+ self.features_cache = GroupFeatures(self)
+ return self.features_cache
def json_url(self):
return "/group/%s.json" % (self.acronym,)
diff --git a/ietf/group/urls.py b/ietf/group/urls.py
index f3cf567b3..f68272aad 100644
--- a/ietf/group/urls.py
+++ b/ietf/group/urls.py
@@ -7,10 +7,13 @@ urlpatterns = patterns('',
(r'^chartering/$', 'ietf.group.info.chartering_groups'),
(r'^chartering/create/(?P(wg|rg))/$', 'ietf.group.edit.edit', {'action': "charter"}, "group_create"),
(r'^concluded/$', 'ietf.group.info.concluded_groups'),
+ # FIXME: the remainder here is currently duplicated in urls_info.py, need to unify these at some point
(r'^(?P[a-zA-Z0-9-._]+)/$', 'ietf.group.info.group_home', None, "group_home"),
(r'^(?P[a-zA-Z0-9-._]+)/documents/$', 'ietf.group.info.group_documents', None, "group_docs"),
(r'^(?P[a-zA-Z0-9-._]+)/charter/$', 'ietf.group.info.group_charter', None, 'group_charter'),
(r'^(?P[a-zA-Z0-9-._]+)/history/$', 'ietf.group.info.history'),
+ (r'^(?P[a-zA-Z0-9-._]+)/materials/$', 'ietf.group.info.materials', None, "group_materials"),
+ (r'^(?P[a-zA-Z0-9-._]+)/materials/upload/$', 'ietf.group.edit.upload_materials', None, "group_upload_materials"),
(r'^(?P[a-zA-Z0-9-._]+)/deps/dot/$', 'ietf.group.info.dependencies_dot'),
(r'^(?P[a-zA-Z0-9-._]+)/deps/pdf/$', 'ietf.group.info.dependencies_pdf'),
(r'^(?P[a-zA-Z0-9-._]+)/init-charter/', 'ietf.group.edit.submit_initial_charter'),
diff --git a/ietf/group/urls_info.py b/ietf/group/urls_info.py
index 0039047b5..0d838a1b3 100644
--- a/ietf/group/urls_info.py
+++ b/ietf/group/urls_info.py
@@ -19,7 +19,8 @@ urlpatterns = patterns('',
(r'^bofs/$', info.bofs),
(r'^bofs/create/$', edit.edit, {'action': "create"}, "bof_create"),
(r'^(?P[a-zA-Z0-9-._]+)/documents/txt/$', info.group_documents_txt),
- (r'^(?P[a-zA-Z0-9-._]+)/$', info.group_documents, None, "group_docs"),
+ (r'^(?P[a-zA-Z0-9-._]+)/$', info.group_home, None, "group_home"),
+ (r'^(?P[a-zA-Z0-9-._]+)/documents/$', info.group_documents, None, "group_docs"),
(r'^(?P[a-zA-Z0-9-._]+)/charter/$', info.group_charter, None, 'group_charter'),
(r'^(?P[a-zA-Z0-9-._]+)/history/$', info.history),
(r'^(?P[a-zA-Z0-9-._]+)/deps/dot/$', info.dependencies_dot),
diff --git a/ietf/group/utils.py b/ietf/group/utils.py
index 89e8cd120..849802411 100644
--- a/ietf/group/utils.py
+++ b/ietf/group/utils.py
@@ -1,6 +1,7 @@
import os
from django.conf import settings
+from django.shortcuts import get_object_or_404
from ietf.group.models import Group, RoleHistory
from ietf.person.models import Email
@@ -107,3 +108,14 @@ def milestone_reviewer_for_group_type(group_type):
else:
return "Area Director"
+def can_manage_materials(user, group):
+ return group.has_role(user, ("chair", "delegate", "secr")) or has_role(user, 'Secretariat')
+
+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)
+
diff --git a/ietf/templates/group/materials.html b/ietf/templates/group/materials.html
new file mode 100644
index 000000000..d56b623e3
--- /dev/null
+++ b/ietf/templates/group/materials.html
@@ -0,0 +1,27 @@
+{% extends "group/group_base.html" %}
+{% load ietf_filters %}
+
+{% block group_subtitle %}Materials{% endblock %}
+
+{% block morecss %}
+ {{ block.super }}
+ .material { margin-top: 0.5em; }
+{% endblock %}
+
+{% block group_content %}
+{% load ietf_filters %}
+
+ Materials
+
+ {% if materials %}
+ {% for d in materials %}
+
+ {% endfor %}
+ {% else %}
+ No materials uploaded.
+ {% endif %}
+
+{% endblock %}
From 0bd82434c8b671ae5137b31c35e66755153e3d6f Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Thu, 22 May 2014 15:55:15 +0000
Subject: [PATCH 04/26] Fix problem with milestones editing claiming that all
resolved milestones have been changed when you click the review changes
button (by removing work-around for bug in previous version of Django) -
Legacy-Id: 7769
---
ietf/group/milestones.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ietf/group/milestones.py b/ietf/group/milestones.py
index 44ac4423d..7747b9154 100644
--- a/ietf/group/milestones.py
+++ b/ietf/group/milestones.py
@@ -54,7 +54,7 @@ class MilestoneForm(forms.Form):
desc=m.desc,
due_month=m.due.month,
due_year=m.due.year,
- resolved_checkbox="on" if m.resolved else False,
+ resolved_checkbox=bool(m.resolved),
resolved=m.resolved,
docs=",".join(m.docs.values_list("pk", flat=True)),
delete=False,
From 17cf1095c79f6484ad15e6af0950f69ef83504f8 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Thu, 22 May 2014 16:10:37 +0000
Subject: [PATCH 05/26] Fix cosmetic problem with label_suffix and the delete
labels on the milestones page - Legacy-Id: 7770
---
ietf/group/milestones.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/ietf/group/milestones.py b/ietf/group/milestones.py
index 7747b9154..fdfc0eaa5 100644
--- a/ietf/group/milestones.py
+++ b/ietf/group/milestones.py
@@ -26,7 +26,7 @@ def parse_doc_names(s):
class MilestoneForm(forms.Form):
id = forms.IntegerField(required=True, widget=forms.HiddenInput)
- desc = forms.CharField(max_length=500, label="Milestone", required=True)
+ desc = forms.CharField(max_length=500, label="Milestone:", required=True)
due_month = forms.TypedChoiceField(choices=(), required=True, coerce=int)
due_year = forms.TypedChoiceField(choices=(), required=True, coerce=int)
resolved_checkbox = forms.BooleanField(required=False, label="Resolved")
@@ -40,6 +40,8 @@ class MilestoneForm(forms.Form):
required=False, initial="noaction", widget=forms.RadioSelect)
def __init__(self, *args, **kwargs):
+ kwargs["label_suffix"] = ""
+
m = self.milestone = kwargs.pop("instance", None)
self.needs_review = kwargs.pop("needs_review", False)
From f9407c57b80a3ec0bb35ce8c13109eceeab4ffd9 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Fri, 23 May 2014 16:24:40 +0000
Subject: [PATCH 06/26] It looks like Django 1.6 finally added a span around
form field help texts so take advantage of that and apply our help text style
to it - Legacy-Id: 7775
---
static/css/base2.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/static/css/base2.css b/static/css/base2.css
index 80e850c1a..8d5820e56 100644
--- a/static/css/base2.css
+++ b/static/css/base2.css
@@ -177,7 +177,7 @@ form table th {
vertical-align: top;
}
-form table .help {
+form table .help, form table .helptext {
font-style: italic;
font-size: 11px;
}
From 3bfb6dcd4320a403925c395f61297fa368430bf0 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Fri, 23 May 2014 16:26:56 +0000
Subject: [PATCH 07/26] Added support data for a tentative new material
document type, add a upload/edit/revise page for uploading and revising group
materials, still missing a couple of details and tests - Legacy-Id: 7776
---
.../migrations/0019_create_material_states.py | 373 ++++++++++++++++++
ietf/doc/models.py | 2 +
ietf/group/edit.py | 92 ++++-
ietf/group/info.py | 15 +-
ietf/group/urls.py | 4 +-
.../0021_create_material_doc_type.py | 190 +++++++++
ietf/settings.py | 2 +
ietf/templates/group/materials.html | 33 +-
8 files changed, 693 insertions(+), 18 deletions(-)
create mode 100644 ietf/doc/migrations/0019_create_material_states.py
create mode 100644 ietf/name/migrations/0021_create_material_doc_type.py
diff --git a/ietf/doc/migrations/0019_create_material_states.py b/ietf/doc/migrations/0019_create_material_states.py
new file mode 100644
index 000000000..29bc729bf
--- /dev/null
+++ b/ietf/doc/migrations/0019_create_material_states.py
@@ -0,0 +1,373 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+class Migration(DataMigration):
+ depends_on = (
+ ("name", "0021_create_material_doc_type"),
+ )
+
+ def forwards(self, orm):
+ state_type, _ = orm.StateType.objects.get_or_create(slug="material", label="State")
+ orm.State.objects.get_or_create(slug="active", name="Active", order=1, type=state_type)
+ orm.State.objects.get_or_create(slug="inactive", name="Inactive", order=2, type=state_type)
+
+ def backwards(self, orm):
+ orm.State.objects.filter(slug="inactive", type="material").delete()
+ orm.State.objects.filter(slug="active", type="material").delete()
+ orm.StateType.objects.filter(slug="material").delete()
+
+ 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.ballotdocevent': {
+ 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'BallotDocEvent', '_ormbases': [u'doc.DocEvent']},
+ 'ballot_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.BallotType']"}),
+ u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'})
+ },
+ u'doc.ballotpositiondocevent': {
+ 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'BallotPositionDocEvent', '_ormbases': [u'doc.DocEvent']},
+ 'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"}),
+ 'ballot': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['doc.BallotDocEvent']", 'null': 'True'}),
+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'comment_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'discuss': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'discuss_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
+ 'pos': ('django.db.models.fields.related.ForeignKey', [], {'default': "'norecord'", 'to': u"orm['name.BallotPositionName']"})
+ },
+ u'doc.ballottype': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'BallotType'},
+ 'doc_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'positions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['name.BallotPositionName']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'question': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
+ 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ u'doc.consensusdocevent': {
+ 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'ConsensusDocEvent', '_ormbases': [u'doc.DocEvent']},
+ 'consensus': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'})
+ },
+ u'doc.deletedevent': {
+ 'Meta': {'object_name': 'DeletedEvent'},
+ 'by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'json': ('django.db.models.fields.TextField', [], {}),
+ 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'})
+ },
+ u'doc.docalias': {
+ 'Meta': {'object_name': 'DocAlias'},
+ 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
+ },
+ u'doc.docevent': {
+ 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'DocEvent'},
+ 'by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"}),
+ 'desc': ('django.db.models.fields.TextField', [], {}),
+ 'doc': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+ 'type': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ u'doc.dochistory': {
+ 'Meta': {'object_name': 'DocHistory'},
+ 'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_dochistory_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.DocHistoryAuthor']", 'blank': 'True'}),
+ 'doc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': u"orm['doc.Document']"}),
+ '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'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}),
+ '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'}),
+ 'related': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.DocAlias']", 'symmetrical': 'False', 'through': u"orm['doc.RelatedDocHistory']", '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_dochistory_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.dochistoryauthor': {
+ 'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocHistoryAuthor'},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']"}),
+ 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.DocHistory']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'order': ('django.db.models.fields.IntegerField', [], {})
+ },
+ u'doc.docreminder': {
+ 'Meta': {'object_name': 'DocReminder'},
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'due': ('django.db.models.fields.DateTimeField', [], {}),
+ 'event': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.DocEvent']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocReminderTypeName']"})
+ },
+ 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.initialreviewdocevent': {
+ 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'InitialReviewDocEvent', '_ormbases': [u'doc.DocEvent']},
+ u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
+ 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+ },
+ u'doc.lastcalldocevent': {
+ 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'LastCallDocEvent', '_ormbases': [u'doc.DocEvent']},
+ u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
+ 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+ },
+ u'doc.newrevisiondocevent': {
+ 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'NewRevisionDocEvent', '_ormbases': [u'doc.DocEvent']},
+ u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
+ 'rev': ('django.db.models.fields.CharField', [], {'max_length': '16'})
+ },
+ u'doc.relateddochistory': {
+ 'Meta': {'object_name': 'RelatedDocHistory'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocRelationshipName']"}),
+ 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.DocHistory']"}),
+ 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reversely_related_document_history_set'", 'to': u"orm['doc.DocAlias']"})
+ },
+ u'doc.relateddocument': {
+ 'Meta': {'object_name': 'RelatedDocument'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocRelationshipName']"}),
+ 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
+ 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.DocAlias']"})
+ },
+ 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.statedocevent': {
+ 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'StateDocEvent', '_ormbases': [u'doc.DocEvent']},
+ u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
+ 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.State']", 'null': 'True', 'blank': 'True'}),
+ 'state_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.StateType']"})
+ },
+ 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'doc.telechatdocevent': {
+ 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'TelechatDocEvent', '_ormbases': [u'doc.DocEvent']},
+ u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
+ 'returning_item': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'telechat_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
+ },
+ u'doc.writeupdocevent': {
+ 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'WriteupDocEvent', '_ormbases': [u'doc.DocEvent']},
+ u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
+ 'text': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+ },
+ 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'name.ballotpositionname': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'},
+ 'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ '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.docrelationshipname': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'},
+ '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'}),
+ 'revname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
+ 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ u'name.docremindertypename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'},
+ '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.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.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.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 = ['doc']
+ symmetrical = True
diff --git a/ietf/doc/models.py b/ietf/doc/models.py
index 5b4d5d65d..fb2e1f287 100644
--- a/ietf/doc/models.py
+++ b/ietf/doc/models.py
@@ -85,6 +85,8 @@ class DocumentInfo(models.Model):
return settings.CONFLICT_REVIEW_PATH
elif self.type_id == "statchg":
return settings.STATUS_CHANGE_PATH
+ elif self.type_id == "material":
+ return settings.MATERIALS_PATH
else:
raise NotImplemented
diff --git a/ietf/group/edit.py b/ietf/group/edit.py
index 9d5477698..e58793e72 100644
--- a/ietf/group/edit.py
+++ b/ietf/group/edit.py
@@ -9,12 +9,13 @@ from django import forms
from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponseForbidden, Http404
from django.utils.html import mark_safe
+from django.utils.text import slugify
from django.http import Http404, HttpResponse
from django.contrib.auth.decorators import login_required
import debug # pyflakes:ignore
-from ietf.doc.models import DocAlias, DocTagName, Document, State, save_document_in_history
+from ietf.doc.models import Document, DocAlias, DocTagName, DocTypeName, DocEvent, State, save_document_in_history
from ietf.doc.utils import get_tags_for_stream_id
from ietf.group.models import ( Group, Role, GroupEvent, GroupHistory, GroupStateName,
GroupStateTransitions, GroupTypeName, GroupURL, ChangeStateGroupEvent )
@@ -456,8 +457,30 @@ def customize_workflow(request, group_type, acronym):
'tags': tags,
})
+class UploadMaterialForm(forms.Form):
+ title = forms.CharField(max_length=Document._meta.get_field("title").max_length)
+ state = forms.ModelChoiceField(State.objects.filter(type="material"), empty_label=None)
+ material = forms.FileField(label='File', help_text="PDF or text file (ASCII/UTF-8)")
+
+ def __init__(self, *args, **kwargs):
+ action = kwargs.pop("action")
+ doc = kwargs.pop("doc", None)
+
+ super(UploadMaterialForm, self).__init__(*args, **kwargs)
+
+ if action == "new":
+ self.fields["state"].widget = forms.HiddenInput()
+ self.fields["state"].queryset = self.fields["state"].queryset.filter(slug="active")
+ else:
+ self.fields["title"].initial = doc.title
+ self.fields["state"].initial = doc.get_state().pk if doc.get_state() else None
+
+ if action == "edit":
+ del self.fields["material"]
+
+
@login_required
-def upload_materials(request, acronym, group_type=None):
+def edit_material(request, acronym, action="new", name=None, group_type=None):
group = get_group_or_404(acronym, group_type)
if not group.features.has_materials:
raise Http404
@@ -465,9 +488,64 @@ def upload_materials(request, acronym, group_type=None):
if not can_manage_materials(request.user, group):
return HttpResponseForbidden("You don't have permission to access this view")
- # FIXME: fill in
+ existing = None
+ if name and action != "new":
+ existing = get_object_or_404(Document, type="material", name=name)
- return render(request, 'group/materials.html',
- construct_group_menu_context(request, group, "materials", group_type, {
- "materials": materials,
- }))
+ if request.method == 'POST':
+ form = UploadMaterialForm(request.POST, request.FILES, action=action, doc=existing)
+
+ if form.is_valid():
+ if action == "new":
+ d = Document()
+ d.type = DocTypeName.objects.get(slug="material")
+ d.group = group
+ d.rev = "01"
+ else:
+ d = existing
+
+ d.title = form.cleaned_data["title"]
+ d.time = datetime.datetime.now()
+
+ if not d.name:
+ d.name = "material-%s-%s" % (d.group.acronym, slugify(d.title))
+ i = 2
+ while True:
+ if not Document.objects.filter(name=d.name).exists():
+ break
+ d.name = "material-%s-%s-%s" % (d.group.acronym, slugify(d.title), i)
+ i += 1
+
+ if "material" in form.fields:
+ if action != "new":
+ d.rev = "%02d" % (int(d.rev) + 1)
+
+ f = form.cleaned_data["material"]
+ file_ext = os.path.splitext(f.name)[1]
+
+ with open(os.path.join(d.get_file_path(), d.name + "-" + d.rev + file_ext), 'wb+') as dest:
+ for chunk in f.chunks():
+ dest.write(chunk)
+
+ d.save()
+
+ # FIXME: missing edit title event
+
+ # FIXME: missing changed state event
+ d.set_state(form.cleaned_data["state"])
+
+ # FIXME: wrong, should be new revision event
+ DocEvent.objects.create(doc=d,
+ by=request.user.person,
+ type='uploaded',
+ desc="Uploaded material")
+
+ return redirect("group_materials", acronym=group.acronym)
+ else:
+ form = UploadMaterialForm(action=action, doc=existing)
+
+ return render(request, 'group/edit_material.html', {
+ 'group': group,
+ 'form': form,
+ 'action': action,
+ })
diff --git a/ietf/group/info.py b/ietf/group/info.py
index 90a9c633d..44fe2b3cd 100644
--- a/ietf/group/info.py
+++ b/ietf/group/info.py
@@ -35,6 +35,7 @@
import os
import itertools
from tempfile import mkstemp
+import glob
from django.shortcuts import get_object_or_404, render
from django.template.loader import render_to_string
@@ -258,7 +259,7 @@ def concluded_groups(request):
dict(group_types=group_types))
def get_group_materials(group):
- return Document.objects.filter(group=group).exclude(states__slug="deleted")#.exclude(session=None).exclude(type="charter")
+ return Document.objects.filter(group=group, type="material", session=None).exclude(states__slug="inactive")
def construct_group_menu_context(request, group, selected, group_type, others):
"""Return context with info for the group menu filled in."""
@@ -294,9 +295,9 @@ def construct_group_menu_context(request, group, selected, group_type, others):
actions.append((u"Add or edit milestones", urlreverse("group_edit_milestones", kwargs=kwargs)))
if group.features.has_materials and can_manage_materials(request.user, group):
- actions.append((u"Upload materials", urlreverse("group_upload_materials", kwargs=kwargs)))
+ actions.append((u"Upload material", urlreverse("group_new_material", kwargs=kwargs)))
- if group.state_id != "conclude" and can_manage:
+ if group.type_id in ("rg", "wg") and group.state_id != "conclude" and can_manage:
actions.append((u"Edit group", urlreverse("group_edit", kwargs=kwargs)))
if group.features.customize_workflow and (is_chair or can_manage):
@@ -446,10 +447,18 @@ def materials(request, acronym, group_type=None):
raise Http404
materials = get_group_materials(group).order_by("-time")
+ for d in materials:
+ extension = ""
+ globs = glob.glob(d.get_file_path() + d.name + "-" + d.rev + ".*")
+ if globs:
+ extension = os.path.splitext(globs[0])[1]
+
+ d.full_url = d.href() + extension
return render(request, 'group/materials.html',
construct_group_menu_context(request, group, "materials", group_type, {
"materials": materials,
+ "can_manage_materials": can_manage_materials(request.user, group)
}))
def nodename(name):
diff --git a/ietf/group/urls.py b/ietf/group/urls.py
index f68272aad..800dc4d3c 100644
--- a/ietf/group/urls.py
+++ b/ietf/group/urls.py
@@ -13,7 +13,9 @@ urlpatterns = patterns('',
(r'^(?P[a-zA-Z0-9-._]+)/charter/$', 'ietf.group.info.group_charter', None, 'group_charter'),
(r'^(?P[a-zA-Z0-9-._]+)/history/$', 'ietf.group.info.history'),
(r'^(?P[a-zA-Z0-9-._]+)/materials/$', 'ietf.group.info.materials', None, "group_materials"),
- (r'^(?P[a-zA-Z0-9-._]+)/materials/upload/$', 'ietf.group.edit.upload_materials', None, "group_upload_materials"),
+ (r'^(?P[a-zA-Z0-9-._]+)/materials/new/$', 'ietf.group.edit.edit_material', { 'action': "new" }, "group_new_material"),
+ (r'^(?P[a-zA-Z0-9-._]+)/materials/(?Pmaterial-[^/]+)/edit/$', 'ietf.group.edit.edit_material', { 'action': "edit" }, "group_edit_material"),
+ (r'^(?P[a-zA-Z0-9-._]+)/materials/(?Pmaterial-[^/]+)/revise/$', 'ietf.group.edit.edit_material', { 'action': "revise" }, "group_revise_material"),
(r'^(?P[a-zA-Z0-9-._]+)/deps/dot/$', 'ietf.group.info.dependencies_dot'),
(r'^(?P[a-zA-Z0-9-._]+)/deps/pdf/$', 'ietf.group.info.dependencies_pdf'),
(r'^(?P[a-zA-Z0-9-._]+)/init-charter/', 'ietf.group.edit.submit_initial_charter'),
diff --git a/ietf/name/migrations/0021_create_material_doc_type.py b/ietf/name/migrations/0021_create_material_doc_type.py
new file mode 100644
index 000000000..06db0ea5b
--- /dev/null
+++ b/ietf/name/migrations/0021_create_material_doc_type.py
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+class Migration(DataMigration):
+ def forwards(self, orm):
+ orm.DocTypeName.objects.get_or_create(slug="material", name="Material")
+
+ def backwards(self, orm):
+ orm.DocTypeName.objects.filter(slug="material").delete()
+
+ models = {
+ u'name.ballotpositionname': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'},
+ 'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ '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.constraintname': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'ConstraintName'},
+ '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'}),
+ 'penalty': ('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.dbtemplatetypename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'DBTemplateTypeName'},
+ '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.docrelationshipname': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'},
+ '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'}),
+ 'revname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
+ 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ u'name.docremindertypename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'},
+ '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.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.draftsubmissionstatename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'DraftSubmissionStateName'},
+ 'desc': ('django.db.models.fields.TextField', [], {'blank': '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['name.DraftSubmissionStateName']"}),
+ '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.feedbacktype': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'FeedbackType'},
+ '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.liaisonstatementpurposename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'LiaisonStatementPurposeName'},
+ '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.meetingtypename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'MeetingTypeName'},
+ '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.nomineepositionstate': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'NomineePositionState'},
+ '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.sessionstatusname': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'SessionStatusName'},
+ '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'name.timeslottypename': {
+ 'Meta': {'ordering': "['order']", 'object_name': 'TimeSlotTypeName'},
+ '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'})
+ }
+ }
+
+ complete_apps = ['name']
+ symmetrical = True
diff --git a/ietf/settings.py b/ietf/settings.py
index 837f0f34e..00c8d6a7b 100644
--- a/ietf/settings.py
+++ b/ietf/settings.py
@@ -265,6 +265,7 @@ AGENDA_PATH_PATTERN = '/a/www/www6s/proceedings/%(meeting)s/agenda/%(wg)s.%(ext)
MINUTES_PATH_PATTERN = '/a/www/www6s/proceedings/%(meeting)s/minutes/%(wg)s.%(ext)s'
SLIDES_PATH_PATTERN = '/a/www/www6s/proceedings/%(meeting)s/slides/%(wg)s-*'
IPR_DOCUMENT_PATH = '/a/www/ietf-ftp/ietf/IPR/'
+MATERIALS_PATH = '/a/www/ietf-ftp/materials/'
IESG_TASK_FILE = '/a/www/www6/iesg/internal/task.txt'
IESG_ROLL_CALL_FILE = '/a/www/www6/iesg/internal/rollcall.txt'
IESG_MINUTES_FILE = '/a/www/www6/iesg/internal/minutes.txt'
@@ -285,6 +286,7 @@ DOC_HREFS = {
#"liaison": None
"minutes": "http://www.ietf.org/proceedings/{meeting}/minutes/{doc.external_url}",
"slides": "http://www.ietf.org/proceedings/{meeting}/slides/{doc.external_url}",
+ "material": "http://www.ietf.org/material/{doc.name}-{doc.rev}",
}
# Override this in settings_local.py if needed
diff --git a/ietf/templates/group/materials.html b/ietf/templates/group/materials.html
index d56b623e3..d80251781 100644
--- a/ietf/templates/group/materials.html
+++ b/ietf/templates/group/materials.html
@@ -5,7 +5,7 @@
{% block morecss %}
{{ block.super }}
- .material { margin-top: 0.5em; }
+ .materials .edit-options { float: right; margin-left: 2em; font-style: italic; }
{% endblock %}
{% block group_content %}
@@ -14,12 +14,31 @@
Materials
{% if materials %}
- {% for d in materials %}
-
- {% endfor %}
+
+
+
+ Title
+ Rev.
+ Date
+
+
+ {% for d in materials %}
+
+
+ {% if can_manage_materials %}
+
+ edit |
+ revise
+
+ {% endif %}
+
+ {{ d.title }}
+
+ {{ d.rev }}
+ {{ d.time|date:"Y-m-d" }}
+
+ {% endfor %}
+
{% else %}
No materials uploaded.
{% endif %}
From 3c88dc127b3709863b32fd640350a18e3219da23 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Fri, 23 May 2014 16:27:24 +0000
Subject: [PATCH 08/26] Add the template for the material upload page -
Legacy-Id: 7777
---
ietf/templates/group/edit_material.html | 52 +++++++++++++++++++++++++
1 file changed, 52 insertions(+)
create mode 100644 ietf/templates/group/edit_material.html
diff --git a/ietf/templates/group/edit_material.html b/ietf/templates/group/edit_material.html
new file mode 100644
index 000000000..29e7e768c
--- /dev/null
+++ b/ietf/templates/group/edit_material.html
@@ -0,0 +1,52 @@
+{% extends "base.html" %}
+
+{% block title %}Upload Material for Group {{ group.acronym }}{% endblock %}
+
+{% block morecss %}
+{{ block.super }}
+form.upload-material td { padding-bottom: 0.6em; }
+form.upload-material #id_title { width: 30em; }
+form.upload-material .submit-row td { padding-top: 1em; text-align: right; }
+{% endblock %}
+
+{% block content %}
+{% load ietf_filters %}
+
+
+
+
Upload Material for Group {{ group.acronym }}
+
+{% if action == "new" %}
+
+ Below you can upload a file for the group
+ {{ group.acronym }} .
+ The file will appear under the materials tab in the group pages.
+
+{% elif action == "edit" %}
+
+ Below you can edit the details of the material for the group
+ {{ group.acronym }} .
+
+{% elif action == "revise" %}
+
+ Below you can upload a new revision of the material for the group
+ {{ group.acronym }} .
+
+{% endif %}
+
+
Upload
+
+
+
+{% endblock content %}
From 47c9ccf0e116325753ce448be9213fd2d9d1ab8b Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Mon, 26 May 2014 15:40:07 +0000
Subject: [PATCH 09/26] Handle the case of asking for get_state() on an unsaved
document - Legacy-Id: 7779
---
ietf/doc/models.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/ietf/doc/models.py b/ietf/doc/models.py
index fb2e1f287..443ea3bf0 100644
--- a/ietf/doc/models.py
+++ b/ietf/doc/models.py
@@ -122,6 +122,9 @@ class DocumentInfo(models.Model):
"""Get state of type, or default state for document type if
not specified. Uses a local cache to speed multiple state
reads up."""
+ if self.pk == None: # states is many-to-many so not in database implies no state
+ return None
+
if state_type == None:
state_type = self.type_id
From 252db0dffbd153ef2892a963d5c23e893ee2f9e7 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Mon, 26 May 2014 15:40:51 +0000
Subject: [PATCH 10/26] Fix some bugs, add correct document events in materials
upload - Legacy-Id: 7780
---
ietf/group/edit.py | 33 +++++++++++++++++++++++----------
1 file changed, 23 insertions(+), 10 deletions(-)
diff --git a/ietf/group/edit.py b/ietf/group/edit.py
index e58793e72..33b9f3a3e 100644
--- a/ietf/group/edit.py
+++ b/ietf/group/edit.py
@@ -15,8 +15,9 @@ from django.contrib.auth.decorators import login_required
import debug # pyflakes:ignore
-from ietf.doc.models import Document, DocAlias, DocTagName, DocTypeName, DocEvent, State, save_document_in_history
-from ietf.doc.utils import get_tags_for_stream_id
+from ietf.doc.models import Document, DocAlias, DocTagName, DocTypeName, DocEvent, State
+from ietf.doc.models import NewRevisionDocEvent, save_document_in_history
+from ietf.doc.utils import get_tags_for_stream_id, add_state_change_event
from ietf.group.models import ( Group, Role, GroupEvent, GroupHistory, GroupStateName,
GroupStateTransitions, GroupTypeName, GroupURL, ChangeStateGroupEvent )
from ietf.group.utils import (save_group_in_history, can_manage_group_type, can_manage_materials,
@@ -471,6 +472,7 @@ class UploadMaterialForm(forms.Form):
if action == "new":
self.fields["state"].widget = forms.HiddenInput()
self.fields["state"].queryset = self.fields["state"].queryset.filter(slug="active")
+ self.fields["state"].initial = self.fields["state"].queryset[0].pk
else:
self.fields["title"].initial = doc.title
self.fields["state"].initial = doc.get_state().pk if doc.get_state() else None
@@ -504,6 +506,10 @@ def edit_material(request, acronym, action="new", name=None, group_type=None):
else:
d = existing
+ prev_rev = d.rev
+ prev_title = d.title
+ prev_state = d.get_state()
+
d.title = form.cleaned_data["title"]
d.time = datetime.datetime.now()
@@ -529,16 +535,23 @@ def edit_material(request, acronym, action="new", name=None, group_type=None):
d.save()
- # FIXME: missing edit title event
+ if not existing or prev_rev != d.rev:
+ e = NewRevisionDocEvent(type="new_revision", doc=d, rev=d.rev)
+ e.time = d.time
+ e.by = request.user.person
+ e.desc = "New version available: %s-%s " % (d.name, d.rev)
+ e.save()
+
+ if prev_title != d.title:
+ e = DocEvent(doc=d, by=request.user.person, type='changed_document')
+ e.desc = u"Changed title to %s " % d.title
+ if prev_title:
+ e.desc += u" from %s" % prev_title
+ e.time = d.time
+ e.save()
- # FIXME: missing changed state event
d.set_state(form.cleaned_data["state"])
-
- # FIXME: wrong, should be new revision event
- DocEvent.objects.create(doc=d,
- by=request.user.person,
- type='uploaded',
- desc="Uploaded material")
+ add_state_change_event(d, request.user.person, prev_state, form.cleaned_data["state"])
return redirect("group_materials", acronym=group.acronym)
else:
From 97e6cebe911d9202734eca60ae8a3791e1ef9617 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Wed, 28 May 2014 15:58:03 +0000
Subject: [PATCH 11/26] Don't show charter for group types without chartering
processes - Legacy-Id: 7795
---
ietf/secr/templates/groups/view.html | 3 +++
1 file changed, 3 insertions(+)
diff --git a/ietf/secr/templates/groups/view.html b/ietf/secr/templates/groups/view.html
index fd2acb26b..7c581400c 100644
--- a/ietf/secr/templates/groups/view.html
+++ b/ietf/secr/templates/groups/view.html
@@ -45,8 +45,11 @@
Email Address: {{ group.list_email }}
Email Subscription: {{ group.list_subscribe }}
Email Archive: {{ group.list_archive }}
+ {% if group.features.has_chartering_process %}
Charter: View Charter
+ {% else %}
Description: {{ group.description }}
+ {% endif %}
Comments: {{ group.comments }}
Last Modified Date: {{ group.time }}
From 544cd702bba57b64473a3351eaa014ff1385151d Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Fri, 30 May 2014 14:07:16 +0000
Subject: [PATCH 12/26] Drop the material doc type, instead present a choice of
doc types for uploading materials (currently only slides) and use that,
introduce a DOCUMENT_PATH_PATTERN setting for storing the files and only look
in proceedings/ for documents that are actually meeting related - Legacy-Id:
7802
---
.../migrations/0019_create_material_states.py | 373 ------------------
ietf/doc/models.py | 24 +-
ietf/group/edit.py | 41 +-
ietf/group/features.py | 1 +
ietf/group/info.py | 18 +-
ietf/group/urls.py | 12 +-
ietf/group/utils.py | 1 -
.../0021_create_material_doc_type.py | 190 ---------
ietf/settings.py | 9 +-
.../templates/group/choose_material_type.html | 23 ++
ietf/templates/group/edit_material.html | 2 -
ietf/templates/group/materials.html | 56 +--
12 files changed, 124 insertions(+), 626 deletions(-)
delete mode 100644 ietf/doc/migrations/0019_create_material_states.py
delete mode 100644 ietf/name/migrations/0021_create_material_doc_type.py
create mode 100644 ietf/templates/group/choose_material_type.html
diff --git a/ietf/doc/migrations/0019_create_material_states.py b/ietf/doc/migrations/0019_create_material_states.py
deleted file mode 100644
index 29bc729bf..000000000
--- a/ietf/doc/migrations/0019_create_material_states.py
+++ /dev/null
@@ -1,373 +0,0 @@
-# -*- coding: utf-8 -*-
-from south.utils import datetime_utils as datetime
-from south.db import db
-from south.v2 import DataMigration
-from django.db import models
-
-class Migration(DataMigration):
- depends_on = (
- ("name", "0021_create_material_doc_type"),
- )
-
- def forwards(self, orm):
- state_type, _ = orm.StateType.objects.get_or_create(slug="material", label="State")
- orm.State.objects.get_or_create(slug="active", name="Active", order=1, type=state_type)
- orm.State.objects.get_or_create(slug="inactive", name="Inactive", order=2, type=state_type)
-
- def backwards(self, orm):
- orm.State.objects.filter(slug="inactive", type="material").delete()
- orm.State.objects.filter(slug="active", type="material").delete()
- orm.StateType.objects.filter(slug="material").delete()
-
- 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.ballotdocevent': {
- 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'BallotDocEvent', '_ormbases': [u'doc.DocEvent']},
- 'ballot_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.BallotType']"}),
- u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'})
- },
- u'doc.ballotpositiondocevent': {
- 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'BallotPositionDocEvent', '_ormbases': [u'doc.DocEvent']},
- 'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"}),
- 'ballot': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['doc.BallotDocEvent']", 'null': 'True'}),
- 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
- 'comment_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
- 'discuss': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
- 'discuss_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
- u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
- 'pos': ('django.db.models.fields.related.ForeignKey', [], {'default': "'norecord'", 'to': u"orm['name.BallotPositionName']"})
- },
- u'doc.ballottype': {
- 'Meta': {'ordering': "['order']", 'object_name': 'BallotType'},
- 'doc_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'positions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['name.BallotPositionName']", 'symmetrical': 'False', 'blank': 'True'}),
- 'question': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
- 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
- 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
- },
- u'doc.consensusdocevent': {
- 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'ConsensusDocEvent', '_ormbases': [u'doc.DocEvent']},
- 'consensus': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'})
- },
- u'doc.deletedevent': {
- 'Meta': {'object_name': 'DeletedEvent'},
- 'by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"}),
- 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'json': ('django.db.models.fields.TextField', [], {}),
- 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'})
- },
- u'doc.docalias': {
- 'Meta': {'object_name': 'DocAlias'},
- 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'})
- },
- u'doc.docevent': {
- 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'DocEvent'},
- 'by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']"}),
- 'desc': ('django.db.models.fields.TextField', [], {}),
- 'doc': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
- 'type': ('django.db.models.fields.CharField', [], {'max_length': '50'})
- },
- u'doc.dochistory': {
- 'Meta': {'object_name': 'DocHistory'},
- 'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
- 'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_dochistory_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.DocHistoryAuthor']", 'blank': 'True'}),
- 'doc': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'history_set'", 'to': u"orm['doc.Document']"}),
- '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'}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}),
- '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'}),
- 'related': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.DocAlias']", 'symmetrical': 'False', 'through': u"orm['doc.RelatedDocHistory']", '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_dochistory_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.dochistoryauthor': {
- 'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocHistoryAuthor'},
- 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']"}),
- 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.DocHistory']"}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'order': ('django.db.models.fields.IntegerField', [], {})
- },
- u'doc.docreminder': {
- 'Meta': {'object_name': 'DocReminder'},
- 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'due': ('django.db.models.fields.DateTimeField', [], {}),
- 'event': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.DocEvent']"}),
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocReminderTypeName']"})
- },
- 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.initialreviewdocevent': {
- 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'InitialReviewDocEvent', '_ormbases': [u'doc.DocEvent']},
- u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
- 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
- },
- u'doc.lastcalldocevent': {
- 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'LastCallDocEvent', '_ormbases': [u'doc.DocEvent']},
- u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
- 'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
- },
- u'doc.newrevisiondocevent': {
- 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'NewRevisionDocEvent', '_ormbases': [u'doc.DocEvent']},
- u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
- 'rev': ('django.db.models.fields.CharField', [], {'max_length': '16'})
- },
- u'doc.relateddochistory': {
- 'Meta': {'object_name': 'RelatedDocHistory'},
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocRelationshipName']"}),
- 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.DocHistory']"}),
- 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reversely_related_document_history_set'", 'to': u"orm['doc.DocAlias']"})
- },
- u'doc.relateddocument': {
- 'Meta': {'object_name': 'RelatedDocument'},
- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'relationship': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocRelationshipName']"}),
- 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
- 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.DocAlias']"})
- },
- 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.statedocevent': {
- 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'StateDocEvent', '_ormbases': [u'doc.DocEvent']},
- u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
- 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.State']", 'null': 'True', 'blank': 'True'}),
- 'state_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.StateType']"})
- },
- 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'doc.telechatdocevent': {
- 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'TelechatDocEvent', '_ormbases': [u'doc.DocEvent']},
- u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
- 'returning_item': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'telechat_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
- },
- u'doc.writeupdocevent': {
- 'Meta': {'ordering': "['-time', '-id']", 'object_name': 'WriteupDocEvent', '_ormbases': [u'doc.DocEvent']},
- u'docevent_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['doc.DocEvent']", 'unique': 'True', 'primary_key': 'True'}),
- 'text': ('django.db.models.fields.TextField', [], {'blank': 'True'})
- },
- 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'name.ballotpositionname': {
- 'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'},
- 'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- '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.docrelationshipname': {
- 'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'},
- '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'}),
- 'revname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
- 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
- },
- u'name.docremindertypename': {
- 'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'},
- '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.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.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.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 = ['doc']
- symmetrical = True
diff --git a/ietf/doc/models.py b/ietf/doc/models.py
index 443ea3bf0..e70af5db4 100644
--- a/ietf/doc/models.py
+++ b/ietf/doc/models.py
@@ -74,9 +74,10 @@ class DocumentInfo(models.Model):
return ext.lstrip(".").lower()
def get_file_path(self):
+
if self.type_id == "draft":
return settings.INTERNET_DRAFT_PATH
- elif self.type_id in ("agenda", "minutes", "slides"):
+ elif self.type_id in ("agenda", "minutes", "slides") and self.meeting_related():
meeting = self.name.split("-")[1]
return os.path.join(settings.AGENDA_PATH, meeting, self.type_id) + "/"
elif self.type_id == "charter":
@@ -85,21 +86,27 @@ class DocumentInfo(models.Model):
return settings.CONFLICT_REVIEW_PATH
elif self.type_id == "statchg":
return settings.STATUS_CHANGE_PATH
- elif self.type_id == "material":
- return settings.MATERIALS_PATH
else:
- raise NotImplemented
+ return settings.DOCUMENT_PATH_PATTERN.format(doc=self)
def href(self):
+ meeting_related = self.meeting_related()
+
+ settings_var = settings.DOC_HREFS
+ if meeting_related:
+ settings_var = settings.MEETING_DOC_HREFS
+
try:
- format = settings.DOC_HREFS[self.type_id]
+ format = settings_var[self.type_id]
except KeyError:
if len(self.external_url):
return self.external_url
return None
+
meeting = None
- if self.type_id in ("agenda", "minutes", "slides"):
+ if meeting_related:
meeting = self.name.split("-")[1]
+
return format.format(doc=self,meeting=meeting)
def set_state(self, state):
@@ -163,6 +170,11 @@ class DocumentInfo(models.Model):
else:
return None
+ def meeting_related(self):
+ return(self.type_id in ("agenda", "minutes", "slides") and (
+ self.name.split("-")[1] == "interim"
+ or self.session_set.exists()))
+
class Meta:
abstract = True
diff --git a/ietf/group/edit.py b/ietf/group/edit.py
index 33b9f3a3e..620aab91c 100644
--- a/ietf/group/edit.py
+++ b/ietf/group/edit.py
@@ -20,8 +20,8 @@ from ietf.doc.models import NewRevisionDocEvent, save_document_in_history
from ietf.doc.utils import get_tags_for_stream_id, add_state_change_event
from ietf.group.models import ( Group, Role, GroupEvent, GroupHistory, GroupStateName,
GroupStateTransitions, GroupTypeName, GroupURL, ChangeStateGroupEvent )
-from ietf.group.utils import (save_group_in_history, can_manage_group_type, can_manage_materials,
- get_group_or_404)
+from ietf.group.utils import save_group_in_history, can_manage_group_type, can_manage_materials
+from ietf.group.utils import get_group_or_404
from ietf.ietfauth.utils import has_role
from ietf.person.forms import EmailsField
from ietf.person.models import Person, Email
@@ -458,17 +458,28 @@ def customize_workflow(request, group_type, acronym):
'tags': tags,
})
+@login_required
+def choose_material_type(request, acronym, group_type=None):
+ group = get_group_or_404(acronym, group_type)
+ if not group.features.has_materials:
+ raise Http404
+
+ return render(request, 'group/choose_material_type.html', {
+ 'group': group,
+ 'material_types': DocTypeName.objects.filter(slug__in=group.features.material_types),
+ })
+
+
class UploadMaterialForm(forms.Form):
title = forms.CharField(max_length=Document._meta.get_field("title").max_length)
- state = forms.ModelChoiceField(State.objects.filter(type="material"), empty_label=None)
+ state = forms.ModelChoiceField(State.objects.all(), empty_label=None)
material = forms.FileField(label='File', help_text="PDF or text file (ASCII/UTF-8)")
- def __init__(self, *args, **kwargs):
- action = kwargs.pop("action")
- doc = kwargs.pop("doc", None)
-
+ def __init__(self, doc_type, action, doc, *args, **kwargs):
super(UploadMaterialForm, self).__init__(*args, **kwargs)
+ self.fields["state"].queryset = self.fields["state"].queryset.filter(type=doc_type)
+
if action == "new":
self.fields["state"].widget = forms.HiddenInput()
self.fields["state"].queryset = self.fields["state"].queryset.filter(slug="active")
@@ -482,7 +493,7 @@ class UploadMaterialForm(forms.Form):
@login_required
-def edit_material(request, acronym, action="new", name=None, group_type=None):
+def edit_material(request, acronym, action="new", name=None, doc_type=None, group_type=None):
group = get_group_or_404(acronym, group_type)
if not group.features.has_materials:
raise Http404
@@ -490,19 +501,21 @@ def edit_material(request, acronym, action="new", name=None, group_type=None):
if not can_manage_materials(request.user, group):
return HttpResponseForbidden("You don't have permission to access this view")
+ document_type = get_object_or_404(DocTypeName, slug=doc_type)
+
existing = None
if name and action != "new":
existing = get_object_or_404(Document, type="material", name=name)
if request.method == 'POST':
- form = UploadMaterialForm(request.POST, request.FILES, action=action, doc=existing)
+ form = UploadMaterialForm(document_type, action, existing, request.POST, request.FILES)
if form.is_valid():
if action == "new":
d = Document()
- d.type = DocTypeName.objects.get(slug="material")
+ d.type = document_type
d.group = group
- d.rev = "01"
+ d.rev = "00"
else:
d = existing
@@ -514,12 +527,12 @@ def edit_material(request, acronym, action="new", name=None, group_type=None):
d.time = datetime.datetime.now()
if not d.name:
- d.name = "material-%s-%s" % (d.group.acronym, slugify(d.title))
+ d.name = "%s-%s-%s" % (d.type_id, d.group.acronym, slugify(d.title))
i = 2
while True:
if not Document.objects.filter(name=d.name).exists():
break
- d.name = "material-%s-%s-%s" % (d.group.acronym, slugify(d.title), i)
+ d.name = "%s-%s-%s-%s" % (d.type_id, d.group.acronym, slugify(d.title), i)
i += 1
if "material" in form.fields:
@@ -555,7 +568,7 @@ def edit_material(request, acronym, action="new", name=None, group_type=None):
return redirect("group_materials", acronym=group.acronym)
else:
- form = UploadMaterialForm(action=action, doc=existing)
+ form = UploadMaterialForm(document_type, action, existing)
return render(request, 'group/edit_material.html', {
'group': group,
diff --git a/ietf/group/features.py b/ietf/group/features.py
index e97d06529..3ab67a5f9 100644
--- a/ietf/group/features.py
+++ b/ietf/group/features.py
@@ -8,6 +8,7 @@ class GroupFeatures(object):
has_materials = False
customize_workflow = False
default_tab = "group_charter"
+ material_types = ["slides"]
def __init__(self, group):
if group.type_id in ("wg", "rg"):
diff --git a/ietf/group/info.py b/ietf/group/info.py
index 44fe2b3cd..4c237cb3f 100644
--- a/ietf/group/info.py
+++ b/ietf/group/info.py
@@ -36,6 +36,7 @@ import os
import itertools
from tempfile import mkstemp
import glob
+from collections import OrderedDict
from django.shortcuts import get_object_or_404, render
from django.template.loader import render_to_string
@@ -259,7 +260,7 @@ def concluded_groups(request):
dict(group_types=group_types))
def get_group_materials(group):
- return Document.objects.filter(group=group, type="material", session=None).exclude(states__slug="inactive")
+ return Document.objects.filter(group=group, type__in=group.features.material_types, session=None).exclude(states__slug="inactive")
def construct_group_menu_context(request, group, selected, group_type, others):
"""Return context with info for the group menu filled in."""
@@ -267,7 +268,7 @@ def construct_group_menu_context(request, group, selected, group_type, others):
if group_type:
kwargs["group_type"] = group_type
- # entries
+ # menu entries
entries = []
if group.features.has_documents:
entries.append(("Documents", urlreverse("ietf.group.info.group_documents", kwargs=kwargs)))
@@ -295,7 +296,7 @@ def construct_group_menu_context(request, group, selected, group_type, others):
actions.append((u"Add or edit milestones", urlreverse("group_edit_milestones", kwargs=kwargs)))
if group.features.has_materials and can_manage_materials(request.user, group):
- actions.append((u"Upload material", urlreverse("group_new_material", kwargs=kwargs)))
+ actions.append((u"Upload material", urlreverse("group_choose_material_type", kwargs=kwargs)))
if group.type_id in ("rg", "wg") and group.state_id != "conclude" and can_manage:
actions.append((u"Edit group", urlreverse("group_edit", kwargs=kwargs)))
@@ -446,8 +447,9 @@ def materials(request, acronym, group_type=None):
if not group.features.has_materials:
raise Http404
- materials = get_group_materials(group).order_by("-time")
- for d in materials:
+ docs = get_group_materials(group).order_by("type", "-time").select_related("type")
+ doc_types = OrderedDict()
+ for d in docs:
extension = ""
globs = glob.glob(d.get_file_path() + d.name + "-" + d.rev + ".*")
if globs:
@@ -455,9 +457,13 @@ def materials(request, acronym, group_type=None):
d.full_url = d.href() + extension
+ if d.type not in doc_types:
+ doc_types[d.type] = []
+ doc_types[d.type].append(d)
+
return render(request, 'group/materials.html',
construct_group_menu_context(request, group, "materials", group_type, {
- "materials": materials,
+ "doc_types": doc_types.items(),
"can_manage_materials": can_manage_materials(request.user, group)
}))
diff --git a/ietf/group/urls.py b/ietf/group/urls.py
index 800dc4d3c..7ea9150a8 100644
--- a/ietf/group/urls.py
+++ b/ietf/group/urls.py
@@ -7,15 +7,11 @@ urlpatterns = patterns('',
(r'^chartering/$', 'ietf.group.info.chartering_groups'),
(r'^chartering/create/(?P(wg|rg))/$', 'ietf.group.edit.edit', {'action': "charter"}, "group_create"),
(r'^concluded/$', 'ietf.group.info.concluded_groups'),
- # FIXME: the remainder here is currently duplicated in urls_info.py, need to unify these at some point
+ # FIXME: the things below are duplicated in urls_info.py, need to unify these at some point
(r'^(?P[a-zA-Z0-9-._]+)/$', 'ietf.group.info.group_home', None, "group_home"),
(r'^(?P[a-zA-Z0-9-._]+)/documents/$', 'ietf.group.info.group_documents', None, "group_docs"),
(r'^(?P[a-zA-Z0-9-._]+)/charter/$', 'ietf.group.info.group_charter', None, 'group_charter'),
(r'^(?P[a-zA-Z0-9-._]+)/history/$', 'ietf.group.info.history'),
- (r'^(?P[a-zA-Z0-9-._]+)/materials/$', 'ietf.group.info.materials', None, "group_materials"),
- (r'^(?P[a-zA-Z0-9-._]+)/materials/new/$', 'ietf.group.edit.edit_material', { 'action': "new" }, "group_new_material"),
- (r'^(?P[a-zA-Z0-9-._]+)/materials/(?Pmaterial-[^/]+)/edit/$', 'ietf.group.edit.edit_material', { 'action': "edit" }, "group_edit_material"),
- (r'^(?P[a-zA-Z0-9-._]+)/materials/(?Pmaterial-[^/]+)/revise/$', 'ietf.group.edit.edit_material', { 'action': "revise" }, "group_revise_material"),
(r'^(?P[a-zA-Z0-9-._]+)/deps/dot/$', 'ietf.group.info.dependencies_dot'),
(r'^(?P[a-zA-Z0-9-._]+)/deps/pdf/$', 'ietf.group.info.dependencies_pdf'),
(r'^(?P[a-zA-Z0-9-._]+)/init-charter/', 'ietf.group.edit.submit_initial_charter'),
@@ -26,6 +22,12 @@ urlpatterns = patterns('',
(r'^(?P[a-zA-Z0-9-._]+)/milestones/charter/reset/$', 'ietf.group.milestones.reset_charter_milestones', None, "group_reset_charter_milestones"),
(r'^(?P[a-zA-Z0-9-._]+)/ajax/searchdocs/$', 'ietf.group.milestones.ajax_search_docs', None, "group_ajax_search_docs"),
(r'^(?P[a-zA-Z0-9-._]+)/workflow/$', 'ietf.group.edit.customize_workflow'),
+
+ (r'^(?P[a-zA-Z0-9-._]+)/materials/$', 'ietf.group.info.materials', None, "group_materials"),
+ (r'^(?P[a-zA-Z0-9-._]+)/materials/new/$', 'ietf.group.edit.choose_material_type', None, "group_choose_material_type"),
+ (r'^(?P[a-zA-Z0-9-._]+)/materials/new/(?P[\w-]+)/$', 'ietf.group.edit.edit_material', { 'action': "new" }, "group_new_material"),
+ (r'^(?P[a-zA-Z0-9-._]+)/materials/(?P[^/]+)/edit/$', 'ietf.group.edit.edit_material', { 'action': "edit" }, "group_edit_material"),
+ (r'^(?P[a-zA-Z0-9-._]+)/materials/(?P[^/]+)/revise/$', 'ietf.group.edit.edit_material', { 'action': "revise" }, "group_revise_material"),
)
diff --git a/ietf/group/utils.py b/ietf/group/utils.py
index 849802411..c6a96a03d 100644
--- a/ietf/group/utils.py
+++ b/ietf/group/utils.py
@@ -118,4 +118,3 @@ def get_group_or_404(acronym, group_type):
possible_groups = possible_groups.filter(type=group_type)
return get_object_or_404(possible_groups, acronym=acronym)
-
diff --git a/ietf/name/migrations/0021_create_material_doc_type.py b/ietf/name/migrations/0021_create_material_doc_type.py
deleted file mode 100644
index 06db0ea5b..000000000
--- a/ietf/name/migrations/0021_create_material_doc_type.py
+++ /dev/null
@@ -1,190 +0,0 @@
-# -*- coding: utf-8 -*-
-from south.utils import datetime_utils as datetime
-from south.db import db
-from south.v2 import DataMigration
-from django.db import models
-
-class Migration(DataMigration):
- def forwards(self, orm):
- orm.DocTypeName.objects.get_or_create(slug="material", name="Material")
-
- def backwards(self, orm):
- orm.DocTypeName.objects.filter(slug="material").delete()
-
- models = {
- u'name.ballotpositionname': {
- 'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'},
- 'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- '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.constraintname': {
- 'Meta': {'ordering': "['order']", 'object_name': 'ConstraintName'},
- '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'}),
- 'penalty': ('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.dbtemplatetypename': {
- 'Meta': {'ordering': "['order']", 'object_name': 'DBTemplateTypeName'},
- '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.docrelationshipname': {
- 'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'},
- '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'}),
- 'revname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'slug': ('django.db.models.fields.CharField', [], {'max_length': '8', 'primary_key': 'True'}),
- 'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
- },
- u'name.docremindertypename': {
- 'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'},
- '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.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.draftsubmissionstatename': {
- 'Meta': {'ordering': "['order']", 'object_name': 'DraftSubmissionStateName'},
- 'desc': ('django.db.models.fields.TextField', [], {'blank': '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['name.DraftSubmissionStateName']"}),
- '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.feedbacktype': {
- 'Meta': {'ordering': "['order']", 'object_name': 'FeedbackType'},
- '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.liaisonstatementpurposename': {
- 'Meta': {'ordering': "['order']", 'object_name': 'LiaisonStatementPurposeName'},
- '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.meetingtypename': {
- 'Meta': {'ordering': "['order']", 'object_name': 'MeetingTypeName'},
- '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.nomineepositionstate': {
- 'Meta': {'ordering': "['order']", 'object_name': 'NomineePositionState'},
- '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.sessionstatusname': {
- 'Meta': {'ordering': "['order']", 'object_name': 'SessionStatusName'},
- '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'name.timeslottypename': {
- 'Meta': {'ordering': "['order']", 'object_name': 'TimeSlotTypeName'},
- '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'})
- }
- }
-
- complete_apps = ['name']
- symmetrical = True
diff --git a/ietf/settings.py b/ietf/settings.py
index 00c8d6a7b..3321622ee 100644
--- a/ietf/settings.py
+++ b/ietf/settings.py
@@ -251,6 +251,7 @@ DATETIME_FORMAT = "Y-m-d H:i"
# Override this in settings_local.py if needed
# *_PATH variables ends with a slash/ .
+DOCUMENT_PATH_PATTERN = '/a/www/ietf-ftp/{doc.type_id}/'
INTERNET_DRAFT_PATH = '/a/www/ietf-ftp/internet-drafts/'
INTERNET_DRAFT_PDF_PATH = '/a/www/ietf-datatracker/pdf/'
RFC_PATH = '/a/www/ietf-ftp/rfc/'
@@ -265,7 +266,6 @@ AGENDA_PATH_PATTERN = '/a/www/www6s/proceedings/%(meeting)s/agenda/%(wg)s.%(ext)
MINUTES_PATH_PATTERN = '/a/www/www6s/proceedings/%(meeting)s/minutes/%(wg)s.%(ext)s'
SLIDES_PATH_PATTERN = '/a/www/www6s/proceedings/%(meeting)s/slides/%(wg)s-*'
IPR_DOCUMENT_PATH = '/a/www/ietf-ftp/ietf/IPR/'
-MATERIALS_PATH = '/a/www/ietf-ftp/materials/'
IESG_TASK_FILE = '/a/www/www6/iesg/internal/task.txt'
IESG_ROLL_CALL_FILE = '/a/www/www6/iesg/internal/rollcall.txt'
IESG_MINUTES_FILE = '/a/www/www6/iesg/internal/minutes.txt'
@@ -275,7 +275,6 @@ INTERNET_DRAFT_ARCHIVE_DIR = '/a/www/www6s/draft-archive'
# Ideally, more of these would be local -- but since we don't support
# versions right now, we'll point to external websites
DOC_HREFS = {
- "agenda": "/meeting/{meeting}/agenda/{doc.group.acronym}/",
#"charter": "/doc/{doc.name}-{doc.rev}/",
"charter": "http://www.ietf.org/charter/{doc.name}-{doc.rev}.txt",
#"draft": "/doc/{doc.name}-{doc.rev}/",
@@ -284,9 +283,13 @@ DOC_HREFS = {
# who understands this better can take care of it.
#"liai-att": None
#"liaison": None
+ "slides": 'http://www.ietf.org/slides/',
+}
+
+MEETING_DOC_HREFS = {
+ "agenda": "/meeting/{meeting}/agenda/{doc.group.acronym}/",
"minutes": "http://www.ietf.org/proceedings/{meeting}/minutes/{doc.external_url}",
"slides": "http://www.ietf.org/proceedings/{meeting}/slides/{doc.external_url}",
- "material": "http://www.ietf.org/material/{doc.name}-{doc.rev}",
}
# Override this in settings_local.py if needed
diff --git a/ietf/templates/group/choose_material_type.html b/ietf/templates/group/choose_material_type.html
new file mode 100644
index 000000000..e99604630
--- /dev/null
+++ b/ietf/templates/group/choose_material_type.html
@@ -0,0 +1,23 @@
+{% extends "base.html" %}
+
+{% block title %}Upload Material for Group {{ group.acronym }}{% endblock %}
+
+{% block morecss %}
+{{ block.super }}
+.material-types li { margin-bottom: 0.5em; }
+{% endblock %}
+
+{% block content %}
+{% load ietf_filters %}
+
+Upload Material for Group {{ group.acronym }}
+
+Select what kind of material you wish to upload:
+
+
+ {% for t in material_types %}
+ {{ t.name }}
+ {% endfor %}
+
+
+{% endblock content %}
diff --git a/ietf/templates/group/edit_material.html b/ietf/templates/group/edit_material.html
index 29e7e768c..fed4e6f51 100644
--- a/ietf/templates/group/edit_material.html
+++ b/ietf/templates/group/edit_material.html
@@ -12,8 +12,6 @@ form.upload-material .submit-row td { padding-top: 1em; text-align: right; }
{% block content %}
{% load ietf_filters %}
-
-
Upload Material for Group {{ group.acronym }}
{% if action == "new" %}
diff --git a/ietf/templates/group/materials.html b/ietf/templates/group/materials.html
index d80251781..4287e1ff0 100644
--- a/ietf/templates/group/materials.html
+++ b/ietf/templates/group/materials.html
@@ -9,37 +9,41 @@
{% endblock %}
{% block group_content %}
-{% load ietf_filters %}
+ {% load ietf_filters %}
-
Materials
+ {% if doc_types %}
+ {% for doc_type, docs in doc_types %}
+
{{ doc_type.name }}
- {% if materials %}
-
+
-
- Title
- Rev.
- Date
-
-
- {% for d in materials %}
-
-
- {% if can_manage_materials %}
-
- edit |
- revise
-
- {% endif %}
-
- {{ d.title }}
-
- {{ d.rev }}
- {{ d.time|date:"Y-m-d" }}
+
+ Title
+ Rev.
+ Date
- {% endfor %}
-
+
+ {% for d in docs %}
+
+
+ {% if can_manage_materials %}
+
+ edit |
+ revise
+
+ {% endif %}
+
+ {{ d.title }}
+
+ {{ d.rev }}
+ {{ d.time|date:"Y-m-d" }}
+
+ {% endfor %}
+
+ {% endfor %}
{% else %}
+
Materials
+
No materials uploaded.
{% endif %}
From 976d0b9d685d5fe34a406db4113646fd1a9c5de8 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Fri, 30 May 2014 15:59:02 +0000
Subject: [PATCH 13/26] Turn charter page for non-chartering groups into about
page, fix a bunch of potentially broken links due to the split between
group-type prefixed URLs and /group/ ones - Legacy-Id: 7805
---
ietf/group/edit.py | 19 ++++++++-------
ietf/group/features.py | 6 ++++-
ietf/group/feeds.py | 4 ++--
ietf/group/info.py | 24 ++++++-------------
ietf/group/mails.py | 8 +++----
ietf/group/milestones.py | 4 ++--
ietf/group/models.py | 8 +++++++
ietf/group/tests_info.py | 2 +-
ietf/group/urls.py | 3 ++-
ietf/group/urls_info.py | 2 +-
ietf/templates/doc/document_charter.html | 2 +-
ietf/templates/group/bofs.html | 2 +-
ietf/templates/group/chartering_groups.html | 2 +-
ietf/templates/group/conclude.html | 2 +-
ietf/templates/group/concluded_groups.html | 2 +-
ietf/templates/group/customize_workflow.html | 2 +-
ietf/templates/group/edit.html | 2 +-
ietf/templates/group/edit_milestones.html | 4 ++--
.../{group_charter.html => group_about.html} | 11 +++++++--
19 files changed, 60 insertions(+), 49 deletions(-)
rename ietf/templates/group/{group_charter.html => group_about.html} (93%)
diff --git a/ietf/group/edit.py b/ietf/group/edit.py
index 620aab91c..5cdcdb08a 100644
--- a/ietf/group/edit.py
+++ b/ietf/group/edit.py
@@ -7,10 +7,9 @@ import shutil
from django import forms
from django.shortcuts import render, get_object_or_404, redirect
-from django.http import HttpResponseForbidden, Http404
+from django.http import HttpResponse, HttpResponseForbidden, Http404, HttpResponseRedirect
from django.utils.html import mark_safe
from django.utils.text import slugify
-from django.http import Http404, HttpResponse
from django.contrib.auth.decorators import login_required
import debug # pyflakes:ignore
@@ -304,7 +303,7 @@ def edit(request, group_type=None, acronym=None, action="edit"):
if action=="charter":
return redirect('charter_submit', name=group.charter.name, option="initcharter")
- return redirect('group_charter', group_type=group.type_id, acronym=group.acronym)
+ return HttpResponseRedirect(group.about_url())
else: # form.is_valid()
if not new_group:
init = dict(name=group.name,
@@ -337,11 +336,11 @@ class ConcludeForm(forms.Form):
instructions = forms.CharField(widget=forms.Textarea(attrs={'rows': 30}), required=True)
@login_required
-def conclude(request, group_type, acronym):
+def conclude(request, acronym, group_type=None):
"""Request the closing of group, prompting for instructions."""
group = get_group_or_404(acronym, group_type)
- if not can_manage_group_type(request.user, group_type):
+ if not can_manage_group_type(request.user, group.type_id):
return HttpResponseForbidden("You don't have permission to access this view")
if request.method == 'POST':
@@ -356,13 +355,15 @@ def conclude(request, group_type, acronym):
e.desc = "Requested closing group"
e.save()
- return redirect('group_charter', group_type=group.type_id, acronym=group.acronym)
+ return redirect(group.features.about_page, group_type=group_type, acronym=group.acronym)
else:
form = ConcludeForm()
- return render(request, 'group/conclude.html',
- dict(form=form, group=group))
-
+ return render(request, 'group/conclude.html', {
+ 'form': form,
+ 'group': group,
+ 'group_type': group_type,
+ })
@login_required
def customize_workflow(request, group_type, acronym):
diff --git a/ietf/group/features.py b/ietf/group/features.py
index 3ab67a5f9..f2e0b2293 100644
--- a/ietf/group/features.py
+++ b/ietf/group/features.py
@@ -7,7 +7,8 @@ class GroupFeatures(object):
has_documents = False # i.e. drafts/RFCs
has_materials = False
customize_workflow = False
- default_tab = "group_charter"
+ about_page = "group_about"
+ default_tab = about_page
material_types = ["slides"]
def __init__(self, group):
@@ -19,3 +20,6 @@ class GroupFeatures(object):
self.default_tab = "group_docs"
elif group.type_id in ("team",):
self.has_materials = True
+
+ if self.has_chartering_process:
+ about_page = "group_charter"
diff --git a/ietf/group/feeds.py b/ietf/group/feeds.py
index e54c6a2bf..2a09314e4 100644
--- a/ietf/group/feeds.py
+++ b/ietf/group/feeds.py
@@ -22,7 +22,7 @@ class GroupChangesFeed(Feed):
def link(self, obj):
if not obj:
raise FeedDoesNotExist
- return urlreverse('group_charter', kwargs=dict(group_type=obj.type_id, acronym=obj.acronym))
+ return group.about_url()
def description(self, obj):
return self.title(obj)
@@ -40,7 +40,7 @@ class GroupChangesFeed(Feed):
if isinstance(obj, DocEvent):
return urlreverse("doc_view", kwargs={'name': obj.doc_id })
elif isinstance(obj, GroupEvent):
- return urlreverse('group_charter', kwargs=dict(group_type=obj.group.type_id, acronym=obj.group.acronym))
+ return group.about_url()
def item_pubdate(self, obj):
return obj.time
diff --git a/ietf/group/info.py b/ietf/group/info.py
index 4c237cb3f..d194a20e6 100644
--- a/ietf/group/info.py
+++ b/ietf/group/info.py
@@ -272,7 +272,10 @@ def construct_group_menu_context(request, group, selected, group_type, others):
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)))
+ if group.features.has_chartering_process:
+ entries.append(("Charter", urlreverse("group_charter", kwargs=kwargs)))
+ else:
+ entries.append(("About", urlreverse("group_about", kwargs=kwargs)))
if group.features.has_materials and get_group_materials(group).exists():
entries.append(("Materials", urlreverse("ietf.group.info.materials", kwargs=kwargs)))
entries.append(("History", urlreverse("ietf.group.info.history", kwargs=kwargs)))
@@ -400,7 +403,7 @@ def group_documents_txt(request, acronym, group_type=None):
return HttpResponse(u"\n".join(rows), content_type='text/plain; charset=UTF-8')
-def group_charter(request, acronym, group_type=None):
+def group_about(request, acronym, group_type=None):
group = get_group_or_404(acronym, group_type)
fill_in_charter_info(group)
@@ -408,27 +411,14 @@ def group_charter(request, acronym, group_type=None):
e = group.latest_event(type__in=("changed_state", "requested_close",))
requested_close = group.state_id != "conclude" and e and e.type == "requested_close"
- 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 yet."
-
- return render(request, 'group/group_charter.html',
- construct_group_menu_context(request, group, "charter", group_type, {
+ return render(request, 'group/group_about.html',
+ construct_group_menu_context(request, group, "charter" if group.features.has_chartering_process else "about", group_type, {
"milestones_in_review": group.groupmilestone_set.filter(state="review"),
"milestone_reviewer": milestone_reviewer_for_group_type(group_type),
"requested_close": requested_close,
- "verbose_group_type": verbose_group_types.get(group.type_id, "Group"),
"can_manage": can_manage,
- "description": description,
}))
diff --git a/ietf/group/mails.py b/ietf/group/mails.py
index 4a2cbdd26..1fc7e8b84 100644
--- a/ietf/group/mails.py
+++ b/ietf/group/mails.py
@@ -22,7 +22,7 @@ def email_secretariat(request, group, subject, text):
"group/email_secretariat.txt",
dict(text=text,
group=group,
- group_url=settings.IDTRACKER_BASE_URL + urlreverse('group_charter', kwargs=dict(group_type=group.type_id, acronym=group.acronym)),
+ group_url=settings.IDTRACKER_BASE_URL + group.about_url(),
charter_url=settings.IDTRACKER_BASE_URL + urlreverse('doc_view', kwargs=dict(name=group.charter.name)),
)
)
@@ -31,7 +31,7 @@ def email_milestones_changed(request, group, changes):
def wrap_up_email(to, text):
text = wrap(strip_tags(text), 70)
text += "\n\n"
- text += u"URL: %s" % (settings.IDTRACKER_BASE_URL + urlreverse("group_charter", kwargs=dict(group_type=group.type_id, acronym=group.acronym)))
+ text += u"URL: %s" % (settings.IDTRACKER_BASE_URL + group.about_url())
send_mail_text(request, to, None,
u"Milestones changed for %s %s" % (group.acronym, group.type.name),
@@ -121,7 +121,7 @@ def email_milestones_due(group, early_warning_days):
milestones=milestones,
today=today,
early_warning_days=early_warning_days,
- url=settings.IDTRACKER_BASE_URL + urlreverse("group_charter", kwargs=dict(group_type=group.type_id, acronym=group.acronym))
+ url=settings.IDTRACKER_BASE_URL + group.about_url(),
))
def groups_needing_milestones_due_reminder(early_warning_days):
@@ -146,7 +146,7 @@ def email_milestones_overdue(group):
"group/reminder_milestones_overdue.txt",
dict(group=group,
milestones=milestones,
- url=settings.IDTRACKER_BASE_URL + urlreverse("group_charter", kwargs=dict(group_type=group.type_id, acronym=group.acronym))
+ url=settings.IDTRACKER_BASE_URL + group.about_url(),
))
def groups_needing_milestones_overdue_reminder(grace_period=30):
diff --git a/ietf/group/milestones.py b/ietf/group/milestones.py
index fdfc0eaa5..a82e1a637 100644
--- a/ietf/group/milestones.py
+++ b/ietf/group/milestones.py
@@ -5,7 +5,7 @@ import calendar
import json
from django import forms
-from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest
+from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest, HttpResponseRedirect
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
@@ -311,7 +311,7 @@ def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
if milestone_set == "charter":
return redirect('doc_view', name=group.charter.canonical_name())
else:
- return redirect('group_charter', group_type=group.type_id, acronym=group.acronym)
+ return HttpResponseRedirect(group.about_url())
else:
for m in milestones:
forms.append(MilestoneForm(instance=m, needs_review=needs_review))
diff --git a/ietf/group/models.py b/ietf/group/models.py
index 64009012e..c69a1d458 100644
--- a/ietf/group/models.py
+++ b/ietf/group/models.py
@@ -36,6 +36,14 @@ class GroupInfo(models.Model):
res += " %s (%s)" % (self.type, self.acronym)
return res
+ def about_url(self):
+ # bridge gap between group-type prefixed URLs and /group/ ones
+ from django.core.urlresolvers import reverse as urlreverse
+ kwargs = { 'acronym': self.acronym }
+ if self.type_id in ("wg", "rg"):
+ kwargs["group_type"] = self.type_id
+ return urlreverse(self.features.about_page, kwargs=kwargs)
+
class Meta:
abstract = True
diff --git a/ietf/group/tests_info.py b/ietf/group/tests_info.py
index 4e1b26b3f..e04433ffc 100644
--- a/ietf/group/tests_info.py
+++ b/ietf/group/tests_info.py
@@ -183,7 +183,7 @@ class GroupPagesTests(TestCase):
due=datetime.date.today() + datetime.timedelta(days=100))
milestone.docs.add(draft)
- url = urlreverse('ietf.group.info.group_charter', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
+ url = group.about_url()
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue(group.name in r.content)
diff --git a/ietf/group/urls.py b/ietf/group/urls.py
index 7ea9150a8..4afb28fc9 100644
--- a/ietf/group/urls.py
+++ b/ietf/group/urls.py
@@ -10,7 +10,7 @@ urlpatterns = patterns('',
# FIXME: the things below are duplicated in urls_info.py, need to unify these at some point
(r'^(?P[a-zA-Z0-9-._]+)/$', 'ietf.group.info.group_home', None, "group_home"),
(r'^(?P[a-zA-Z0-9-._]+)/documents/$', 'ietf.group.info.group_documents', None, "group_docs"),
- (r'^(?P[a-zA-Z0-9-._]+)/charter/$', 'ietf.group.info.group_charter', None, 'group_charter'),
+ (r'^(?P[a-zA-Z0-9-._]+)/charter/$', 'ietf.group.info.group_about', None, 'group_charter'),
(r'^(?P[a-zA-Z0-9-._]+)/history/$', 'ietf.group.info.history'),
(r'^(?P[a-zA-Z0-9-._]+)/deps/dot/$', 'ietf.group.info.dependencies_dot'),
(r'^(?P[a-zA-Z0-9-._]+)/deps/pdf/$', 'ietf.group.info.dependencies_pdf'),
@@ -23,6 +23,7 @@ urlpatterns = patterns('',
(r'^(?P[a-zA-Z0-9-._]+)/ajax/searchdocs/$', 'ietf.group.milestones.ajax_search_docs', None, "group_ajax_search_docs"),
(r'^(?P[a-zA-Z0-9-._]+)/workflow/$', 'ietf.group.edit.customize_workflow'),
+ (r'^(?P[a-zA-Z0-9-._]+)/about/(?P.)?$', 'ietf.group.info.group_about', None, 'group_about'),
(r'^(?P[a-zA-Z0-9-._]+)/materials/$', 'ietf.group.info.materials', None, "group_materials"),
(r'^(?P[a-zA-Z0-9-._]+)/materials/new/$', 'ietf.group.edit.choose_material_type', None, "group_choose_material_type"),
(r'^(?P[a-zA-Z0-9-._]+)/materials/new/(?P[\w-]+)/$', 'ietf.group.edit.edit_material', { 'action': "new" }, "group_new_material"),
diff --git a/ietf/group/urls_info.py b/ietf/group/urls_info.py
index 0d838a1b3..ba5d2ab1f 100644
--- a/ietf/group/urls_info.py
+++ b/ietf/group/urls_info.py
@@ -21,7 +21,7 @@ urlpatterns = patterns('',
(r'^(?P[a-zA-Z0-9-._]+)/documents/txt/$', info.group_documents_txt),
(r'^(?P[a-zA-Z0-9-._]+)/$', info.group_home, None, "group_home"),
(r'^(?P[a-zA-Z0-9-._]+)/documents/$', info.group_documents, None, "group_docs"),
- (r'^(?P[a-zA-Z0-9-._]+)/charter/$', info.group_charter, None, 'group_charter'),
+ (r'^(?P[a-zA-Z0-9-._]+)/charter/$', info.group_about, None, 'group_charter'),
(r'^(?P[a-zA-Z0-9-._]+)/history/$', info.history),
(r'^(?P[a-zA-Z0-9-._]+)/deps/dot/$', info.dependencies_dot),
(r'^(?P[a-zA-Z0-9-._]+)/deps/pdf/$', info.dependencies_pdf),
diff --git a/ietf/templates/doc/document_charter.html b/ietf/templates/doc/document_charter.html
index a39f26226..ed9f119f3 100644
--- a/ietf/templates/doc/document_charter.html
+++ b/ietf/templates/doc/document_charter.html
@@ -25,7 +25,7 @@
{% if snapshot %}Snapshot of{% endif %}
{% if doc.get_state_slug != "approved" %}Proposed{% endif %}
Charter for "{{ group.name }}"
- ({{ group.acronym }} ) {{ group.type.name }}
+ ({{ group.acronym }} ) {{ group.type.name }}
diff --git a/ietf/templates/group/group_charter.html b/ietf/templates/group/group_about.html
similarity index 93%
rename from ietf/templates/group/group_charter.html
rename to ietf/templates/group/group_about.html
index 9fb5ca5ba..4e1ef4bb6 100644
--- a/ietf/templates/group/group_charter.html
+++ b/ietf/templates/group/group_about.html
@@ -99,9 +99,16 @@ is occasionally incorrect.
{% endif %}
{% endwith %}
-Charter for {% if group.state_id == "proposed" %}Proposed{% endif %} {{ verbose_group_type }}
+{% if group.features.has_chartering_process %}
+Charter for {% if group.state_id == "proposed" %}Proposed{% endif %} {{ group.type.desc.title }}
+
+{{ group.charter_text|linebreaks }}
+{% else %}
+About
+
+{{ group.description|default:"No description yet."|linebreaks }}
+{% endif %}
-{{ description|linebreaks }}
{% if group.features.has_milestones %}
{% if group.state_id == "proposed" %}Proposed{% endif %} Milestones
From 63dabff77902bbfc99f1a93f2803137e9c4ff5db Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Fri, 30 May 2014 16:17:21 +0000
Subject: [PATCH 14/26] Fix some bugs in previous commit - Legacy-Id: 7806
---
ietf/group/features.py | 2 +-
ietf/group/models.py | 14 +++++++-------
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/ietf/group/features.py b/ietf/group/features.py
index f2e0b2293..98b5ba2da 100644
--- a/ietf/group/features.py
+++ b/ietf/group/features.py
@@ -22,4 +22,4 @@ class GroupFeatures(object):
self.has_materials = True
if self.has_chartering_process:
- about_page = "group_charter"
+ self.about_page = "group_charter"
diff --git a/ietf/group/models.py b/ietf/group/models.py
index c69a1d458..efa4d871a 100644
--- a/ietf/group/models.py
+++ b/ietf/group/models.py
@@ -36,6 +36,13 @@ class GroupInfo(models.Model):
res += " %s (%s)" % (self.type, self.acronym)
return res
+ @property
+ def features(self):
+ if not hasattr(self, "features_cache"):
+ from ietf.group.features import GroupFeatures
+ self.features_cache = GroupFeatures(self)
+ return self.features_cache
+
def about_url(self):
# bridge gap between group-type prefixed URLs and /group/ ones
from django.core.urlresolvers import reverse as urlreverse
@@ -88,13 +95,6 @@ class Group(GroupInfo):
def bg_color(self):
return bg_group_colors[self.upcase_acronym]
- @property
- def features(self):
- if not hasattr(self, "features_cache"):
- from ietf.group.features import GroupFeatures
- self.features_cache = GroupFeatures(self)
- return self.features_cache
-
def json_url(self):
return "/group/%s.json" % (self.acronym,)
From 8aede5ebcd38dc1c6f123258a4098570e51f40ad Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Sat, 31 May 2014 14:14:47 +0000
Subject: [PATCH 15/26] Move the additional group URLs to info box -
Legacy-Id: 7807
---
ietf/templates/group/group_about.html | 23 +++++++++++++----------
1 file changed, 13 insertions(+), 10 deletions(-)
diff --git a/ietf/templates/group/group_about.html b/ietf/templates/group/group_about.html
index 4e1ef4bb6..6f8265b37 100644
--- a/ietf/templates/group/group_about.html
+++ b/ietf/templates/group/group_about.html
@@ -50,6 +50,19 @@ is occasionally incorrect.
{% endif %}
+ {% with group.groupurl_set.all as urls %}
+ {% if urls %}
+
+ More info:
+
+ {% for url in urls %}
+ {{ url.name }} {% if not forloop.last %} {% endif %}
+ {% endfor %}
+
+
+ {% endif %}
+ {% endwith %}
+
Personnel
{% for slug, label, roles in group.personnel %}
@@ -89,16 +102,6 @@ is occasionally incorrect.
-{% with group.groupurl_set.all as urls %}
-{% if urls %}
-In addition to the charter, there is additional information about this group on the Web at:
- {% for url in urls %}
- {{ url.name }} {% if not forloop.last %}, {% endif %}
- {% endfor %}
-
-{% endif %}
-{% endwith %}
-
{% if group.features.has_chartering_process %}
Charter for {% if group.state_id == "proposed" %}Proposed{% endif %} {{ group.type.desc.title }}
From 7df4de78e3198ff2fe3cfa0d1ada49a1b0a70759 Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Sat, 31 May 2014 14:16:59 +0000
Subject: [PATCH 16/26] Throw a validation error when uploading a material with
an already used name, asking uploader to either choose a new title or revise
the previous material - Legacy-Id: 7808
---
ietf/group/edit.py | 45 ++++++++++++++++---------
ietf/group/info.py | 2 +-
ietf/templates/group/edit_material.html | 2 +-
3 files changed, 31 insertions(+), 18 deletions(-)
diff --git a/ietf/group/edit.py b/ietf/group/edit.py
index 5cdcdb08a..67fcbf95b 100644
--- a/ietf/group/edit.py
+++ b/ietf/group/edit.py
@@ -11,6 +11,7 @@ from django.http import HttpResponse, HttpResponseForbidden, Http404, HttpRespon
from django.utils.html import mark_safe
from django.utils.text import slugify
from django.contrib.auth.decorators import login_required
+from django.core.urlresolvers import reverse as urlreverse
import debug # pyflakes:ignore
@@ -469,18 +470,24 @@ def choose_material_type(request, acronym, group_type=None):
'group': group,
'material_types': DocTypeName.objects.filter(slug__in=group.features.material_types),
})
-
+
+def name_for_material(doc_type, group, title):
+ return "%s-%s-%s" % (doc_type.slug, group.acronym, slugify(title))
class UploadMaterialForm(forms.Form):
title = forms.CharField(max_length=Document._meta.get_field("title").max_length)
state = forms.ModelChoiceField(State.objects.all(), empty_label=None)
material = forms.FileField(label='File', help_text="PDF or text file (ASCII/UTF-8)")
- def __init__(self, doc_type, action, doc, *args, **kwargs):
+ def __init__(self, doc_type, action, group, doc, *args, **kwargs):
super(UploadMaterialForm, self).__init__(*args, **kwargs)
self.fields["state"].queryset = self.fields["state"].queryset.filter(type=doc_type)
+ self.doc_type = doc_type
+ self.action = action
+ self.group = group
+
if action == "new":
self.fields["state"].widget = forms.HiddenInput()
self.fields["state"].queryset = self.fields["state"].queryset.filter(slug="active")
@@ -488,10 +495,23 @@ class UploadMaterialForm(forms.Form):
else:
self.fields["title"].initial = doc.title
self.fields["state"].initial = doc.get_state().pk if doc.get_state() else None
+ if doc.get_state_slug() == "deleted":
+ self.fields["state"].help_text = "Note: If you wish to revise this document, you may wish to change the state so it's not deleted."
if action == "edit":
del self.fields["material"]
+ def clean_title(self):
+ title = self.cleaned_data["title"]
+ if self.action == "new":
+ name = name_for_material(self.doc_type, self.group, title)
+ existing = Document.objects.filter(type=self.doc_type, name=name)
+ if existing:
+ url = urlreverse("group_revise_material", kwargs={ 'acronym': self.group.acronym, 'name': existing[0].name })
+ raise forms.ValidationError(mark_safe("Can't upload: %s with name %s already exists. The name is derived from the title so you must either choose another title for what you're uploading or revise the existing %s ." % (self.doc_type.name, name, url, name)))
+
+ return title
+
@login_required
def edit_material(request, acronym, action="new", name=None, doc_type=None, group_type=None):
@@ -502,14 +522,15 @@ def edit_material(request, acronym, action="new", name=None, doc_type=None, grou
if not can_manage_materials(request.user, group):
return HttpResponseForbidden("You don't have permission to access this view")
- document_type = get_object_or_404(DocTypeName, slug=doc_type)
-
existing = None
if name and action != "new":
- existing = get_object_or_404(Document, type="material", name=name)
+ existing = get_object_or_404(Document, name=name)
+ document_type = existing.type
+ else:
+ document_type = get_object_or_404(DocTypeName, slug=doc_type)
if request.method == 'POST':
- form = UploadMaterialForm(document_type, action, existing, request.POST, request.FILES)
+ form = UploadMaterialForm(document_type, action, group, existing, request.POST, request.FILES)
if form.is_valid():
if action == "new":
@@ -517,6 +538,7 @@ def edit_material(request, acronym, action="new", name=None, doc_type=None, grou
d.type = document_type
d.group = group
d.rev = "00"
+ d.name = name_for_material(d.type, d.group, form.cleaned_data["title"])
else:
d = existing
@@ -527,15 +549,6 @@ def edit_material(request, acronym, action="new", name=None, doc_type=None, grou
d.title = form.cleaned_data["title"]
d.time = datetime.datetime.now()
- if not d.name:
- d.name = "%s-%s-%s" % (d.type_id, d.group.acronym, slugify(d.title))
- i = 2
- while True:
- if not Document.objects.filter(name=d.name).exists():
- break
- d.name = "%s-%s-%s-%s" % (d.type_id, d.group.acronym, slugify(d.title), i)
- i += 1
-
if "material" in form.fields:
if action != "new":
d.rev = "%02d" % (int(d.rev) + 1)
@@ -569,7 +582,7 @@ def edit_material(request, acronym, action="new", name=None, doc_type=None, grou
return redirect("group_materials", acronym=group.acronym)
else:
- form = UploadMaterialForm(document_type, action, existing)
+ form = UploadMaterialForm(document_type, action, group, existing)
return render(request, 'group/edit_material.html', {
'group': group,
diff --git a/ietf/group/info.py b/ietf/group/info.py
index d194a20e6..39881fc40 100644
--- a/ietf/group/info.py
+++ b/ietf/group/info.py
@@ -260,7 +260,7 @@ def concluded_groups(request):
dict(group_types=group_types))
def get_group_materials(group):
- return Document.objects.filter(group=group, type__in=group.features.material_types, session=None).exclude(states__slug="inactive")
+ return Document.objects.filter(group=group, type__in=group.features.material_types, session=None).exclude(states__slug="deleted")
def construct_group_menu_context(request, group, selected, group_type, others):
"""Return context with info for the group menu filled in."""
diff --git a/ietf/templates/group/edit_material.html b/ietf/templates/group/edit_material.html
index fed4e6f51..934d56abb 100644
--- a/ietf/templates/group/edit_material.html
+++ b/ietf/templates/group/edit_material.html
@@ -41,7 +41,7 @@ form.upload-material .submit-row td { padding-top: 1em; text-align: right; }
Cancel
-
+
From 779763e58e5f9182873ab1600d7b2dd677236c9f Mon Sep 17 00:00:00 2001
From: Ole Laursen
Date: Mon, 2 Jun 2014 11:09:05 +0000
Subject: [PATCH 17/26] Move common revision list in document templates to a
separate included file - Legacy-Id: 7824
---
ietf/templates/doc/document_charter.html | 9 +--------
ietf/templates/doc/document_conflict_review.html | 9 +--------
ietf/templates/doc/document_status_change.html | 9 +--------
ietf/templates/doc/revisions_list.html | 8 ++++++++
4 files changed, 11 insertions(+), 24 deletions(-)
create mode 100644 ietf/templates/doc/revisions_list.html
diff --git a/ietf/templates/doc/document_charter.html b/ietf/templates/doc/document_charter.html
index ed9f119f3..52034cdb5 100644
--- a/ietf/templates/doc/document_charter.html
+++ b/ietf/templates/doc/document_charter.html
@@ -11,14 +11,7 @@
{% block content %}
{{ top|safe }}
-
- Snapshots:
-
- {% for rev in revisions %}
- {{ rev }}
- {% endfor %}
-
-
+{% include "doc/revisions_list.html" %}