Merged in branch/iola/team-support from olau@iola.dk. This generalises and extends the group support for WGs and RGs so that also other groups, such as for instance the Edu Team, can have pages.
- Legacy-Id: 8013
This commit is contained in:
commit
09f654b1f4
|
@ -88,7 +88,7 @@ class WGField(DisplayField):
|
|||
if raw or not document.group.type_id in ['wg','rg']:
|
||||
return document.group.acronym
|
||||
else:
|
||||
return '<a href="%s">%s</a>' % (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 '<a href="%s">%s</a>' % (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):
|
||||
|
|
|
@ -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":
|
||||
|
@ -86,18 +87,26 @@ class DocumentInfo(models.Model):
|
|||
elif self.type_id == "statchg":
|
||||
return settings.STATUS_CHANGE_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):
|
||||
|
@ -120,6 +129,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
|
||||
|
||||
|
@ -158,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() if isinstance(self, Document) else self.doc.session_set.exists())))
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
@ -270,12 +287,9 @@ class Document(DocumentInfo):
|
|||
a = self.docalias_set.filter(name__startswith="rfc")
|
||||
if a:
|
||||
name = a[0].name
|
||||
# elif self.type_id == "charter":
|
||||
# if self.group.type.slug == "rg":
|
||||
# top_org = "irtf"
|
||||
# else:
|
||||
# top_org = "ietf"
|
||||
# return "charter-%s-%s" % (top_org, self.chartered_group.acronym)
|
||||
elif self.type_id == "charter":
|
||||
from ietf.doc.utils_charter import charter_name_for_group
|
||||
return charter_name_for_group(self.chartered_group)
|
||||
return name
|
||||
|
||||
def canonical_docalias(self):
|
||||
|
@ -468,7 +482,11 @@ class DocHistoryAuthor(models.Model):
|
|||
|
||||
class DocHistory(DocumentInfo):
|
||||
doc = models.ForeignKey(Document, related_name="history_set")
|
||||
name = models.CharField(max_length=255) # WG charter canonical names can change if the group acronym changes
|
||||
# the name here is used to capture the canonical name at the time
|
||||
# - it would perhaps be more elegant to simply call the attribute
|
||||
# canonical_name and replace the function on Document with a
|
||||
# property
|
||||
name = models.CharField(max_length=255)
|
||||
related = models.ManyToManyField('DocAlias', through=RelatedDocHistory, blank=True)
|
||||
authors = models.ManyToManyField(Email, through=DocHistoryAuthor, blank=True)
|
||||
def __unicode__(self):
|
||||
|
|
|
@ -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", "</p><p>").replace("\n","<br/>\n")
|
||||
|
||||
@register.filter
|
||||
def indent(value, numspaces=2):
|
||||
replacement = "\n" + " " * int(numspaces)
|
||||
|
|
|
@ -217,6 +217,22 @@ class DocTestCase(TestCase):
|
|||
r = self.client.get(urlreverse("doc_view", kwargs=dict(name='conflict-review-imaginary-irtf-submission')))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_document_material(self):
|
||||
draft = make_test_data()
|
||||
|
||||
doc = Document.objects.create(
|
||||
name="slides-testteam-test-slides",
|
||||
rev="00",
|
||||
title="Test Slides",
|
||||
group=draft.group,
|
||||
type_id="slides"
|
||||
)
|
||||
doc.set_state(State.objects.get(type="slides", slug="active"))
|
||||
DocAlias.objects.create(name=doc.name, document=doc)
|
||||
|
||||
r = self.client.get(urlreverse("doc_view", kwargs=dict(name=doc.name)))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_document_ballot(self):
|
||||
doc = make_test_data()
|
||||
ballot = doc.active_ballot()
|
||||
|
|
138
ietf/doc/tests_material.py
Normal file
138
ietf/doc/tests_material.py
Normal file
|
@ -0,0 +1,138 @@
|
|||
# Copyright The IETF Trust 2011, All Rights Reserved
|
||||
|
||||
import os, shutil
|
||||
from StringIO import StringIO
|
||||
from pyquery import PyQuery
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
|
||||
from ietf.doc.models import Document, State, DocAlias
|
||||
from ietf.group.models import Group
|
||||
from ietf.utils.test_utils import TestCase, login_testing_unauthorized
|
||||
|
||||
class GroupMaterialTests(TestCase):
|
||||
def setUp(self):
|
||||
self.materials_dir = os.path.abspath("tmp-document-dir")
|
||||
os.mkdir(self.materials_dir)
|
||||
os.mkdir(os.path.join(self.materials_dir, "slides"))
|
||||
settings.DOCUMENT_PATH_PATTERN = self.materials_dir + "/{doc.type_id}/"
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.materials_dir)
|
||||
|
||||
def create_slides(self):
|
||||
group = Group.objects.create(type_id="team", acronym="testteam", name="Test Team", state_id="active")
|
||||
|
||||
doc = Document.objects.create(name="slides-testteam-test-file", rev="00", type_id="slides", group=group)
|
||||
doc.set_state(State.objects.get(type="slides", slug="active"))
|
||||
DocAlias.objects.create(name=doc.name, document=doc)
|
||||
|
||||
return doc
|
||||
|
||||
def test_choose_material_type(self):
|
||||
group = Group.objects.create(type_id="team", acronym="testteam", name="Test Team", state_id="active")
|
||||
|
||||
url = urlreverse('ietf.doc.views_material.choose_material_type', kwargs=dict(acronym=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue("Slides" in r.content)
|
||||
|
||||
def test_upload_slides(self):
|
||||
group = Group.objects.create(type_id="team", acronym="testteam", name="Test Team", state_id="active")
|
||||
|
||||
url = urlreverse('group_new_material', kwargs=dict(acronym=group.acronym, doc_type="slides"))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
content = "%PDF-1.5\n..."
|
||||
test_file = StringIO(content)
|
||||
test_file.name = "unnamed.pdf"
|
||||
|
||||
# faulty post
|
||||
r = self.client.post(url, dict(title="", name="", state="", material=test_file))
|
||||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
|
||||
test_file.seek(0)
|
||||
|
||||
# post
|
||||
r = self.client.post(url, dict(title="Test File",
|
||||
name="slides-%s-test-file" % group.acronym,
|
||||
state=State.objects.get(type="slides", slug="active").pk,
|
||||
material=test_file))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
doc = Document.objects.get(name="slides-%s-test-file" % group.acronym)
|
||||
self.assertEqual(doc.rev, "00")
|
||||
self.assertEqual(doc.title, "Test File")
|
||||
self.assertEqual(doc.get_state_slug(), "active")
|
||||
|
||||
with open(os.path.join(self.materials_dir, "slides", doc.name + "-" + doc.rev + ".pdf")) as f:
|
||||
self.assertEqual(f.read(), content)
|
||||
|
||||
# check that posting same name is prevented
|
||||
test_file.seek(0)
|
||||
|
||||
r = self.client.post(url, dict(title="Test File",
|
||||
name=doc.name,
|
||||
state=State.objects.get(type="slides", slug="active").pk,
|
||||
material=test_file))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
|
||||
def test_change_state(self):
|
||||
doc = self.create_slides()
|
||||
|
||||
url = urlreverse('material_edit', kwargs=dict(name=doc.name, action="state"))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# post
|
||||
r = self.client.post(url, dict(state=State.objects.get(type="slides", slug="deleted").pk))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
doc = Document.objects.get(name=doc.name)
|
||||
self.assertEqual(doc.get_state_slug(), "deleted")
|
||||
|
||||
def test_edit_title(self):
|
||||
doc = self.create_slides()
|
||||
|
||||
url = urlreverse('material_edit', kwargs=dict(name=doc.name, action="title"))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# post
|
||||
r = self.client.post(url, dict(title="New title"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
doc = Document.objects.get(name=doc.name)
|
||||
self.assertEqual(doc.title, "New title")
|
||||
|
||||
def test_revise(self):
|
||||
doc = self.create_slides()
|
||||
|
||||
url = urlreverse('material_edit', kwargs=dict(name=doc.name, action="revise"))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
content = "some text"
|
||||
test_file = StringIO(content)
|
||||
test_file.name = "unnamed.txt"
|
||||
|
||||
# post
|
||||
r = self.client.post(url, dict(title="New title",
|
||||
state=State.objects.get(type="slides", slug="active").pk,
|
||||
material=test_file))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
doc = Document.objects.get(name=doc.name)
|
||||
self.assertEqual(doc.rev, "01")
|
||||
self.assertEqual(doc.title, "New title")
|
||||
self.assertEqual(doc.get_state_slug(), "active")
|
||||
|
||||
with open(os.path.join(self.materials_dir, "slides", doc.name + "-" + doc.rev + ".txt")) as f:
|
||||
self.assertEqual(f.read(), content)
|
||||
|
|
@ -102,4 +102,5 @@ urlpatterns = patterns('',
|
|||
(r'^(?P<name>charter-[A-Za-z0-9._+-]+)/', include('ietf.doc.urls_charter')),
|
||||
(r'^(?P<name>[A-Za-z0-9._+-]+)/conflict-review/', include('ietf.doc.urls_conflict_review')),
|
||||
(r'^(?P<name>[A-Za-z0-9._+-]+)/status-change/', include('ietf.doc.urls_status_change')),
|
||||
(r'^(?P<name>[A-Za-z0-9._+-]+)/material/', include('ietf.doc.urls_material')),
|
||||
)
|
||||
|
|
6
ietf/doc/urls_material.py
Normal file
6
ietf/doc/urls_material.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns('ietf.doc.views_material',
|
||||
url(r'^(?P<action>state|title|revise)/$', "edit_material", name="material_edit"),
|
||||
)
|
||||
|
|
@ -5,6 +5,7 @@ import math
|
|||
|
||||
from django.conf import settings
|
||||
from django.db.models.query import EmptyQuerySet
|
||||
from django.forms import ValidationError
|
||||
|
||||
from ietf.utils import markup_txt
|
||||
from ietf.doc.models import Document, DocHistory
|
||||
|
@ -228,16 +229,13 @@ def add_links_in_new_revision_events(doc, events, diff_revisions):
|
|||
|
||||
|
||||
def get_document_content(key, filename, split=True, markup=True):
|
||||
f = None
|
||||
try:
|
||||
f = open(filename, 'rb')
|
||||
raw_content = f.read()
|
||||
with open(filename, 'rb') as f:
|
||||
raw_content = f.read()
|
||||
except IOError:
|
||||
error = "Error; cannot read ("+key+")"
|
||||
return error
|
||||
finally:
|
||||
if f:
|
||||
f.close()
|
||||
|
||||
if markup:
|
||||
return markup_txt.markup(raw_content, split)
|
||||
else:
|
||||
|
@ -397,3 +395,18 @@ def rebuild_reference_relations(doc):
|
|||
ret['unfound']=list(unfound)
|
||||
|
||||
return ret
|
||||
|
||||
def check_common_doc_name_rules(name):
|
||||
"""Check common rules for document names for use in forms, throws
|
||||
ValidationError in case there's a problem."""
|
||||
|
||||
errors = []
|
||||
if re.search("[^a-z0-9-]", name):
|
||||
errors.append("The name may only contain digits, lowercase letters and dashes.")
|
||||
if re.search("--", name):
|
||||
errors.append("Please do not put more than one hyphen between any two words in the name.")
|
||||
if re.search("-[0-9]{2}$", name):
|
||||
errors.append("This name looks like ends in a version number. -00 will be added automatically. Please adjust the end of the name.")
|
||||
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
|
|
@ -9,6 +9,13 @@ from ietf.person.models import Person
|
|||
from ietf.utils.history import find_history_active_at
|
||||
from ietf.utils.mail import send_mail_text
|
||||
|
||||
def charter_name_for_group(group):
|
||||
if group.type_id == "rg":
|
||||
top_org = "irtf"
|
||||
else:
|
||||
top_org = "ietf"
|
||||
|
||||
return "charter-%s-%s" % (top_org, group.acronym)
|
||||
|
||||
def next_revision(rev):
|
||||
if rev == "":
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import os, datetime, urllib, json
|
||||
import os, datetime, urllib, json, glob
|
||||
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.shortcuts import render_to_response, get_object_or_404, redirect
|
||||
|
@ -51,7 +51,7 @@ from ietf.community.models import CommunityList
|
|||
from ietf.doc.mails import email_ad
|
||||
from ietf.doc.views_status_change import RELATION_SLUGS as status_change_relationships
|
||||
from ietf.group.models import Role
|
||||
from ietf.group.utils import can_manage_group_type
|
||||
from ietf.group.utils import can_manage_group_type, can_manage_materials
|
||||
from ietf.ietfauth.utils import has_role, is_authorized_in_doc_stream, user_is_person, role_required
|
||||
from ietf.name.models import StreamName, BallotPositionName
|
||||
from ietf.person.models import Email
|
||||
|
@ -240,7 +240,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 = "<a href=\"%s\">%s</a>" % (urlreverse("group_docs", kwargs=dict(group_type=doc.group.type_id, acronym=doc.group.acronym)), submission)
|
||||
submission = "<a href=\"%s\">%s</a>" % (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
|
||||
|
||||
|
@ -490,6 +490,48 @@ def document_main(request, name, rev=None):
|
|||
),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
if doc.type_id in ("slides", "agenda", "minutes"):
|
||||
can_manage_material = can_manage_materials(request.user, doc.group)
|
||||
if doc.meeting_related():
|
||||
# disallow editing meeting-related stuff through this
|
||||
# interface for the time being
|
||||
can_manage_material = False
|
||||
basename = doc.canonical_name() # meeting materials are unversioned at the moment
|
||||
if doc.external_url:
|
||||
# we need to remove the extension for the globbing below to work
|
||||
basename = os.path.splitext(doc.external_url)[0]
|
||||
else:
|
||||
basename = "%s-%s" % (doc.canonical_name(), doc.rev)
|
||||
|
||||
pathname = os.path.join(doc.get_file_path(), basename)
|
||||
|
||||
content = None
|
||||
other_types = []
|
||||
globs = glob.glob(pathname + ".*")
|
||||
for g in globs:
|
||||
extension = os.path.splitext(g)[1]
|
||||
t = os.path.splitext(g)[1].lstrip(".")
|
||||
url = doc.href()
|
||||
if not url.endswith("/") and not url.endswith(extension):
|
||||
url += extension
|
||||
|
||||
if extension == ".txt":
|
||||
content = get_document_content(basename, pathname + extension, split=False)
|
||||
t = "plain text"
|
||||
|
||||
other_types.append((t, url))
|
||||
|
||||
return render_to_response("doc/document_material.html",
|
||||
dict(doc=doc,
|
||||
top=top,
|
||||
content=content,
|
||||
revisions=revisions,
|
||||
snapshot=snapshot,
|
||||
can_manage_material=can_manage_material,
|
||||
other_types=other_types,
|
||||
),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
raise Http404
|
||||
|
||||
|
||||
|
|
174
ietf/doc/views_material.py
Normal file
174
ietf/doc/views_material.py
Normal file
|
@ -0,0 +1,174 @@
|
|||
# views for managing group materials (slides, ...)
|
||||
import os
|
||||
import datetime
|
||||
import re
|
||||
|
||||
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.contrib.auth.decorators import login_required
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.doc.models import Document, DocAlias, DocTypeName, DocEvent, State
|
||||
from ietf.doc.models import NewRevisionDocEvent, save_document_in_history
|
||||
from ietf.doc.utils import add_state_change_event, check_common_doc_name_rules
|
||||
from ietf.group.models import Group
|
||||
from ietf.group.utils import can_manage_materials
|
||||
|
||||
@login_required
|
||||
def choose_material_type(request, acronym):
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
if not group.features.has_materials:
|
||||
raise Http404
|
||||
|
||||
return render(request, 'doc/material/choose_material_type.html', {
|
||||
'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)
|
||||
name = forms.CharField(max_length=Document._meta.get_field("name").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, 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")
|
||||
self.fields["state"].initial = self.fields["state"].queryset[0].pk
|
||||
self.fields["name"].initial = u"%s-%s-" % (doc_type.slug, group.acronym)
|
||||
else:
|
||||
del self.fields["name"]
|
||||
|
||||
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 == "title":
|
||||
del self.fields["state"]
|
||||
del self.fields["material"]
|
||||
elif action == "state":
|
||||
del self.fields["title"]
|
||||
del self.fields["material"]
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data["name"].strip().rstrip("-")
|
||||
|
||||
check_common_doc_name_rules(name)
|
||||
|
||||
if not re.search("^%s-%s-[a-z0-9]+" % (self.doc_type.slug, self.group.acronym), name):
|
||||
raise forms.ValidationError("The name must start with %s-%s- followed by descriptive dash-separated words." % (self.doc_type.slug, self.group.acronym))
|
||||
|
||||
existing = Document.objects.filter(type=self.doc_type, name=name)
|
||||
if existing:
|
||||
url = urlreverse("material_edit", kwargs={ 'name': existing[0].name, 'action': 'revise' })
|
||||
raise forms.ValidationError(mark_safe("Can't upload: %s with name %s already exists. Choose another title and name for what you're uploading or <a href=\"%s\">revise the existing %s</a>." % (self.doc_type.name, name, url, name)))
|
||||
|
||||
return name
|
||||
|
||||
@login_required
|
||||
def edit_material(request, name=None, acronym=None, action=None, doc_type=None):
|
||||
# the materials process is not very developed, so at the moment we
|
||||
# handle everything through the same view/form
|
||||
|
||||
if action == "new":
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
if not group.features.has_materials:
|
||||
raise Http404
|
||||
|
||||
doc = None
|
||||
document_type = get_object_or_404(DocTypeName, slug=doc_type)
|
||||
else:
|
||||
doc = get_object_or_404(Document, name=name)
|
||||
group = doc.group
|
||||
document_type = doc.type
|
||||
|
||||
if not can_manage_materials(request.user, group):
|
||||
return HttpResponseForbidden("You don't have permission to access this view")
|
||||
|
||||
if request.method == 'POST':
|
||||
form = UploadMaterialForm(document_type, action, group, doc, request.POST, request.FILES)
|
||||
|
||||
if form.is_valid():
|
||||
if action == "new":
|
||||
doc = Document()
|
||||
doc.type = document_type
|
||||
doc.group = group
|
||||
doc.rev = "00"
|
||||
doc.name = name_for_material(doc.type, doc.group, form.cleaned_data["title"])
|
||||
prev_rev = None
|
||||
else:
|
||||
save_document_in_history(doc)
|
||||
prev_rev = doc.rev
|
||||
|
||||
prev_title = doc.title
|
||||
prev_state = doc.get_state()
|
||||
|
||||
if "title" in form.cleaned_data:
|
||||
doc.title = form.cleaned_data["title"]
|
||||
|
||||
doc.time = datetime.datetime.now()
|
||||
|
||||
if "material" in form.fields:
|
||||
if action != "new":
|
||||
doc.rev = "%02d" % (int(doc.rev) + 1)
|
||||
|
||||
f = form.cleaned_data["material"]
|
||||
file_ext = os.path.splitext(f.name)[1]
|
||||
|
||||
with open(os.path.join(doc.get_file_path(), doc.name + "-" + doc.rev + file_ext), 'wb+') as dest:
|
||||
for chunk in f.chunks():
|
||||
dest.write(chunk)
|
||||
|
||||
doc.save()
|
||||
|
||||
if action == "new":
|
||||
DocAlias.objects.get_or_create(name=doc.name, document=doc)
|
||||
|
||||
if prev_rev != doc.rev:
|
||||
e = NewRevisionDocEvent(type="new_revision", doc=doc, rev=doc.rev)
|
||||
e.time = doc.time
|
||||
e.by = request.user.person
|
||||
e.desc = "New version available: <b>%s-%s</b>" % (doc.name, doc.rev)
|
||||
e.save()
|
||||
|
||||
if prev_title != doc.title:
|
||||
e = DocEvent(doc=doc, by=request.user.person, type='changed_document')
|
||||
e.desc = u"Changed title to <b>%s</b>" % doc.title
|
||||
if prev_title:
|
||||
e.desc += u" from %s" % prev_title
|
||||
e.time = doc.time
|
||||
e.save()
|
||||
|
||||
if "state" in form.cleaned_data and form.cleaned_data["state"] != prev_state:
|
||||
doc.set_state(form.cleaned_data["state"])
|
||||
add_state_change_event(doc, request.user.person, prev_state, form.cleaned_data["state"])
|
||||
|
||||
return redirect("doc_view", name=doc.name)
|
||||
else:
|
||||
form = UploadMaterialForm(document_type, action, group, doc)
|
||||
|
||||
return render(request, 'doc/material/edit_material.html', {
|
||||
'group': group,
|
||||
'form': form,
|
||||
'action': action,
|
||||
'document_type': document_type,
|
||||
'doc_name': doc.name if doc else "",
|
||||
})
|
|
@ -1,24 +1,23 @@
|
|||
# edit/create view for groups
|
||||
|
||||
import re
|
||||
import os
|
||||
import datetime
|
||||
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 HttpResponse, HttpResponseForbidden, Http404, HttpResponseRedirect
|
||||
from django.utils.html import mark_safe
|
||||
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, State
|
||||
from ietf.doc.utils import get_tags_for_stream_id
|
||||
from ietf.doc.utils_charter import charter_name_for_group
|
||||
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 get_group_or_404
|
||||
from ietf.ietfauth.utils import has_role
|
||||
from ietf.person.forms import EmailsField
|
||||
from ietf.person.models import Person, Email
|
||||
|
@ -134,12 +133,7 @@ def format_urls(urls, fs="\n"):
|
|||
return fs.join(res)
|
||||
|
||||
def get_or_create_initial_charter(group, group_type):
|
||||
if group_type == "rg":
|
||||
top_org = "irtf"
|
||||
else:
|
||||
top_org = "ietf"
|
||||
|
||||
charter_name = "charter-%s-%s" % (top_org, group.acronym)
|
||||
charter_name = charter_name_for_group(group)
|
||||
|
||||
try:
|
||||
charter = Document.objects.get(docalias__name=charter_name)
|
||||
|
@ -166,6 +160,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 +185,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():
|
||||
|
@ -233,8 +233,6 @@ def edit(request, group_type=None, acronym=None, action="edit"):
|
|||
changes.append(desc(name, clean[attr], v))
|
||||
setattr(group, attr, clean[attr])
|
||||
|
||||
prev_acronym = group.acronym
|
||||
|
||||
# update the attributes, keeping track of what we're doing
|
||||
diff('name', "Name")
|
||||
diff('acronym', "Acronym")
|
||||
|
@ -245,17 +243,6 @@ def edit(request, group_type=None, acronym=None, action="edit"):
|
|||
diff('list_subscribe', "Mailing list subscribe address")
|
||||
diff('list_archive', "Mailing list archive")
|
||||
|
||||
if not new_group and group.acronym != prev_acronym and group.charter:
|
||||
save_document_in_history(group.charter)
|
||||
DocAlias.objects.get_or_create(
|
||||
name="charter-ietf-%s" % group.acronym,
|
||||
document=group.charter,
|
||||
)
|
||||
old = os.path.join(group.charter.get_file_path(), 'charter-ietf-%s-%s.txt' % (prev_acronym, group.charter.rev))
|
||||
if os.path.exists(old):
|
||||
new = os.path.join(group.charter.get_file_path(), 'charter-ietf-%s-%s.txt' % (group.acronym, group.charter.rev))
|
||||
shutil.copy(old, new)
|
||||
|
||||
# update roles
|
||||
for attr, slug, title in [('chairs', 'chair', "Chairs"), ('secretaries', 'secr', "Secretaries"), ('techadv', 'techadv', "Tech Advisors"), ('delegates', 'delegate', "Delegates")]:
|
||||
new = clean[attr]
|
||||
|
@ -295,7 +282,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,
|
||||
|
@ -328,11 +315,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_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):
|
||||
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':
|
||||
|
@ -347,17 +334,22 @@ 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):
|
||||
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")
|
||||
|
|
25
ietf/group/features.py
Normal file
25
ietf/group/features.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
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 # i.e. drafts/RFCs
|
||||
has_materials = False
|
||||
customize_workflow = False
|
||||
about_page = "group_about"
|
||||
default_tab = about_page
|
||||
material_types = ["slides"]
|
||||
|
||||
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_materials = True
|
||||
|
||||
if self.has_chartering_process:
|
||||
self.about_page = "group_charter"
|
|
@ -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 obj.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 obj.group.about_url()
|
||||
|
||||
def item_pubdate(self, obj):
|
||||
return obj.time
|
||||
|
|
|
@ -35,22 +35,25 @@
|
|||
import os
|
||||
import itertools
|
||||
from tempfile import mkstemp
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.shortcuts import 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
|
||||
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):
|
||||
|
@ -58,10 +61,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 +94,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 +125,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 +159,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 +177,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 +258,63 @@ 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 get_group_materials(group):
|
||||
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."""
|
||||
kwargs = dict(acronym=group.acronym)
|
||||
if group_type:
|
||||
kwargs["group_type"] = group_type
|
||||
|
||||
# menu entries
|
||||
entries = []
|
||||
if group.features.has_documents:
|
||||
entries.append(("Documents", urlreverse("ietf.group.info.group_documents", 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)))
|
||||
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))))
|
||||
if group.features.has_materials and can_manage_materials(request.user, group):
|
||||
actions.append((u"Upload material", urlreverse("ietf.doc.views_material.choose_material_type", 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.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):
|
||||
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 +354,33 @@ 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 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)
|
||||
if not group.features.has_documents:
|
||||
raise Http404
|
||||
|
||||
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)
|
||||
if not group.features.has_documents:
|
||||
raise Http404
|
||||
|
||||
docs, meta, docs_related, meta_related = search_for_group_documents(group)
|
||||
|
||||
|
@ -315,44 +402,53 @@ def group_documents_txt(request, group_type, acronym):
|
|||
|
||||
return HttpResponse(u"\n".join(rows), content_type='text/plain; charset=UTF-8')
|
||||
|
||||
def group_about(request, acronym, group_type=None):
|
||||
group = get_group_or_404(acronym, group_type)
|
||||
|
||||
def group_charter(request, group_type, acronym):
|
||||
group = get_object_or_404(Group, type=group_type, acronym=acronym)
|
||||
|
||||
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",
|
||||
)
|
||||
|
||||
can_manage = can_manage_group_type(request.user, group.type_id)
|
||||
|
||||
return render(request, 'group/group_charter.html',
|
||||
construct_group_menu_context(request, group, "charter", {
|
||||
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,
|
||||
"long_group_type":long_group_types.get(group_type, "Group"),
|
||||
"can_manage": can_manage,
|
||||
}))
|
||||
|
||||
|
||||
def history(request, group_type, acronym):
|
||||
group = get_object_or_404(Group, type=group_type, 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", {
|
||||
"events": events,
|
||||
}))
|
||||
|
||||
|
||||
construct_group_menu_context(request, group, "history", group_type, {
|
||||
"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
|
||||
|
||||
docs = get_group_materials(group).order_by("type__order", "-time").select_related("type")
|
||||
doc_types = OrderedDict()
|
||||
for d in docs:
|
||||
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, {
|
||||
"doc_types": doc_types.items(),
|
||||
"can_manage_materials": can_manage_materials(request.user, group)
|
||||
}))
|
||||
|
||||
def nodename(name):
|
||||
return name.replace('-','_')
|
||||
|
||||
|
@ -470,19 +566,21 @@ def make_dot(group):
|
|||
dict( nodes=nodes, edges=edges )
|
||||
)
|
||||
|
||||
def dependencies_dot(request, group_type, acronym):
|
||||
|
||||
group = get_object_or_404(Group, type=group_type, acronym=acronym)
|
||||
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'
|
||||
)
|
||||
|
||||
@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)
|
||||
if not group.features.has_documents:
|
||||
raise Http404
|
||||
|
||||
group = get_object_or_404(Group, type=group_type, acronym=acronym)
|
||||
|
||||
dothandle,dotname = mkstemp()
|
||||
os.close(dothandle)
|
||||
dotfile = open(dotname,"w")
|
||||
|
|
|
@ -22,7 +22,7 @@ def email_iesg_secretary_re_charter(request, group, subject, text):
|
|||
"group/email_iesg_secretary_re_charter.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)) if group.charter else "[no charter]",
|
||||
)
|
||||
)
|
||||
|
@ -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):
|
||||
|
|
|
@ -0,0 +1,315 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
|
||||
|
||||
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']
|
|
@ -5,14 +5,15 @@ import calendar
|
|||
import json
|
||||
|
||||
from django import forms
|
||||
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest, HttpResponseRedirect, Http404
|
||||
from django.shortcuts import render, redirect
|
||||
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.models import GroupMilestone, MilestoneGroupEvent
|
||||
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
|
||||
|
||||
|
@ -25,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")
|
||||
|
@ -39,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)
|
||||
|
@ -53,7 +56,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,
|
||||
|
@ -108,17 +111,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
|
||||
|
@ -306,7 +311,7 @@ def edit_milestones(request, group_type, acronym, 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))
|
||||
|
@ -332,8 +337,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.")
|
||||
|
|
|
@ -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)
|
||||
|
@ -35,6 +36,21 @@ 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
|
||||
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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
@ -192,6 +192,50 @@ class GroupPagesTests(TestCase):
|
|||
self.assertTrue(milestone.desc in r.content)
|
||||
self.assertTrue(milestone.docs.all()[0].name in r.content)
|
||||
|
||||
def test_group_about(self):
|
||||
make_test_data()
|
||||
group = Group.objects.create(
|
||||
type_id="team",
|
||||
acronym="testteam",
|
||||
name="Test Team",
|
||||
description="The test team is testing.",
|
||||
state_id="active",
|
||||
)
|
||||
|
||||
url = group.about_url()
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(group.name in r.content)
|
||||
self.assertTrue(group.acronym in r.content)
|
||||
self.assertTrue(group.description in r.content)
|
||||
|
||||
def test_materials(self):
|
||||
make_test_data()
|
||||
group = Group.objects.create(type_id="team", acronym="testteam", name="Test Team", state_id="active")
|
||||
|
||||
doc = Document.objects.create(
|
||||
name="slides-testteam-test-slides",
|
||||
rev="00",
|
||||
title="Test Slides",
|
||||
group=group,
|
||||
type_id="slides",
|
||||
)
|
||||
doc.set_state(State.objects.get(type="slides", slug="active"))
|
||||
DocAlias.objects.create(name=doc.name, document=doc)
|
||||
|
||||
url = urlreverse("group_materials", kwargs={ 'acronym': group.acronym })
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(doc.title in r.content)
|
||||
self.assertTrue(doc.name in r.content)
|
||||
|
||||
# try deleting the document and check it's gone
|
||||
doc.set_state(State.objects.get(type="slides", slug="deleted"))
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(doc.title not in r.content)
|
||||
|
||||
def test_history(self):
|
||||
draft = make_test_data()
|
||||
group = draft.group
|
||||
|
|
|
@ -7,6 +7,28 @@ urlpatterns = patterns('',
|
|||
(r'^chartering/$', 'ietf.group.info.chartering_groups'),
|
||||
(r'^chartering/create/(?P<group_type>(wg|rg))/$', 'ietf.group.edit.edit', {'action': "charter"}, "group_create"),
|
||||
(r'^concluded/$', 'ietf.group.info.concluded_groups'),
|
||||
# FIXME: the things below are duplicated in urls_info.py while we
|
||||
# figure out whether to serve everything from /group/<acronym>,
|
||||
# need to unify these at some point
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/$', 'ietf.group.info.group_home', None, "group_home"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/documents/$', 'ietf.group.info.group_documents', None, "group_docs"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/charter/$', 'ietf.group.info.group_about', None, 'group_charter'),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/history/$', 'ietf.group.info.history'),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/deps/dot/$', 'ietf.group.info.dependencies_dot'),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/deps/pdf/$', 'ietf.group.info.dependencies_pdf'),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/init-charter/', 'ietf.group.edit.submit_initial_charter'),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/edit/$', 'ietf.group.edit.edit', {'action': "edit"}, "group_edit"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/conclude/$', 'ietf.group.edit.conclude'),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "current"}, "group_edit_milestones"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/charter/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "charter"}, "group_edit_charter_milestones"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/charter/reset/$', 'ietf.group.milestones.reset_charter_milestones', None, "group_reset_charter_milestones"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/ajax/searchdocs/$', 'ietf.group.milestones.ajax_search_docs', None, "group_ajax_search_docs"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/workflow/$', 'ietf.group.edit.customize_workflow'),
|
||||
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/about/(?P<group_type>.)?$', 'ietf.group.info.group_about', None, 'group_about'),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/materials/$', 'ietf.group.info.materials', None, "group_materials"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/materials/new/$', 'ietf.doc.views_material.choose_material_type'),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/materials/new/(?P<doc_type>[\w-]+)/$', 'ietf.doc.views_material.edit_material', { 'action': "new" }, "group_new_material"),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -19,8 +19,9 @@ urlpatterns = patterns('',
|
|||
(r'^bofs/$', info.bofs),
|
||||
(r'^bofs/create/$', edit.edit, {'action': "create"}, "bof_create"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/documents/txt/$', info.group_documents_txt),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/$', info.group_documents, None, "group_docs"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/charter/$', info.group_charter, None, 'group_charter'),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/$', info.group_home, None, "group_home"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/documents/$', info.group_documents, None, "group_docs"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/charter/$', info.group_about, None, 'group_charter'),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/history/$', info.history),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/deps/dot/$', info.dependencies_dot),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/deps/pdf/$', info.dependencies_pdf),
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import os
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from ietf.group.models import Group, RoleHistory
|
||||
from ietf.person.models import Email
|
||||
from ietf.utils.history import get_history_object_for, copy_many_to_many_for_history
|
||||
|
@ -105,3 +107,13 @@ def milestone_reviewer_for_group_type(group_type):
|
|||
else:
|
||||
return "Area Director"
|
||||
|
||||
def can_manage_materials(user, group):
|
||||
return has_role(user, 'Secretariat') or group.has_role(user, ("chair", "delegate", "secr"))
|
||||
|
||||
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)
|
||||
|
|
204
ietf/name/migrations/0020_sort_role_names.py
Normal file
204
ietf/name/migrations/0020_sort_role_names.py
Normal file
|
@ -0,0 +1,204 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from south.v2 import DataMigration
|
||||
|
||||
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
|
|
@ -24,7 +24,7 @@ from ietf.utils.mail import send_mail
|
|||
ROLODEX_URL = getattr(settings, 'ROLODEX_URL', None)
|
||||
|
||||
|
||||
def get_group_or_404(year):
|
||||
def get_nomcom_group_or_404(year):
|
||||
return get_object_or_404(Group,
|
||||
acronym__icontains=year,
|
||||
state__slug='active',
|
||||
|
@ -121,7 +121,7 @@ class EditMembersFormPreview(FormPreview):
|
|||
@method_decorator(role_required("Nomcom Chair", "Nomcom Advisor"))
|
||||
def __call__(self, request, *args, **kwargs):
|
||||
year = kwargs['year']
|
||||
group = get_group_or_404(year)
|
||||
group = get_nomcom_group_or_404(year)
|
||||
self.state['group'] = group
|
||||
self.state['rolodex_url'] = ROLODEX_URL
|
||||
groups = group.nomcom_set.all()
|
||||
|
@ -225,7 +225,7 @@ class EditChairFormPreview(FormPreview):
|
|||
@method_decorator(role_required("Secretariat"))
|
||||
def __call__(self, request, *args, **kwargs):
|
||||
year = kwargs['year']
|
||||
group = get_group_or_404(year)
|
||||
group = get_nomcom_group_or_404(year)
|
||||
self.state['group'] = group
|
||||
self.state['rolodex_url'] = ROLODEX_URL
|
||||
self.group = group
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -45,7 +45,11 @@
|
|||
<tr><td>Email Address:</td><td>{{ group.list_email }}</td></tr>
|
||||
<tr><td>Email Subscription:</td><td>{{ group.list_subscribe }}</td></tr>
|
||||
<tr><td>Email Archive:</td><td>{{ group.list_archive }}</td></tr>
|
||||
{% if group.features.has_chartering_process %}
|
||||
<tr><td>Charter:</td><td><a href="{% url "groups_charter" acronym=group.acronym %}">View Charter</a></td></tr>
|
||||
{% else %}
|
||||
<tr><td>Description:</td><td>{{ group.description }}</td></tr>
|
||||
{% endif %}
|
||||
<tr><td>Comments:</td><td>{{ group.comments }}</td></tr>
|
||||
<tr><td>Last Modified Date:</td><td>{{ group.time }}</td></tr>
|
||||
</table>
|
||||
|
|
|
@ -256,6 +256,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/'
|
||||
|
@ -276,7 +277,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}/",
|
||||
|
@ -285,6 +285,11 @@ DOC_HREFS = {
|
|||
# who understands this better can take care of it.
|
||||
#"liai-att": None
|
||||
#"liaison": None
|
||||
"slides": 'http://www.ietf.org/slides/{doc.name}-{doc.rev}',
|
||||
}
|
||||
|
||||
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}",
|
||||
}
|
||||
|
|
|
@ -11,21 +11,14 @@
|
|||
{% block content %}
|
||||
{{ top|safe }}
|
||||
|
||||
<div class="snapshots">
|
||||
Snapshots:
|
||||
<span class="revisions">
|
||||
{% for rev in revisions %}
|
||||
<a {% if rev != doc.rev %}href="{% url "doc_view" name=doc.name %}{% if not forloop.last %}{{ rev }}/{% endif %}"{% endif %}>{{ rev }}</a>
|
||||
{% endfor %}
|
||||
</span>
|
||||
</div>
|
||||
{% include "doc/revisions_list.html" %}
|
||||
|
||||
<div class="ietf-box metabox">
|
||||
<div>
|
||||
{% if snapshot %}Snapshot of{% endif %}
|
||||
{% if doc.get_state_slug != "approved" %}Proposed{% endif %}
|
||||
Charter for "{{ group.name }}"
|
||||
(<a {% if group.type_id == "wg" or group.type_id == "rg" %}href="{% url "ietf.group.info.group_charter" group_type=group.type_id acronym=group.acronym %}"{% endif %}>{{ group.acronym }}</a>) {{ group.type.name }}
|
||||
(<a href="{{ group.about_url }}">{{ group.acronym }}</a>) {{ group.type.name }}
|
||||
</div>
|
||||
|
||||
<table id="metatable" width="100%">
|
||||
|
|
|
@ -11,14 +11,7 @@
|
|||
{% block content %}
|
||||
{{ top|safe }}
|
||||
|
||||
<div class="snapshots">
|
||||
Versions:
|
||||
<span class="revisions">
|
||||
{% for rev in revisions %}
|
||||
<a {% if rev != doc.rev %}href="{% url "doc_view" name=doc.name %}{% if not forloop.last %}{{ rev }}/{% endif %}"{% endif %}>{{ rev }}</a>
|
||||
{% endfor %}
|
||||
</span>
|
||||
</div>
|
||||
{% include "doc/revisions_list.html" %}
|
||||
|
||||
<div class="ietf-box metabox">
|
||||
<div>
|
||||
|
|
76
ietf/templates/doc/document_material.html
Normal file
76
ietf/templates/doc/document_material.html
Normal file
|
@ -0,0 +1,76 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% block title %}{{ doc.canonical_name }}-{{ doc.rev }}{% endblock %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" type="text/css" href="/css/doc.css"></link>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ top|safe }}
|
||||
|
||||
{% include "doc/revisions_list.html" %}
|
||||
|
||||
<div class="ietf-box metabox">
|
||||
<div>
|
||||
{% if snapshot %}Snapshot of{% endif %} {% if doc.meeting_related %}Meeting{% endif %} {{ doc.type.name }} for <a href="{{ doc.group.about_url }}">{{ doc.group.acronym }}</a> group
|
||||
</div>
|
||||
|
||||
<table id="metatable" width="100%">
|
||||
<tr>
|
||||
<td>Title:</td>
|
||||
<td>
|
||||
<a {% if not snapshot and can_manage_material %} class="editlink" href="{% url "material_edit" name=doc.name action="title" name=doc.name %}"{% endif %}>{{ doc.title }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>State:</td>
|
||||
<td>
|
||||
<a title="{{ doc.get_state.desc }}"{% if not snapshot and can_manage_material %} class="editlink" href="{% url "material_edit" name=doc.name action="state" %}"{% endif %}>{{ doc.get_state.name }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% if other_types %}
|
||||
<tr>
|
||||
<td>Other versions:</td>
|
||||
<td>
|
||||
{% for t, url in other_types %}
|
||||
<a href="{{ url }}">{{ t }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
<tr>
|
||||
<td>Last updated:</td>
|
||||
<td>{{ doc.time|date:"Y-m-d" }}</td>
|
||||
</tr>
|
||||
|
||||
{% if not snapshot and can_manage_material %}
|
||||
<tr><td colspan="2">
|
||||
<a class="button" href="{% url "material_edit" name=doc.name action="revise" %}">Upload New Revision</a>
|
||||
</td><tr/>
|
||||
{% endif %}
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if doc.rev and content != None %}
|
||||
<h3>{{ doc.title }}</h3>
|
||||
|
||||
<div class="markup_draft">
|
||||
{{ content|fill:"80"|safe|linebreaksbr|keep_spacing|sanitize_html|safe }}
|
||||
</div>
|
||||
{% else %}
|
||||
<p>Not available as plain text.</p>
|
||||
|
||||
{% if other_types %}
|
||||
<p class="download-instead"><a href="{{ other_types.0.1 }}">Download as {{ other_types.0.0.upper }}</a></p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -11,14 +11,7 @@
|
|||
{% block content %}
|
||||
{{ top|safe }}
|
||||
|
||||
<div class="snapshots">
|
||||
Versions:
|
||||
<span class="revisions">
|
||||
{% for rev in revisions %}
|
||||
<a {% if rev != doc.rev %}href="{% url "doc_view" name=doc.name %}{% if not forloop.last %}{{ rev }}/{% endif %}"{% endif %}>{{ rev }}</a>
|
||||
{% endfor %}
|
||||
</span>
|
||||
</div>
|
||||
{% include "doc/revisions_list.html" %}
|
||||
|
||||
<div class="ietf-box metabox">
|
||||
<div>
|
||||
|
|
23
ietf/templates/doc/material/choose_material_type.html
Normal file
23
ietf/templates/doc/material/choose_material_type.html
Normal file
|
@ -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 %}
|
||||
|
||||
<h1>Upload Material for Group {{ group.acronym }}</h1>
|
||||
|
||||
<p>Select what kind of material you wish to upload:</p>
|
||||
|
||||
<ul class="material-types">
|
||||
{% for t in material_types %}
|
||||
<li><a href="{% url "group_new_material" acronym=group.acronym doc_type=t.slug %}">{{ t.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% endblock content %}
|
58
ietf/templates/doc/material/edit_material.html
Normal file
58
ietf/templates/doc/material/edit_material.html
Normal file
|
@ -0,0 +1,58 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{% if action == "new" or action == "revise" %}Upload{% else %}Edit{% endif %} {{ document_type.name }} for Group {{ group.acronym }}{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
{{ block.super }}
|
||||
form.upload-material td { padding-bottom: 0.6em; }
|
||||
form.upload-material #id_title, form.upload-material #id_name { width: 30em; }
|
||||
form.upload-material .submit-row td { padding-top: 1em; text-align: right; }
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% load ietf_filters %}
|
||||
|
||||
<h1>{% if action == "new" or action == "revise" %}Upload{% else %}Edit{% endif %} {{ document_type.name }} for Group {{ group.acronym }}</h1>
|
||||
|
||||
{% if action == "new" %}
|
||||
<p>
|
||||
Below you can upload a file for the group
|
||||
<a href="{% url "group_materials" acronym=group.acronym %}">{{ group.acronym }}</a>.
|
||||
The file will appear under the materials tab in the group pages.
|
||||
</p>
|
||||
|
||||
<h3>Upload</h3>
|
||||
{% elif action == "revise" %}
|
||||
<p>
|
||||
Below you can upload a new revision of {{ doc_name }} for the group
|
||||
<a href="{% url "group_materials" acronym=group.acronym %}">{{ group.acronym }}</a>.
|
||||
</p>
|
||||
|
||||
<h3>Upload New Revision</h3>
|
||||
{% endif %}
|
||||
|
||||
<form class="upload-material" method="post" enctype="multipart/form-data" data-nameprefix="{{ document_type.slug }}-{{ group.acronym }}-">{% csrf_token %}
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
|
||||
<tr class="submit-row">
|
||||
<td colspan="2">
|
||||
<a class="button" href="{% if doc_name %}{% url "doc_view" name=doc_name %}{% else %}{% url "group_materials" acronym=group.acronym %}{% endif %}">Cancel</a>
|
||||
<input class="submit button" type="submit" value="{% if action == "new" or action == "revise" %}Upload{% else %}Save{% endif %}" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block scripts %}
|
||||
jQuery(document).ready(function () {
|
||||
jQuery("form.upload-material input#id_title").on("change keyup", function () {
|
||||
var v = jQuery(this).val();
|
||||
var slug = jQuery(this).parents("form").data("nameprefix");
|
||||
slug += v.toLowerCase().replace(/ /g,'-').replace(/[-]+/g, '-').replace(/[^a-z-]+/g,'');
|
||||
jQuery(this).parents("form").find("input#id_name").val(slug);
|
||||
});
|
||||
});
|
||||
{% endblock %}
|
8
ietf/templates/doc/revisions_list.html
Normal file
8
ietf/templates/doc/revisions_list.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<div class="snapshots">
|
||||
Versions:
|
||||
<span class="revisions">
|
||||
{% for rev in revisions %}
|
||||
<a {% if rev != doc.rev %}href="{% url "doc_view" name=doc.name %}{% if not forloop.last %}{{ rev }}/{% endif %}"{% endif %}>{{ rev }}</a>
|
||||
{% endfor %}
|
||||
</span>
|
||||
</div>
|
|
@ -22,7 +22,7 @@
|
|||
<table class="ietf-wg-table">
|
||||
{% for group in groups %}
|
||||
<tr>
|
||||
<td width="10%;"><a href="{% url "ietf.group.info.group_documents" group_type=group.type_id acronym=group.acronym %}">{{ group.acronym }}</a></td>
|
||||
<td width="10%;"><a href="{% url "ietf.group.info.group_home" group_type=group.type_id acronym=group.acronym %}">{{ group.acronym }}</a></td>
|
||||
<td width="50%">{{ group.name }}</td>
|
||||
<td width="40%">{% for chair in group.chairs %}<a href="mailto:{{ chair.email.address }}">{{ chair.person.plain_name }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</td>
|
||||
</tr>
|
||||
|
|
|
@ -80,7 +80,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
<table class="ietf-wg-table">
|
||||
{% for group in area.groups %}
|
||||
<tr>
|
||||
<td width="10%;"><a href="{% url "ietf.group.info.group_documents" group_type=group.type_id acronym=group.acronym %}">{{ group.acronym }}</a></td>
|
||||
<td width="10%;"><a href="{% url "ietf.group.info.group_home" group_type=group.type_id acronym=group.acronym %}">{{ group.acronym }}</a></td>
|
||||
<td width="1%">{% for ad in area.ads %}{% if ad.person_id == group.ad_id %}<span title="AD for {{ group.acronym }}: {{ ad.person }}" class="square bgcolor{{forloop.counter}}"></span>{% endif %}{% endfor %}</td>
|
||||
<td width="50%">{{ group.name }}</td>
|
||||
<td width="39%">{% for chair in group.chairs %}<a href="mailto:{{ chair.email.address }}">{{ chair.person.plain_name }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</td>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
{% for g in groups %}
|
||||
<tr class="{{ forloop.counter|divisibleby:2|yesno:"oddrow,evenrow" }}">
|
||||
<td class="acronym">
|
||||
<a href="{% url "group_charter" group_type=g.type_id acronym=g.acronym %}">{{ g.acronym }}</a>
|
||||
<a href="{{ g.about_url }}">{{ g.acronym }}</a>
|
||||
</td>
|
||||
<td class="title">
|
||||
<a {%comment%}href="{% url "doc_view" name=g.charter.name %}"{%endcomment%}>{{ g.name }}</a>
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
{% for g in t.chartering_groups %}
|
||||
<tr class="{{ forloop.counter|divisibleby:2|yesno:"oddrow,evenrow" }}">
|
||||
<td class="acronym">
|
||||
<a href="{% url "group_charter" group_type=g.type_id acronym=g.acronym %}">{{ g.acronym }}</a>
|
||||
<a href="{{ g.about_url }}">{{ g.acronym }}</a>
|
||||
</td>
|
||||
<td class="title">
|
||||
<a href="{% url "doc_view" name=g.charter.name %}">{{ g.name }}</a>
|
||||
|
|
|
@ -29,7 +29,7 @@ form.conclude .actions {
|
|||
{{ form.as_table }}
|
||||
<tr>
|
||||
<td colspan="2" class="actions">
|
||||
<a class="button" href="{% url "group_charter" group_type=group.type_id acronym=group.acronym %}">Cancel</a>
|
||||
<a class="button" href="{{ group.about_url }}">Cancel</a>
|
||||
<input class="button" type="submit" value="Send request"/>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
{% for g in t.concluded_groups %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url "group_charter" group_type=g.type_id acronym=g.acronym %}">{{ g.acronym }}</a>
|
||||
<a href="{{ group.about_url }}">{{ g.acronym }}</a>
|
||||
</td>
|
||||
<td>{{ g.name }}
|
||||
<span class="active-period">({% if g.start_date %}{{ g.start_date|date:"M. Y" }}{% else %}?{% endif %}
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<h1>Customize Workflow for {{ group.acronym }} {{ group.type.name }}</h1>
|
||||
|
||||
<p>Below you can customize the draft states and tags used in the
|
||||
<a href="{% url "group_charter" group_type=group.type_id acronym=group.acronym %}">{{ group.acronym }} {{ group.type.name }}</a>. Note that some states are
|
||||
<a href="{{ group.about_url }}">{{ group.acronym }} {{ group.type.name }}</a>. Note that some states are
|
||||
mandatory for group operation and cannot be deactivated.</p>
|
||||
|
||||
{% if group.type_id == "wg" %}
|
||||
|
|
|
@ -69,7 +69,7 @@ so. New accounts can be <a href="{% url "create_account" %}">created here</a>.</
|
|||
<td></td>
|
||||
<td class="actions">
|
||||
{% if action == "edit" %}
|
||||
<a class="button" href="{% url "group_charter" group_type=group.type_id acronym=group.acronym %}">Cancel</a>
|
||||
<a class="button" href="{{ group.about_url }}">Cancel</a>
|
||||
<input class="button" type="submit" value="Save"/>
|
||||
{% else %}
|
||||
{% if action == "charter" %}
|
||||
|
|
|
@ -42,7 +42,7 @@ tr.milestone.add { font-style: italic; }
|
|||
<noscript>This page depends on Javascript being enabled to work properly.</noscript>
|
||||
|
||||
<p>Links:
|
||||
<a href="{% url "group_charter" group_type=group.type_id acronym=group.acronym %}">{{ group.acronym }} {{ group.type.name }}</a>
|
||||
<a href="{{ g.about_url }}">{{ group.acronym }} {{ group.type.name }}</a>
|
||||
{% if group.charter %}
|
||||
- <a href="{% url "doc_view" name=group.charter.canonical_name %}">{{ group.charter.canonical_name }}</a>
|
||||
{% endif %}
|
||||
|
@ -92,7 +92,7 @@ this list</a> to the milestones currently in use for the {{ group.acronym }} {{
|
|||
</table>
|
||||
|
||||
<div class="actions">
|
||||
<a class="button" href="{% if milestone_set == "charter" %}{% url "doc_view" name=group.charter.canonical_name %}{% else %}{% url "group_charter" group_type=group.type_id acronym=group.acronym %}{% endif %}">Cancel</a>
|
||||
<a class="button" href="{% if milestone_set == "charter" %}{% url "doc_view" name=group.charter.canonical_name %}{% else %}{{ group.about_url }}{% endif %}">Cancel</a>
|
||||
<input class="button" type="submit" data-labelsave="Save" data-labelreview="Review changes" value="Save" style="display:none"/>
|
||||
<input type="hidden" name="action" value="save">
|
||||
</div>
|
||||
|
|
127
ietf/templates/group/group_about.html
Normal file
127
ietf/templates/group/group_about.html
Normal file
|
@ -0,0 +1,127 @@
|
|||
{% extends "group/group_base.html" %}
|
||||
|
||||
{% load ietf_filters %}
|
||||
{% block group_subtitle %}Charter{% endblock %}
|
||||
|
||||
{% block group_content %}
|
||||
<div class="ietf-box ietf-group-details">
|
||||
|
||||
{% if group.state_id == "conclude" %}
|
||||
<span class="ietf-concluded-warning">Note: The data for concluded {{ group.type.name }}s
|
||||
is occasionally incorrect.</span>
|
||||
{% endif %}
|
||||
|
||||
<table>
|
||||
<tr><th colspan="2">Group</th></tr>
|
||||
|
||||
<tr valign="top">
|
||||
<td style="width:14ex;">Name:</td>
|
||||
<td>{{ group.name }}</td>
|
||||
</tr>
|
||||
|
||||
<tr><td>Acronym:</td><td>{{ group.acronym }}</td></tr>
|
||||
|
||||
{% if group.parent and group.parent.type_id == "area" %}
|
||||
<tr><td>{{ group.parent.type.name }}:</td><td>{{ group.parent.name }} ({{ group.parent.acronym }})</td></tr>
|
||||
{% endif %}
|
||||
|
||||
<tr>
|
||||
<td>State:</td>
|
||||
<td>{{ group.state.name }}
|
||||
{% if requested_close %}
|
||||
(but in the process of being closed)
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% if group.features.has_chartering_process %}
|
||||
<tr>
|
||||
<td>Charter:</td>
|
||||
<td>
|
||||
{% if group.charter %}
|
||||
<a href="{% url "doc_view" name=group.charter.name %}">{{ group.charter.name }}-{{ group.charter.rev }}</a> ({{ group.charter.get_state.name }})
|
||||
{% else %}
|
||||
none
|
||||
{% if can_manage %}
|
||||
- <a href="{% url "ietf.group.edit.submit_initial_charter" group_type=group.type_id acronym=group.acronym %}">Submit Charter</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
{% with group.groupurl_set.all as urls %}
|
||||
{% if urls %}
|
||||
<tr>
|
||||
<td>More info:</td>
|
||||
<td>
|
||||
{% for url in urls %}
|
||||
<a href="{{ url.url }}">{{ url.name }}</a>{% if not forloop.last %}<br>{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<tr><th colspan="2">Personnel</th></tr>
|
||||
|
||||
{% for slug, label, roles in group.personnel %}
|
||||
<tr valign="top">
|
||||
<td>{{ label }}:</td>
|
||||
<td>
|
||||
{% for r in roles %}
|
||||
<a href="mailto:{{ r.email.address }}">{{ r.person.plain_name }} <{{ r.email.address }}></a><br/>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{% if group.list_email %}
|
||||
<tr><th colspan="2">Mailing List</th></tr>
|
||||
|
||||
<tr><td>Address:</td><td>{{ group.list_email|urlize }}</td></tr>
|
||||
<tr><td>To Subscribe:</td><td>{{ group.list_subscribe|urlize }}</td></tr>
|
||||
<tr><td>Archive:</td><td>{{ group.list_archive|urlize }}</td></tr>
|
||||
{% endif %}
|
||||
|
||||
{% if group.state_id != "conclude" %}
|
||||
<tr><th colspan="2">Jabber Chat</th></tr>
|
||||
|
||||
<tr>
|
||||
<td>Room Address:</td>
|
||||
<td><a href="xmpp:{{ group.acronym }}@jabber.ietf.org">xmpp:{{ group.acronym }}@jabber.ietf.org</a></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Logs:</td>
|
||||
<td><a href="http://jabber.ietf.org/logs/{{ group.acronym }}/">http://jabber.ietf.org/logs/{{ group.acronym }}/</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
{% if group.features.has_chartering_process %}
|
||||
<h2>Charter for {% if group.state_id == "proposed" %}Proposed{% endif %} {{ group.type.desc.title }}</h2>
|
||||
|
||||
<p>{{ group.charter_text|linebreaks }}</p>
|
||||
{% else %}
|
||||
<h2>About</h2>
|
||||
|
||||
<p>{{ group.description|default:"No description yet."|linebreaks }}</p>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if group.features.has_milestones %}
|
||||
<h2>{% if group.state_id == "proposed" %}Proposed{% endif %} Milestones</h2>
|
||||
|
||||
{% include "group/milestones.html" with milestones=group.milestones %}
|
||||
|
||||
{% if milestones_in_review %}
|
||||
<p>+ {{ milestones_in_review|length }} new milestone{{ milestones_in_review|pluralize }}
|
||||
currently in {{ milestone_reviewer }} review.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -66,16 +66,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
<div class="ietf-navset">
|
||||
<div>
|
||||
<a {% if selected == "documents" %}class="selected"{% else %}href="{% url "ietf.group.info.group_documents" group_type=group.type_id acronym=group.acronym %}"{% endif %}>Documents</a> |
|
||||
<a {% if selected == "charter" %}class="selected"{% else %}href="{% url "ietf.group.info.group_charter" group_type=group.type_id acronym=group.acronym %}"{% endif %}>Charter</a> |
|
||||
<a {% if selected == "history" %}class="selected"{% else %}href="{% url "ietf.group.info.history" group_type=group.type_id acronym=group.acronym %}"{% endif %}>History</a>
|
||||
| <a href="{% url 'ietf.group.info.dependencies_pdf' group_type=group.type_id acronym=group.acronym %}">Dependency Graph</a>
|
||||
{% if group.list_archive|startswith:"http:" or group.list_archive|startswith:"https:" or group.list_archive|startswith:"ftp:" %}
|
||||
| <a href="{{ group.list_archive }}">List Archive »</a>
|
||||
{% endif %}
|
||||
{% if group.has_tools_page %}
|
||||
| <a href="http://tools.ietf.org/{{ group.type_id }}/{{ group.acronym }}/">Tools {{ group.type.name }} Page »</a>
|
||||
{% endif %}
|
||||
{% for name, url in menu_entries %}
|
||||
<a {% if selected_menu_entry == name.lower %}class="selected"{% else %}href="{{ url }}"{% endif %}>{{ name }}</a> {% if not forloop.last %} | {% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if menu_actions %}
|
||||
|
|
|
@ -1,168 +0,0 @@
|
|||
{% extends "group/group_base.html" %}
|
||||
|
||||
{% 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 %}
|
||||
<div class="ietf-box ietf-group-details">
|
||||
|
||||
{% if group.state_id == "conclude" %}
|
||||
<span class="ietf-concluded-warning">Note: The data for concluded {{ group.type.name }}s
|
||||
is occasionally incorrect.</span>
|
||||
{% endif %}
|
||||
|
||||
<table>
|
||||
<tr><th colspan="2">Group</th></tr>
|
||||
|
||||
<tr valign="top">
|
||||
<td style="width:14ex;">Name:</td>
|
||||
<td>{{ group.name }}</td>
|
||||
</tr>
|
||||
|
||||
<tr><td>Acronym:</td><td>{{ group.acronym }}</td></tr>
|
||||
|
||||
{% if group.parent and group.parent.type_id == "area" %}
|
||||
<tr><td>{{ group.parent.type.name }}:</td><td>{{ group.parent.name }} ({{ group.parent.acronym }})</td></tr>
|
||||
{% endif %}
|
||||
|
||||
<tr>
|
||||
<td>State:</td>
|
||||
<td>{{ group.state.name }}
|
||||
{% if requested_close %}
|
||||
(but in the process of being closed)
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Charter:</td>
|
||||
<td>
|
||||
{% if group.charter %}
|
||||
<a href="{% url "doc_view" name=group.charter.name %}">{{ group.charter.name }}-{{ group.charter.rev }}</a> ({{ group.charter.get_state.name }})
|
||||
{% else %}
|
||||
none
|
||||
{% if can_manage %}
|
||||
- <a href="{% url "ietf.group.edit.submit_initial_charter" group_type=group.type_id acronym=group.acronym %}">Submit Charter</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr><th colspan="2">Personnel</th></tr>
|
||||
|
||||
<tr valign="top">
|
||||
<td>Chair{{ group.chairs|pluralize }}:</td>
|
||||
<td>
|
||||
{% for chair in group.chairs %}
|
||||
<a href="mailto:{{ chair.email.address }}">{{ chair.person.plain_name }} <{{ chair.email.address }}></a><br/>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% if group.parent.type_id == "area" %}
|
||||
<tr><td>Area Director:</td>
|
||||
<td>
|
||||
{% if group.areadirector %}
|
||||
<a href="mailto:{{ group.areadirector.address }}">{{ group.areadirector.person.plain_name }} <{{ group.areadirector.address }}></a>
|
||||
{% else %}?{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
{% if group.techadvisors %}
|
||||
<tr>
|
||||
<td>Tech Advisor{{ group.techadvisors|pluralize }}:</td>
|
||||
<td>
|
||||
{% for techadvisor in group.techadvisors %}
|
||||
<a href="mailto:{{ techadvisor.email.address }}">{{ techadvisor.person.plain_name }} <{{ techadvisor.email.address }}></a><br/>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
{% if group.editors %}
|
||||
<tr>
|
||||
<td>Editor{{ group.editors|pluralize }}:</td>
|
||||
<td>
|
||||
{% for editor in group.editors %}
|
||||
<a href="mailto:{{ editor.email.address }}">{{ editor.person.plain_name }} <{{ editor.email.address }}></a><br/>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
{% if group.secretaries %}
|
||||
<tr>
|
||||
<td>Secretar{{ group.secretaries|pluralize:"y,ies" }}:</td>
|
||||
<td>
|
||||
{% for secretary in group.secretaries %}
|
||||
<a href="mailto:{{ secretary.email.address }}">{{ secretary.person.plain_name }} <{{ secretary.email.address }}></a><br/>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
{% if group.delegates %}
|
||||
<tr>
|
||||
<td>Delegate{{ group.delegates|pluralize }}:</td>
|
||||
<td>
|
||||
{% for delegate in group.delegates %}
|
||||
<a href="mailto:{{ delegate.email.address }}">{{ delegate.person.plain_name }} <{{ delegate.email.address }}></a><br/>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
<tr><th colspan="2">Mailing List</th></tr>
|
||||
|
||||
<tr><td>Address:</td><td>{{ group.list_email|urlize }}</td></tr>
|
||||
<tr><td>To Subscribe:</td><td>{{ group.list_subscribe|urlize }}</td></tr>
|
||||
<tr><td>Archive:</td><td>{{ group.list_archive|urlize }}</td></tr>
|
||||
|
||||
{% if group.state_id != "conclude" %}
|
||||
<tr><th colspan="2">Jabber Chat</th></tr>
|
||||
|
||||
<tr>
|
||||
<td>Room Address:</td>
|
||||
<td><a href="xmpp:{{ group.acronym }}@jabber.ietf.org">xmpp:{{ group.acronym }}@jabber.ietf.org</a></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Logs:</td>
|
||||
<td><a href="http://jabber.ietf.org/logs/{{ group.acronym }}/">http://jabber.ietf.org/logs/{{ group.acronym }}/</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
{% with group.groupurl_set.all as urls %}
|
||||
{% if urls %}
|
||||
<p>In addition to the charter, there is additional information about this group on the Web at:
|
||||
{% for url in urls %}
|
||||
<a href="{{ url.url }}">{{ url.name }}</a>{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<h2>Charter for {% if group.state_id == "proposed" %}Proposed{% endif %} {{ long_group_type }}</h2>
|
||||
|
||||
<p>{{ group.charter_text|escape|format_charter|safe }}</p>
|
||||
|
||||
<h2>{% if group.state_id == "proposed" %}Proposed{% endif %} Milestones</h2>
|
||||
|
||||
{% include "group/milestones.html" with milestones=group.milestones %}
|
||||
|
||||
{% if milestones_in_review %}
|
||||
<p>+ {{ milestones_in_review|length }} new milestone{{ milestones_in_review|pluralize }}
|
||||
currently in {{ milestone_reviewer }} review.</p>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -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 }}>
|
||||
|
|
41
ietf/templates/group/materials.html
Normal file
41
ietf/templates/group/materials.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
{% extends "group/group_base.html" %}
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% block group_subtitle %}Materials{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
{{ block.super }}
|
||||
.materials .edit-options { float: right; margin-left: 2em; font-style: italic; }
|
||||
{% endblock %}
|
||||
|
||||
{% block group_content %}
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% if doc_types %}
|
||||
{% for doc_type, docs in doc_types %}
|
||||
<h2>{{ doc_type.name }}</h2>
|
||||
|
||||
<table class="ietf-table ietf-doctable materials">
|
||||
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Rev.</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
|
||||
{% for d in docs %}
|
||||
<tr class="{% cycle "evenrow" "oddrow" %}">
|
||||
<td><a class="title-link" href="{% url "doc_view" name=d.name %}">{{ d.title }}</a></td>
|
||||
<td>{{ d.rev }}</td>
|
||||
<td>{{ d.time|date:"Y-m-d" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<h2>Materials</h2>
|
||||
|
||||
<p>No materials uploaded.</p>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -15,6 +15,7 @@ from ietf.utils.mail import send_mail_text, send_mail_mime, outbox
|
|||
class PyFlakesTestCase(TestCase):
|
||||
|
||||
def test_pyflakes(self):
|
||||
self.maxDiff = None
|
||||
path = os.path.join(settings.BASE_DIR)
|
||||
warnings = []
|
||||
warnings = pyflakes.checkPaths([path], verbosity=0)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -46,3 +46,5 @@ h3 a.edit { font-weight: normal; font-size: 13px; display: inline-block; margin-
|
|||
|
||||
h4 { margin-bottom: 0; }
|
||||
h4 + p { margin-top: 0; max-width: 400px; }
|
||||
|
||||
p.download-instead a { font-size: 20px; font-weight: bold; color: #2647a0; }
|
||||
|
|
Loading…
Reference in a new issue