Merged in support for RG pages, from branch/iola/rg-support.

- Legacy-Id: 7866
This commit is contained in:
Henrik Levkowetz 2014-06-09 20:52:14 +00:00
commit 9ff2b9ff9a
64 changed files with 831 additions and 588 deletions

View file

@ -1,3 +1,14 @@
ietfdb (5.5.0) ietf; urgency=medium
This is a feature release, which introduces pages and workflow support for
IRTF RGs, similar to what is available for IETF WGs. You'll find the rg pages
under /rg/, for instance https://datatracker.ietf.org/rg/cfrg . Having this
new baseline in place, I'm sure we'll get requests for refinements, but
that's the name of the game :-)
-- Henrik Levkowetz <henrik@levkowetz.com> 03 Jun 2014 23:12:44 -0200
ietfdb (5.4.3) ietf; urgency=medium
* Merged in [7772] from rjsparks@nostrum.com:

View file

@ -11,7 +11,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ietf.settings")
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0)
from ietf.wginfo.mails import *
from ietf.group.mails import *
today = datetime.date.today()

View file

@ -88,7 +88,7 @@ class WGField(DisplayField):
if raw:
return document.group.acronym
else:
return '<a href="%s">%s</a>' % (urlreverse('wg_docs', kwargs={'acronym':document.group.acronym}), document.group.acronym) if (document.group and document.group.acronym != 'none') 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 ''
class ADField(DisplayField):

View file

@ -270,8 +270,12 @@ class Document(DocumentInfo):
a = self.docalias_set.filter(name__startswith="rfc")
if a:
name = a[0].name
elif self.type_id == "charter":
return "charter-ietf-%s" % self.chartered_group.acronym
# 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)
return name
def canonical_docalias(self):

View file

@ -1,6 +1,6 @@
import os, datetime, shutil, textwrap, json
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound, Http404
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound, HttpResponseForbidden, Http404
from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.core.urlresolvers import reverse as urlreverse
from django.template import RequestContext
@ -9,6 +9,7 @@ from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
import debug # pyflakes:ignore
@ -21,7 +22,7 @@ from ietf.doc.utils_charter import ( historic_milestones_for_charter,
approved_revision, default_review_text, default_action_text, email_state_changed,
generate_ballot_writeup, generate_issue_ballot_mail, next_approved_revision, next_revision )
from ietf.group.models import ChangeStateGroupEvent, MilestoneGroupEvent
from ietf.group.utils import save_group_in_history, save_milestone_in_history
from ietf.group.utils import save_group_in_history, save_milestone_in_history, can_manage_group_type
from ietf.iesg.models import TelechatDate
from ietf.ietfauth.utils import has_role, role_required
from ietf.name.models import GroupStateName
@ -29,29 +30,38 @@ from ietf.person.models import Person
from ietf.utils.history import find_history_active_at
from ietf.utils.mail import send_mail_preformatted
from ietf.utils.textupload import get_cleaned_text_file_content
from ietf.wginfo.mails import email_secretariat
from ietf.group.mails import email_secretariat
class ChangeStateForm(forms.Form):
charter_state = forms.ModelChoiceField(State.objects.filter(used=True, type="charter", slug__in=["infrev", "intrev", "extrev", "iesgrev"]), label="Charter state", empty_label=None, required=False)
charter_state = forms.ModelChoiceField(State.objects.filter(used=True, type="charter"), label="Charter state", empty_label=None, required=False)
initial_time = forms.IntegerField(initial=0, label="Review time", help_text="(in weeks)", required=False)
message = forms.CharField(widget=forms.Textarea, help_text="Leave blank to change state without notifying the Secretariat", required=False, label=mark_safe("Message to<br> Secretariat"))
comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the charter history", required=False)
def __init__(self, *args, **kwargs):
self.hide = kwargs.pop('hide', None)
group = kwargs.pop('group')
super(ChangeStateForm, self).__init__(*args, **kwargs)
state_field = self.fields["charter_state"]
if group.type_id == "wg":
state_field.queryset = state_field.queryset.filter(slug__in=("infrev", "intrev", "extrev", "iesgrev"))
else:
state_field.queryset = state_field.queryset.filter(slug__in=("intrev", "extrev", "approved"))
# hide requested fields
if self.hide:
for f in self.hide:
self.fields[f].widget = forms.HiddenInput
@role_required("Area Director", "Secretariat")
@login_required
def change_state(request, name, option=None):
"""Change state of charter, notifying parties as necessary and
logging the change as a comment."""
charter = get_object_or_404(Document, type="charter", name=name)
group = charter.group
if not can_manage_group_type(request.user, group.type_id):
return HttpResponseForbidden("You don't have permission to access this view")
chartering_type = get_chartering_type(charter)
initial_review = charter.latest_event(InitialReviewDocEvent, type="initial_review")
@ -61,13 +71,17 @@ def change_state(request, name, option=None):
login = request.user.person
if request.method == 'POST':
form = ChangeStateForm(request.POST)
form = ChangeStateForm(request.POST, group=group)
if form.is_valid():
clean = form.cleaned_data
charter_rev = charter.rev
if option in ("initcharter", "recharter"):
charter_state = State.objects.get(used=True, type="charter", slug="infrev")
if group.type_id == "wg":
charter_state = State.objects.get(used=True, type="charter", slug="infrev")
else:
charter_state = clean['charter_state']
# make sure we have the latest revision set, if we
# abandoned a charter before, we could have reset the
# revision to latest approved
@ -133,7 +147,7 @@ def change_state(request, name, option=None):
email_state_changed(request, charter, "State changed to %s." % charter_state)
if charter_state.slug == "intrev":
if charter_state.slug == "intrev" and group.type_id == "wg":
if request.POST.get("ballot_wo_extern"):
create_ballot_if_not_open(charter, login, "r-wo-ext")
else:
@ -142,6 +156,9 @@ def change_state(request, name, option=None):
default_action_text(group, charter, login)
elif charter_state.slug == "iesgrev":
create_ballot_if_not_open(charter, login, "approve")
elif charter_state.slug == "approved":
change_group_state_after_charter_approval(group, login)
fix_charter_revision_after_approval(charter, login)
if charter_state.slug == "infrev" and clean["initial_time"] and clean["initial_time"] != 0:
e = InitialReviewDocEvent(type="initial_review", by=login, doc=charter)
@ -151,20 +168,24 @@ def change_state(request, name, option=None):
return redirect('doc_view', name=charter.name)
else:
if option == "recharter":
hide = ['initial_time', 'charter_state', 'message']
init = dict()
elif option == "initcharter":
hide = ['charter_state']
init = dict(initial_time=1, message='%s has initiated chartering of the proposed %s:\n "%s" (%s).' % (login.plain_name(), group.type.name, group.name, group.acronym))
elif option == "abandon":
hide = ['initial_time']
s = charter.get_state()
init = dict(charter_state=s.pk if s and option != "recharter" else None)
if option == "abandon":
hide = ['initial_time', 'charter_state']
init = dict(message='%s has abandoned the chartering effort on the %s:\n "%s" (%s).' % (login.plain_name(), group.type.name, group.name, group.acronym))
else:
hide = ['initial_time']
s = charter.get_state()
init = dict(charter_state=s.pk if s else None)
form = ChangeStateForm(hide=hide, initial=init)
if group.type_id == "wg":
if option == "recharter":
hide = ['initial_time', 'charter_state', 'message']
init = dict()
elif option == "initcharter":
hide = ['charter_state']
init = dict(initial_time=1, message='%s has initiated chartering of the proposed %s:\n "%s" (%s).' % (login.plain_name(), group.type.name, group.name, group.acronym))
elif option == "abandon":
hide = ['initial_time', 'charter_state']
init = dict(message='%s has abandoned the chartering effort on the %s:\n "%s" (%s).' % (login.plain_name(), group.type.name, group.name, group.acronym))
form = ChangeStateForm(hide=hide, initial=init, group=group)
prev_charter_state = None
charter_hists = DocHistory.objects.filter(doc=charter).exclude(states__type="charter", states__slug=charter.get_state_slug()).order_by("-time")[:1]
@ -182,13 +203,15 @@ def change_state(request, name, option=None):
def state_pk(slug):
return State.objects.get(used=True, type="charter", slug=slug).pk
info_msg = {
state_pk("infrev"): 'The %s "%s" (%s) has been set to Informal IESG review by %s.' % (group.type.name, group.name, group.acronym, login.plain_name()),
state_pk("intrev"): 'The %s "%s" (%s) has been set to Internal review by %s.\nPlease place it on the next IESG telechat and inform the IAB.' % (group.type.name, group.name, group.acronym, login.plain_name()),
state_pk("extrev"): 'The %s "%s" (%s) has been set to External review by %s.\nPlease send out the external review announcement to the appropriate lists.\n\nSend the announcement to other SDOs: Yes\nAdditional recipients of the announcement: ' % (group.type.name, group.name, group.acronym, login.plain_name()),
}
info_msg = {}
if group.type_id == "wg":
info_msg[state_pk("infrev")] = 'The %s "%s" (%s) has been set to Informal IESG review by %s.' % (group.type.name, group.name, group.acronym, login.plain_name())
info_msg[state_pk("intrev")] = 'The %s "%s" (%s) has been set to Internal review by %s.\nPlease place it on the next IESG telechat and inform the IAB.' % (group.type.name, group.name, group.acronym, login.plain_name())
info_msg[state_pk("extrev")] = 'The %s "%s" (%s) has been set to External review by %s.\nPlease send out the external review announcement to the appropriate lists.\n\nSend the announcement to other SDOs: Yes\nAdditional recipients of the announcement: ' % (group.type.name, group.name, group.acronym, login.plain_name())
states_for_ballot_wo_extern = State.objects.filter(used=True, type="charter", slug="intrev").values_list("pk", flat=True)
states_for_ballot_wo_extern = State.objects.none()
if group.type_id == "wg":
states_for_ballot_wo_extern = State.objects.filter(used=True, type="charter", slug="intrev").values_list("pk", flat=True)
return render_to_response('doc/charter/change_state.html',
dict(form=form,
@ -352,17 +375,16 @@ class UploadForm(forms.Form):
else:
destination.write(self.cleaned_data['content'].encode("utf-8"))
@role_required('Area Director','Secretariat')
def submit(request, name=None, acronym=None, option=None):
if name:
if not name.startswith('charter-'):
name = "charter-ietf-" + name
elif acronym:
name = "charter-ietf-" + acronym
@login_required
def submit(request, name=None, option=None):
if not name.startswith('charter-'):
raise Http404
charter = get_object_or_404(Document, type="charter", name=name)
group = charter.group
login = request.user.person
if not can_manage_group_type(request.user, group.type_id):
return HttpResponseForbidden("You don't have permission to access this view")
path = os.path.join(settings.CHARTER_PATH, '%s-%s.txt' % (charter.canonical_name(), charter.rev))
not_uploaded_yet = charter.rev.endswith("-00") and not os.path.exists(path)
@ -386,7 +408,7 @@ def submit(request, name=None, acronym=None, option=None):
charter.rev = next_rev
e = NewRevisionDocEvent(doc=charter, by=login, type="new_revision")
e = NewRevisionDocEvent(doc=charter, by=request.user.person, type="new_revision")
e.desc = "New version available: <b>%s-%s.txt</b>" % (charter.canonical_name(), charter.rev)
e.rev = charter.rev
e.save()
@ -423,7 +445,8 @@ def submit(request, name=None, acronym=None, option=None):
return render_to_response('doc/charter/submit.html',
{'form': form,
'next_rev': next_rev,
'group': group },
'group': group,
'name': name },
context_instance=RequestContext(request))
class AnnouncementTextForm(forms.Form):
@ -568,6 +591,46 @@ def ballot_writeupnotes(request, name):
),
context_instance=RequestContext(request))
def change_group_state_after_charter_approval(group, by):
new_state = GroupStateName.objects.get(slug="active")
if group.state == new_state:
return None
save_group_in_history(group)
group.state = new_state
group.time = datetime.datetime.now()
group.save()
# create an event for the group state change, too
e = ChangeStateGroupEvent(group=group, type="changed_state")
e.time = group.time
e.by = by
e.state_id = "active"
e.desc = "Charter approved, group active"
e.save()
return e
def fix_charter_revision_after_approval(charter, by):
# according to spec, 00-02 becomes 01, so copy file and record new revision
try:
old = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), charter.rev))
new = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), next_approved_revision(charter.rev)))
shutil.copy(old, new)
except IOError:
return HttpResponse("There was an error copying %s to %s" %
('%s-%s.txt' % (charter.canonical_name(), charter.rev),
'%s-%s.txt' % (charter.canonical_name(), next_approved_revision(charter.rev))))
e = NewRevisionDocEvent(doc=charter, by=by, type="new_revision")
e.rev = next_approved_revision(charter.rev)
e.desc = "New version available: <b>%s-%s.txt</b>" % (charter.canonical_name(), e.rev)
e.save()
charter.rev = e.rev
charter.time = e.time
charter.save()
@role_required("Secretariat")
def approve(request, name):
"""Approve charter, changing state, fixing revision, copying file to final location."""
@ -599,43 +662,13 @@ def approve(request, name):
change_description = e.desc
new_state = GroupStateName.objects.get(slug="active")
if group.state != new_state:
save_group_in_history(group)
group.state = new_state
group.time = e.time
group.save()
# create an event for the wg state change, too
e = ChangeStateGroupEvent(group=group, type="changed_state")
e.time = group.time
e.by = login
e.state_id = "active"
e.desc = "Charter approved, group active"
e.save()
change_description += " and %s state has been changed to %s" % (group.type.name, new_state.name)
group_state_change_event = change_group_state_after_charter_approval(group, login)
if group_state_change_event:
change_description += " and group state has been changed to %s" % group.state.name
add_state_change_event(charter, login, prev_charter_state, new_charter_state)
# according to spec, 00-02 becomes 01, so copy file and record new revision
try:
old = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), charter.rev))
new = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), next_approved_revision(charter.rev)))
shutil.copy(old, new)
except IOError:
return HttpResponse("There was an error copying %s to %s" %
('%s-%s.txt' % (charter.canonical_name(), charter.rev),
'%s-%s.txt' % (charter.canonical_name(), next_approved_revision(charter.rev))))
e = NewRevisionDocEvent(doc=charter, by=login, type="new_revision")
e.rev = next_approved_revision(charter.rev)
e.desc = "New version available: <b>%s-%s.txt</b>" % (charter.canonical_name(), e.rev)
e.save()
charter.rev = e.rev
charter.time = e.time
charter.save()
fix_charter_revision_after_approval(charter, login)
email_secretariat(request, group, "Charter state changed to %s" % new_charter_state.name, change_description)

View file

@ -51,6 +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.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
@ -63,10 +64,10 @@ def render_document_top(request, doc, tab, name):
ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
if doc.type_id in ("draft","conflrev", "statchg"):
tabs.append(("IESG Evaluation Record", "ballot", urlreverse("doc_ballot", kwargs=dict(name=name)), ballot, None if ballot else "IESG Evaluation Ballot has not been created yet"))
elif doc.type_id == "charter":
tabs.append(("IESG Review", "ballot", urlreverse("doc_ballot", kwargs=dict(name=name)), ballot, None if ballot else "IEST Review Ballot has not been created yet"))
elif doc.type_id == "charter" and doc.group.type_id == "wg":
tabs.append(("IESG Review", "ballot", urlreverse("doc_ballot", kwargs=dict(name=name)), ballot, None if ballot else "IESG Review Ballot has not been created yet"))
if doc.type_id not in ["conflrev", "statchg"]:
if doc.type_id == "draft" or (doc.type_id == "charter" and doc.group.type_id == "wg"):
tabs.append(("IESG Writeups", "writeup", urlreverse("doc_writeup", kwargs=dict(name=name)), True))
tabs.append(("History", "history", urlreverse("doc_history", kwargs=dict(name=name)), True))
@ -239,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("wg_docs", kwargs=dict(acronym=doc.group.acronym)), submission)
submission = "<a href=\"%s\">%s</a>" % (urlreverse("group_docs", 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
@ -410,6 +411,8 @@ def document_main(request, name, rev=None):
if chartering and not snapshot:
milestones = doc.group.groupmilestone_set.filter(state="charter")
can_manage = can_manage_group_type(request.user, doc.group.type_id)
return render_to_response("doc/document_charter.html",
dict(doc=doc,
top=top,
@ -422,6 +425,7 @@ def document_main(request, name, rev=None):
ballot_summary=ballot_summary,
group=group,
milestones=milestones,
can_manage=can_manage,
),
context_instance=RequestContext(request))

View file

@ -2,7 +2,7 @@ from django.conf.urls import patterns
from django.views.generic import RedirectView
from ietf.doc.feeds import DocumentChangesFeed, InLastCallFeed
from ietf.wginfo.feeds import GroupChangesFeed
from ietf.group.feeds import GroupChangesFeed
from ietf.iesg.feeds import IESGAgendaFeed
from ietf.ipr.feeds import LatestIprDisclosuresFeed
from ietf.liaisons.feeds import LiaisonStatementsFeed

View file

@ -1,4 +1,4 @@
# edit/create view for WGs
# edit/create view for groups
import re
import os
@ -6,11 +6,11 @@ import datetime
import shutil
from django import forms
from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponseForbidden
from django.template import RequestContext
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
@ -18,15 +18,15 @@ from ietf.doc.models import DocAlias, DocTagName, Document, State, save_document
from ietf.doc.utils import get_tags_for_stream_id
from ietf.group.models import ( Group, Role, GroupEvent, GroupHistory, GroupStateName,
GroupStateTransitions, GroupTypeName, GroupURL, ChangeStateGroupEvent )
from ietf.group.utils import save_group_in_history
from ietf.ietfauth.utils import role_required, has_role
from ietf.group.utils import save_group_in_history, can_manage_group_type
from ietf.ietfauth.utils import has_role
from ietf.person.forms import EmailsField
from ietf.person.models import Person, Email
from ietf.wginfo.mails import email_secretariat
from ietf.group.mails import email_secretariat
MAX_GROUP_DELEGATES = 3
class WGForm(forms.Form):
class GroupForm(forms.Form):
name = forms.CharField(max_length=255, label="Name", required=True)
acronym = forms.CharField(max_length=10, label="Acronym", required=True)
state = forms.ModelChoiceField(GroupStateName.objects.all(), label="State", required=True)
@ -35,18 +35,22 @@ class WGForm(forms.Form):
techadv = EmailsField(label="Technical Advisors", required=False)
delegates = EmailsField(label="Delegates", required=False, help_text=mark_safe("Type in name to search for person<br>Chairs can delegate the authority to update the state of group documents - max %s persons at a given time" % MAX_GROUP_DELEGATES))
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active").order_by('name'), label="Shepherding AD", empty_label="(None)", required=False)
parent = forms.ModelChoiceField(Group.objects.filter(type="area", state="active").order_by('name'), label="IETF Area", empty_label="(None)", required=False)
parent = forms.ModelChoiceField(Group.objects.filter(state="active").order_by('name'), empty_label="(None)", required=False)
list_email = forms.CharField(max_length=64, required=False)
list_subscribe = forms.CharField(max_length=255, required=False)
list_archive = forms.CharField(max_length=255, required=False)
urls = forms.CharField(widget=forms.Textarea, label="Additional URLs", help_text="Format: http://site/path (Optional description). Separate multiple entries with newline.", required=False)
def __init__(self, *args, **kwargs):
self.wg = kwargs.pop('wg', None)
self.group = kwargs.pop('group', None)
self.confirmed = kwargs.pop('confirmed', False)
self.group_type = kwargs.pop('group_type', False)
super(self.__class__, self).__init__(*args, **kwargs)
if self.group_type == "rg":
self.fields["state"].queryset = self.fields["state"].queryset.exclude(slug__in=("bof", "bof-conc"))
# if previous AD is now ex-AD, append that person to the list
ad_pk = self.initial.get('ad')
choices = self.fields['ad'].choices
@ -55,50 +59,58 @@ class WGForm(forms.Form):
self.confirm_msg = ""
self.autoenable_confirm = False
if self.wg:
if self.group:
self.fields['acronym'].widget.attrs['readonly'] = True
if self.group_type == "rg":
self.fields['ad'].widget = forms.HiddenInput()
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(acronym="irtf")
self.fields['parent'].widget = forms.HiddenInput()
else:
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(type="area")
self.fields['parent'].label = "IETF Area"
def clean_acronym(self):
self.confirm_msg = ""
self.autoenable_confirm = False
# Changing the acronym of an already existing WG will cause 404s all
# Changing the acronym of an already existing group will cause 404s all
# over the place, loose history, and generally muck up a lot of
# things, so we don't permit it
if self.wg:
return self.wg.acronym # no change permitted
if self.group:
return self.group.acronym # no change permitted
acronym = self.cleaned_data['acronym'].strip().lower()
# be careful with acronyms, requiring confirmation to take existing or override historic
if not re.match(r'^[a-z][a-z0-9]+$', acronym):
raise forms.ValidationError("Acronym is invalid, must be at least two characters and only contain lowercase letters and numbers starting with a letter.")
# be careful with acronyms, requiring confirmation to take existing or override historic
existing = Group.objects.filter(acronym__iexact=acronym)
if existing:
existing = existing[0]
if existing and existing.type_id == "wg":
if existing and existing.type_id == self.group_type:
if self.confirmed:
return acronym # take over confirmed
if existing.state_id == "bof":
self.confirm_msg = "Turn BoF %s into proposed WG and start chartering it" % existing.acronym
self.confirm_msg = "Turn BoF %s into proposed %s and start chartering it" % (existing.acronym, existing.type.name)
self.autoenable_confirm = True
raise forms.ValidationError("Warning: Acronym used for an existing BoF (%s)." % existing.name)
else:
self.confirm_msg = "Set state of %s WG to proposed and start chartering it" % existing.acronym
self.confirm_msg = "Set state of %s %s to proposed and start chartering it" % (existing.acronym, existing.type.name)
self.autoenable_confirm = False
raise forms.ValidationError("Warning: Acronym used for an existing WG (%s, %s)." % (existing.name, existing.state.name if existing.state else "unknown state"))
raise forms.ValidationError("Warning: Acronym used for an existing %s (%s, %s)." % (existing.type.name, existing.name, existing.state.name if existing.state else "unknown state"))
if existing:
raise forms.ValidationError("Acronym used for an existing group (%s)." % existing.name)
old = GroupHistory.objects.filter(acronym__iexact=acronym, type="wg")
old = GroupHistory.objects.filter(acronym__iexact=acronym, type__in=("wg", "rg"))
if old and not self.confirmed:
self.confirm_msg = "Confirm reusing acronym %s" % old[0].acronym
self.autoenable_confirm = False
raise forms.ValidationError("Warning: Acronym used for a historic WG.")
raise forms.ValidationError("Warning: Acronym used for a historic group.")
return acronym
@ -121,98 +133,107 @@ def format_urls(urls, fs="\n"):
res.append(u.url)
return fs.join(res)
def get_or_create_initial_charter(wg):
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)
try:
charter = Document.objects.get(docalias__name="charter-ietf-%s" % wg.acronym)
charter = Document.objects.get(docalias__name=charter_name)
except Document.DoesNotExist:
charter = Document(
name="charter-ietf-" + wg.acronym,
name=charter_name,
type_id="charter",
title=wg.name,
group=wg,
abstract=wg.name,
title=group.name,
group=group,
abstract=group.name,
rev="00-00",
)
charter.save()
charter.set_state(State.objects.get(used=True, type="charter", slug="notrev"))
# Create an alias as well
DocAlias.objects.create(
name=charter.name,
document=charter
)
# Create an alias as well
DocAlias.objects.create(name=charter.name, document=charter)
return charter
@role_required('Area Director', 'Secretariat')
def submit_initial_charter(request, acronym=None):
wg = get_object_or_404(Group, acronym=acronym)
if not wg.charter:
wg.charter = get_or_create_initial_charter(wg)
wg.save()
return redirect('charter_submit', name=wg.charter.name, option="initcharter")
@role_required('Area Director', 'Secretariat')
def edit(request, acronym=None, action="edit"):
"""Edit or create a WG, notifying parties as
@login_required
def submit_initial_charter(request, group_type, acronym=None):
if not can_manage_group_type(request.user, group_type):
return HttpResponseForbidden("You don't have permission to access this view")
group = get_object_or_404(Group, acronym=acronym)
if not group.charter:
group.charter = get_or_create_initial_charter(group, group_type)
group.save()
return redirect('charter_submit', name=group.charter.name, option="initcharter")
@login_required
def edit(request, group_type=None, acronym=None, action="edit"):
"""Edit or create a group, notifying parties as
necessary and logging changes as group events."""
if not can_manage_group_type(request.user, group_type):
return HttpResponseForbidden("You don't have permission to access this view")
if action == "edit":
wg = get_object_or_404(Group, acronym=acronym)
new_wg = False
group = get_object_or_404(Group, acronym=acronym)
new_group = False
elif action in ("create","charter"):
wg = None
new_wg = True
group = None
new_group = True
else:
raise Http404
login = request.user.person
if request.method == 'POST':
form = WGForm(request.POST, wg=wg, confirmed=request.POST.get("confirmed", False))
form = GroupForm(request.POST, group=group, confirmed=request.POST.get("confirmed", False), group_type=group_type)
if form.is_valid():
clean = form.cleaned_data
if new_wg:
if new_group:
try:
wg = Group.objects.get(acronym=clean["acronym"])
save_group_in_history(wg)
wg.time = datetime.datetime.now()
wg.save()
group = Group.objects.get(acronym=clean["acronym"])
save_group_in_history(group)
group.time = datetime.datetime.now()
group.save()
except Group.DoesNotExist:
wg = Group.objects.create(name=clean["name"],
group = Group.objects.create(name=clean["name"],
acronym=clean["acronym"],
type=GroupTypeName.objects.get(slug="wg"),
type=GroupTypeName.objects.get(slug=group_type),
state=clean["state"]
)
e = ChangeStateGroupEvent(group=wg, type="changed_state")
e.time = wg.time
e.by = login
e = ChangeStateGroupEvent(group=group, type="changed_state")
e.time = group.time
e.by = request.user.person
e.state_id = clean["state"].slug
e.desc = "Group created in state %s" % clean["state"].name
e.save()
else:
save_group_in_history(wg)
save_group_in_history(group)
if action=="charter" and not wg.charter: # make sure we have a charter
wg.charter = get_or_create_initial_charter(wg)
if action == "charter" and not group.charter: # make sure we have a charter
group.charter = get_or_create_initial_charter(group, group_type)
changes = []
def desc(attr, new, old):
entry = "%(attr)s changed to <b>%(new)s</b> from %(old)s"
if new_wg:
if new_group:
entry = "%(attr)s changed to <b>%(new)s</b>"
return entry % dict(attr=attr, new=new, old=old)
def diff(attr, name):
v = getattr(wg, attr)
v = getattr(group, attr)
if clean[attr] != v:
changes.append(desc(name, clean[attr], v))
setattr(wg, attr, clean[attr])
setattr(group, attr, clean[attr])
prev_acronym = wg.acronym
prev_acronym = group.acronym
# update the attributes, keeping track of what we're doing
diff('name', "Name")
@ -224,127 +245,130 @@ def edit(request, acronym=None, action="edit"):
diff('list_subscribe', "Mailing list subscribe address")
diff('list_archive', "Mailing list archive")
if not new_wg and wg.acronym != prev_acronym and wg.charter:
save_document_in_history(wg.charter)
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" % wg.acronym,
document=wg.charter,
name="charter-ietf-%s" % group.acronym,
document=group.charter,
)
old = os.path.join(wg.charter.get_file_path(), 'charter-ietf-%s-%s.txt' % (prev_acronym, wg.charter.rev))
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(wg.charter.get_file_path(), 'charter-ietf-%s-%s.txt' % (wg.acronym, wg.charter.rev))
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]
old = Email.objects.filter(role__group=wg, role__name=slug).select_related("person")
old = Email.objects.filter(role__group=group, role__name=slug).select_related("person")
if set(new) != set(old):
changes.append(desc(title,
", ".join(x.get_name() for x in new),
", ".join(x.get_name() for x in old)))
wg.role_set.filter(name=slug).delete()
group.role_set.filter(name=slug).delete()
for e in new:
Role.objects.get_or_create(name_id=slug, email=e, group=wg, person=e.person)
Role.objects.get_or_create(name_id=slug, email=e, group=group, person=e.person)
# update urls
new_urls = clean['urls']
old_urls = format_urls(wg.groupurl_set.order_by('url'), ", ")
old_urls = format_urls(group.groupurl_set.order_by('url'), ", ")
if ", ".join(sorted(new_urls)) != old_urls:
changes.append(desc('Urls', ", ".join(sorted(new_urls)), old_urls))
wg.groupurl_set.all().delete()
group.groupurl_set.all().delete()
# Add new ones
for u in new_urls:
m = re.search('(?P<url>[\w\d:#@%/;$()~_?\+-=\\\.&]+)( \((?P<name>.+)\))?', u)
if m:
if m.group('name'):
url = GroupURL(url=m.group('url'), name=m.group('name'), group=wg)
url = GroupURL(url=m.group('url'), name=m.group('name'), group=group)
else:
url = GroupURL(url=m.group('url'), name='', group=wg)
url = GroupURL(url=m.group('url'), name='', group=group)
url.save()
wg.time = datetime.datetime.now()
group.time = datetime.datetime.now()
if changes and not new_wg:
if changes and not new_group:
for c in changes:
GroupEvent.objects.create(group=wg, by=login, type="info_changed", desc=c)
GroupEvent.objects.create(group=group, by=request.user.person, type="info_changed", desc=c)
wg.save()
group.save()
if action=="charter":
return redirect('charter_submit', name=wg.charter.name, option="initcharter")
return redirect('charter_submit', name=group.charter.name, option="initcharter")
return redirect('group_charter', acronym=wg.acronym)
return redirect('group_charter', group_type=group.type_id, acronym=group.acronym)
else: # form.is_valid()
if not new_wg:
init = dict(name=wg.name,
acronym=wg.acronym,
state=wg.state,
chairs=Email.objects.filter(role__group=wg, role__name="chair"),
secretaries=Email.objects.filter(role__group=wg, role__name="secr"),
techadv=Email.objects.filter(role__group=wg, role__name="techadv"),
delegates=Email.objects.filter(role__group=wg, role__name="delegate"),
ad=wg.ad_id if wg.ad else None,
parent=wg.parent.id if wg.parent else None,
list_email=wg.list_email if wg.list_email else None,
list_subscribe=wg.list_subscribe if wg.list_subscribe else None,
list_archive=wg.list_archive if wg.list_archive else None,
urls=format_urls(wg.groupurl_set.all()),
if not new_group:
init = dict(name=group.name,
acronym=group.acronym,
state=group.state,
chairs=Email.objects.filter(role__group=group, role__name="chair"),
secretaries=Email.objects.filter(role__group=group, role__name="secr"),
techadv=Email.objects.filter(role__group=group, role__name="techadv"),
delegates=Email.objects.filter(role__group=group, role__name="delegate"),
ad=group.ad_id if group.ad else None,
parent=group.parent.id if group.parent else None,
list_email=group.list_email if group.list_email else None,
list_subscribe=group.list_subscribe if group.list_subscribe else None,
list_archive=group.list_archive if group.list_archive else None,
urls=format_urls(group.groupurl_set.all()),
)
else:
init = dict(ad=login.id if has_role(request.user, "Area Director") else None,
init = dict(ad=request.user.person.id if group_type == "wg" and has_role(request.user, "Area Director") else None,
)
form = WGForm(initial=init, wg=wg)
form = GroupForm(initial=init, group=group, group_type=group_type)
return render_to_response('wginfo/edit.html',
dict(wg=wg,
form=form,
action=action,
user=request.user,
login=login),
context_instance=RequestContext(request))
return render(request, 'group/edit.html',
dict(group=group,
form=form,
action=action))
class ConcludeForm(forms.Form):
instructions = forms.CharField(widget=forms.Textarea(attrs={'rows': 30}), required=True)
@role_required('Area Director','Secretariat')
def conclude(request, acronym):
"""Request the closing of a WG, prompting for instructions."""
wg = get_object_or_404(Group, acronym=acronym)
@login_required
def conclude(request, group_type, acronym):
"""Request the closing of group, prompting for instructions."""
group = get_object_or_404(Group, type=group_type, acronym=acronym)
login = request.user.person
if not can_manage_group_type(request.user, group_type):
return HttpResponseForbidden("You don't have permission to access this view")
if request.method == 'POST':
form = ConcludeForm(request.POST)
if form.is_valid():
instructions = form.cleaned_data['instructions']
email_secretariat(request, wg, "Request closing of group", instructions)
email_secretariat(request, group, "Request closing of group", instructions)
e = GroupEvent(group=wg, by=login)
e = GroupEvent(group=group, by=request.user.person)
e.type = "requested_close"
e.desc = "Requested closing group"
e.save()
return redirect('group_charter', acronym=wg.acronym)
return redirect('group_charter', group_type=group.type_id, acronym=group.acronym)
else:
form = ConcludeForm()
return render_to_response('wginfo/conclude.html',
dict(form=form,
wg=wg),
context_instance=RequestContext(request))
return render(request, 'group/conclude.html',
dict(form=form, group=group))
def customize_workflow(request, acronym):
MANDATORY_STATES = ('c-adopt', 'wg-doc', 'sub-pub')
group = get_object_or_404(Group, acronym=acronym, type="wg")
if not request.user.is_authenticated() or not (has_role(request.user, "Secretariat") or group.role_set.filter(name="chair", person__user=request.user)):
@login_required
def customize_workflow(request, group_type, acronym):
group = get_object_or_404(Group, type=group_type, acronym=acronym)
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")
if group_type == "rg":
stream_id = "irtf"
MANDATORY_STATES = ('candidat', 'active', 'rfc-edit', 'pub', 'dead')
else:
stream_id = "ietf"
MANDATORY_STATES = ('c-adopt', 'wg-doc', 'sub-pub')
if request.method == 'POST':
action = request.POST.get("action")
if action == "setstateactive":
@ -361,7 +385,7 @@ def customize_workflow(request, acronym):
# redirect so the back button works correctly, otherwise
# repeated POSTs fills up the history
return redirect("ietf.wginfo.edit.customize_workflow", acronym=group.acronym)
return redirect("ietf.group.edit.customize_workflow", group_type=group.type_id, acronym=group.acronym)
if action == "setnextstates":
try:
@ -369,7 +393,7 @@ def customize_workflow(request, acronym):
except State.DoesNotExist:
return HttpResponse("Invalid state %s" % request.POST.get("state"))
next_states = State.objects.filter(used=True, type='draft-stream-ietf', pk__in=request.POST.getlist("next_states"))
next_states = State.objects.filter(used=True, type='draft-stream-%s' % stream_id, pk__in=request.POST.getlist("next_states"))
unused = group.unused_states.all()
if set(next_states.exclude(pk__in=unused)) == set(state.next_states.exclude(pk__in=unused)):
# just use the default
@ -378,7 +402,7 @@ def customize_workflow(request, acronym):
transitions, _ = GroupStateTransitions.objects.get_or_create(group=group, state=state)
transitions.next_states = next_states
return redirect("ietf.wginfo.edit.customize_workflow", acronym=group.acronym)
return redirect("ietf.group.edit.customize_workflow", group_type=group.type_id, acronym=group.acronym)
if action == "settagactive":
active = request.POST.get("active") == "1"
@ -392,17 +416,16 @@ def customize_workflow(request, acronym):
else:
group.unused_tags.add(tag)
return redirect("ietf.wginfo.edit.customize_workflow", acronym=group.acronym)
return redirect("ietf.group.edit.customize_workflow", group_type=group.type_id, acronym=group.acronym)
# put some info for the template on tags and states
unused_tags = group.unused_tags.all().values_list('slug', flat=True)
tags = DocTagName.objects.filter(slug__in=get_tags_for_stream_id("ietf"))
tags = DocTagName.objects.filter(slug__in=get_tags_for_stream_id(stream_id))
for t in tags:
t.used = t.slug not in unused_tags
unused_states = group.unused_states.all().values_list('slug', flat=True)
states = State.objects.filter(used=True, type="draft-stream-ietf")
states = State.objects.filter(used=True, type="draft-stream-%s" % stream_id)
transitions = dict((o.state, o) for o in group.groupstatetransitions_set.all())
for s in states:
s.used = s.slug not in unused_states
@ -417,8 +440,8 @@ def customize_workflow(request, acronym):
s.next_states_checkboxes = [(x in n, x in default_n, x) for x in states]
s.used_next_states = [x for x in n if x.slug not in unused_states]
return render_to_response('wginfo/customize_workflow.html', {
return render(request, 'group/customize_workflow.html', {
'group': group,
'states': states,
'tags': tags,
}, RequestContext(request))
})

View file

@ -11,7 +11,7 @@ from ietf.doc.models import DocEvent
class GroupChangesFeed(Feed):
feed_type = Atom1Feed
description_template = "wginfo/feed_item_description.html"
description_template = "group/feed_item_description.html"
def get_object(self, request, acronym):
return Group.objects.get(acronym=acronym)
@ -22,7 +22,7 @@ class GroupChangesFeed(Feed):
def link(self, obj):
if not obj:
raise FeedDoesNotExist
return urlreverse('group_charter', kwargs={'acronym': obj.acronym})
return urlreverse('group_charter', kwargs=dict(group_type=obj.type_id, acronym=obj.acronym))
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={'acronym': obj.group.acronym })
return urlreverse('group_charter', kwargs=dict(group_type=obj.group.type_id, acronym=obj.group.acronym))
def item_pubdate(self, obj):
return obj.time

View file

@ -36,22 +36,21 @@ import os
import itertools
from tempfile import mkstemp
from django.shortcuts import get_object_or_404, render_to_response
from django.shortcuts import get_object_or_404, render
from django.template.loader import render_to_string
from django.template import RequestContext
from django.http import HttpResponse
from django.http import HttpResponse, Http404
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 ietf.doc.views_search import SearchForm, retrieve_search_results
from ietf.group.models import Group, Role
from ietf.doc.models import State, DocAlias, RelatedDocument
from ietf.doc.utils import get_chartering_type
from ietf.group.utils import get_charter_text
from ietf.doc.templatetags.ietf_filters import clean_whitespace
from ietf.ietfauth.utils import has_role
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.utils.pipe import pipe
def roles(group, role_name):
@ -86,7 +85,9 @@ def fill_in_charter_info(group, include_drafts=False):
def extract_last_name(role):
return role.person.name_parts()[3]
def wg_summary_area(request):
def wg_summary_area(request, group_type):
if group_type != "wg":
raise Http404
areas = Group.objects.filter(type="area", state="active").order_by("name")
for area in areas:
area.ads = sorted(roles(area, "ad"), key=extract_last_name)
@ -96,21 +97,25 @@ def wg_summary_area(request):
areas = [a for a in areas if a.groups]
return render_to_response('wginfo/1wg-summary.txt',
{ 'areas': areas },
content_type='text/plain; charset=UTF-8')
return render(request, 'group/1wg-summary.txt',
{ 'areas': areas },
content_type='text/plain; charset=UTF-8')
def wg_summary_acronym(request):
def wg_summary_acronym(request, group_type):
if group_type != "wg":
raise Http404
areas = Group.objects.filter(type="area", state="active").order_by("name")
groups = Group.objects.filter(type="wg", state="active").order_by("acronym").select_related("parent")
for group in groups:
group.chairs = sorted(roles(group, "chair"), key=extract_last_name)
return render_to_response('wginfo/1wg-summary-by-acronym.txt',
{ 'areas': areas,
'groups': groups },
content_type='text/plain; charset=UTF-8')
return render(request, 'group/1wg-summary-by-acronym.txt',
{ 'areas': areas,
'groups': groups },
content_type='text/plain; charset=UTF-8')
def wg_charters(request):
def wg_charters(request, group_type):
if group_type != "wg":
raise Http404
areas = Group.objects.filter(type="area", state="active").order_by("name")
for area in areas:
area.ads = sorted(roles(area, "ad"), key=extract_last_name)
@ -118,11 +123,13 @@ def wg_charters(request):
for group in area.groups:
fill_in_charter_info(group, include_drafts=True)
group.area = area
return render_to_response('wginfo/1wg-charters.txt',
{ 'areas': areas },
content_type='text/plain; charset=UTF-8')
return render(request, 'group/1wg-charters.txt',
{ 'areas': areas },
content_type='text/plain; charset=UTF-8')
def wg_charters_by_acronym(request):
def wg_charters_by_acronym(request, group_type):
if group_type != "wg":
raise Http404
areas = dict((a.id, a) for a in Group.objects.filter(type="area", state="active").order_by("name"))
for area in areas.itervalues():
@ -132,9 +139,17 @@ def wg_charters_by_acronym(request):
for group in groups:
fill_in_charter_info(group, include_drafts=True)
group.area = areas.get(group.parent_id)
return render_to_response('wginfo/1wg-charters-by-acronym.txt',
{ 'groups': groups },
content_type='text/plain; charset=UTF-8')
return render(request, 'group/1wg-charters-by-acronym.txt',
{ 'groups': groups },
content_type='text/plain; charset=UTF-8')
def active_groups(request, group_type):
if group_type == "wg":
return active_wgs(request)
elif group_type == "rg":
return active_rgs(request)
else:
raise Http404
def active_wgs(request):
areas = Group.objects.filter(type="area", state="active").order_by("name")
@ -148,43 +163,77 @@ def active_wgs(request):
for group in area.groups:
group.chairs = sorted(roles(group, "chair"), key=extract_last_name)
return render_to_response('wginfo/active_wgs.html', {'areas':areas}, RequestContext(request))
return render(request, 'group/active_wgs.html', { 'areas':areas })
def bofs(request):
groups = Group.objects.filter(type="wg", state="bof")
return render_to_response('wginfo/bofs.html',dict(groups=groups), RequestContext(request))
def active_rgs(request):
irtf = Group.objects.get(acronym="irtf")
irtf.chair = roles(irtf, "chair").first()
def chartering_wgs(request):
groups = Group.objects.filter(type="rg", state="active").order_by("acronym")
for group in groups:
group.chairs = sorted(roles(group, "chair"), key=extract_last_name)
return render(request, 'group/active_rgs.html', { 'irtf': irtf, 'groups': groups })
def bofs(request, group_type):
groups = Group.objects.filter(type=group_type, state="bof")
return render(request, 'group/bofs.html',dict(groups=groups))
def chartering_groups(request):
charter_states = State.objects.filter(used=True, type="charter").exclude(slug__in=("approved", "notrev"))
groups = Group.objects.filter(type="wg", charter__states__in=charter_states).select_related("state", "charter")
for g in groups:
g.chartering_type = get_chartering_type(g.charter)
group_types = GroupTypeName.objects.filter(slug__in=("wg", "rg"))
return render_to_response('wginfo/chartering_wgs.html',
dict(charter_states=charter_states,
groups=groups),
RequestContext(request))
for t in group_types:
t.chartering_groups = Group.objects.filter(type=t, charter__states__in=charter_states).select_related("state", "charter").order_by("acronym")
t.can_manage = can_manage_group_type(request.user, t.slug)
for g in t.chartering_groups:
g.chartering_type = get_chartering_type(g.charter)
return render(request, 'group/chartering_groups.html',
dict(charter_states=charter_states,
group_types=group_types))
def concluded_groups(request):
group_types = GroupTypeName.objects.filter(slug__in=("wg", "rg"))
for t in group_types:
t.concluded_groups = Group.objects.filter(type=t, state__in=("conclude", "bof-conc")).select_related("state", "charter").order_by("acronym")
# add start/conclusion date
d = dict((g.pk, g) for g in t.concluded_groups)
for g in t.concluded_groups:
g.start_date = g.conclude_date = None
for e in ChangeStateGroupEvent.objects.filter(group__in=t.concluded_groups, state="active").order_by("-time"):
d[e.group_id].start_date = e.time
for e in ChangeStateGroupEvent.objects.filter(group__in=t.concluded_groups, state="conclude").order_by("time"):
d[e.group_id].conclude_date = e.time
return render(request, 'group/concluded_groups.html',
dict(group_types=group_types))
def construct_group_menu_context(request, group, selected, others):
"""Return context with info for the group menu filled in."""
actions = []
is_chair = group.has_role(request.user, "chair")
is_ad_or_secretariat = has_role(request.user, ("Area Director", "Secretariat"))
can_manage = can_manage_group_type(request.user, group.type_id)
if group.state_id != "proposed" and (is_chair or is_ad_or_secretariat):
actions.append((u"Add or edit milestones", urlreverse("wg_edit_milestones", kwargs=dict(acronym=group.acronym))))
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.state_id != "conclude" and is_ad_or_secretariat:
actions.append((u"Edit group", urlreverse("group_edit", kwargs=dict(acronym=group.acronym))))
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 is_chair or is_ad_or_secretariat:
actions.append((u"Customize workflow", urlreverse("ietf.wginfo.edit.customize_workflow", kwargs=dict(acronym=group.acronym))))
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.state_id in ("active", "dormant") and is_ad_or_secretariat:
actions.append((u"Request closing group", urlreverse("wg_conclude", kwargs=dict(acronym=group.acronym))))
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))))
d = {
"group": group,
@ -207,8 +256,8 @@ def search_for_group_documents(group):
docs_related = []
for d in raw_docs_related:
parts = d.name.split("-", 2);
# canonical form draft-<name|ietf>-wg-etc
if len(parts) >= 3 and parts[1] != "ietf" and parts[2].startswith(group.acronym + "-"):
# canonical form draft-<name|ietf|irtf>-wg-etc
if len(parts) >= 3 and parts[1] not in ("ietf", "irtf") and parts[2].startswith(group.acronym + "-"):
d.search_heading = "Related Internet-Draft"
docs_related.append(d)
@ -229,22 +278,22 @@ def search_for_group_documents(group):
return docs, meta, docs_related, meta_related
def group_documents(request, acronym):
group = get_object_or_404(Group, type="wg", acronym=acronym)
def group_documents(request, group_type, acronym):
group = get_object_or_404(Group, type=group_type, acronym=acronym)
docs, meta, docs_related, meta_related = search_for_group_documents(group)
return render_to_response('wginfo/group_documents.html',
construct_group_menu_context(request, group, "documents", {
return render(request, 'group/group_documents.html',
construct_group_menu_context(request, group, "documents", {
'docs': docs,
'meta': meta,
'docs_related': docs_related,
'meta_related': meta_related
}), RequestContext(request))
}))
def group_documents_txt(request, acronym):
def group_documents_txt(request, group_type, acronym):
"""Return tabulator-separated rows with documents for group."""
group = get_object_or_404(Group, type="wg", acronym=acronym)
group = get_object_or_404(Group, type=group_type, acronym=acronym)
docs, meta, docs_related, meta_related = search_for_group_documents(group)
@ -267,8 +316,8 @@ def group_documents_txt(request, acronym):
return HttpResponse(u"\n".join(rows), content_type='text/plain; charset=UTF-8')
def group_charter(request, acronym):
group = get_object_or_404(Group, type="wg", acronym=acronym)
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")
@ -276,22 +325,32 @@ def group_charter(request, acronym):
e = group.latest_event(type__in=("changed_state", "requested_close",))
requested_close = group.state_id != "conclude" and e and e.type == "requested_close"
return render_to_response('wginfo/group_charter.html',
construct_group_menu_context(request, group, "charter", {
"milestones_in_review": group.groupmilestone_set.filter(state="review"),
"requested_close": requested_close,
}), RequestContext(request))
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", {
"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, acronym):
def history(request, group_type, acronym):
group = get_object_or_404(Group, acronym=acronym)
events = group.groupevent_set.all().select_related('by').order_by('-time', '-id')
return render_to_response('wginfo/history.html',
construct_group_menu_context(request, group, "history", {
return render(request, 'group/history.html',
construct_group_menu_context(request, group, "history", {
"events": events,
}), RequestContext(request))
}))
def nodename(name):
@ -407,11 +466,11 @@ def make_dot(group):
node.nodename=nodename(node.name)
node.styles = get_node_styles(node,group)
return render_to_string('wginfo/dot.txt',
return render_to_string('group/dot.txt',
dict( nodes=nodes, edges=edges )
)
def dependencies_dot(request, acronym):
def dependencies_dot(request, group_type, acronym):
group = get_object_or_404(Group, acronym=acronym)
@ -420,7 +479,7 @@ def dependencies_dot(request, acronym):
)
@cache_page ( 60 * 60 )
def dependencies_pdf(request, acronym):
def dependencies_pdf(request, group_type, acronym):
group = get_object_or_404(Group, acronym=acronym)

View file

@ -10,8 +10,8 @@ from django.conf import settings
from django.core.urlresolvers import reverse as urlreverse
from ietf.utils.mail import send_mail, send_mail_text
from ietf.group.models import Group
from ietf.group.utils import milestone_reviewer_for_group_type
def email_secretariat(request, group, subject, text):
to = ["iesg-secretary@ietf.org"]
@ -19,10 +19,10 @@ def email_secretariat(request, group, subject, text):
text = strip_tags(text)
send_mail(request, to, None, full_subject,
"wginfo/email_secretariat.txt",
"group/email_secretariat.txt",
dict(text=text,
group=group,
group_url=settings.IDTRACKER_BASE_URL + urlreverse('group_charter', kwargs=dict(acronym=group.acronym)),
group_url=settings.IDTRACKER_BASE_URL + urlreverse('group_charter', kwargs=dict(group_type=group.type_id, acronym=group.acronym)),
charter_url=settings.IDTRACKER_BASE_URL + urlreverse('doc_view', kwargs=dict(name=group.charter.name)),
)
)
@ -31,16 +31,18 @@ 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(acronym=group.acronym)))
text += u"URL: %s" % (settings.IDTRACKER_BASE_URL + urlreverse("group_charter", kwargs=dict(group_type=group.type_id, acronym=group.acronym)))
send_mail_text(request, to, None,
u"Milestones changed for %s %s" % (group.acronym, group.type.name),
text)
# first send to AD and chairs
# first send to management and chairs
to = []
if group.ad:
to.append(group.ad.role_email("ad").formatted_email())
elif group.type_id == "rg":
to.append("IRTF Chair <irtf-chair@irtf.org>")
for r in group.role_set.filter(name="chair"):
to.append(r.formatted_email())
@ -48,7 +50,7 @@ def email_milestones_changed(request, group, changes):
if to:
wrap_up_email(to, u"\n\n".join(c + "." for c in changes))
# then send to WG
# then send to group
if group.list_email:
review_re = re.compile("Added .* for review, due")
to = [ group.list_email ]
@ -56,11 +58,17 @@ def email_milestones_changed(request, group, changes):
def email_milestone_review_reminder(group, grace_period=7):
"""Email reminders about milestones needing review to AD."""
if not group.ad:
"""Email reminders about milestones needing review to management."""
to = []
if group.ad:
to.append(group.ad.role_email("ad").formatted_email())
elif group.type_id == "rg":
to.append("IRTF Chair <irtf-chair@irtf.org>")
if not to:
return False
to = [group.ad.role_email("ad").formatted_email()]
cc = [r.formatted_email() for r in group.role_set.filter(name="chair")]
now = datetime.datetime.now()
@ -81,10 +89,11 @@ def email_milestone_review_reminder(group, grace_period=7):
send_mail(None, to, None,
subject,
"wginfo/reminder_milestones_need_review.txt",
"group/reminder_milestones_need_review.txt",
dict(group=group,
milestones=milestones,
url=settings.IDTRACKER_BASE_URL + urlreverse("wg_edit_milestones", kwargs=dict(acronym=group.acronym)),
reviewer=milestone_reviewer_for_group_type(group.type_id),
url=settings.IDTRACKER_BASE_URL + urlreverse("group_edit_milestones", kwargs=dict(group_type=group.type_id, acronym=group.acronym)),
cc=cc,
)
)
@ -107,12 +116,12 @@ def email_milestones_due(group, early_warning_days):
send_mail(None, to, None,
subject,
"wginfo/reminder_milestones_due.txt",
"group/reminder_milestones_due.txt",
dict(group=group,
milestones=milestones,
today=today,
early_warning_days=early_warning_days,
url=settings.IDTRACKER_BASE_URL + urlreverse("group_charter", kwargs=dict(acronym=group.acronym))
url=settings.IDTRACKER_BASE_URL + urlreverse("group_charter", kwargs=dict(group_type=group.type_id, acronym=group.acronym))
))
def groups_needing_milestones_due_reminder(early_warning_days):
@ -134,10 +143,10 @@ def email_milestones_overdue(group):
send_mail(None, to, None,
subject,
"wginfo/reminder_milestones_overdue.txt",
"group/reminder_milestones_overdue.txt",
dict(group=group,
milestones=milestones,
url=settings.IDTRACKER_BASE_URL + urlreverse("group_charter", kwargs=dict(acronym=group.acronym))
url=settings.IDTRACKER_BASE_URL + urlreverse("group_charter", kwargs=dict(group_type=group.type_id, acronym=group.acronym))
))
def groups_needing_milestones_overdue_reminder(grace_period=30):

View file

@ -1,4 +1,4 @@
# WG milestone editing views
# group milestone editing views
import datetime
import calendar
@ -6,16 +6,15 @@ import json
from django import forms
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest
from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.template import RequestContext
from django.shortcuts import render, get_object_or_404, 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
from ietf.ietfauth.utils import role_required, has_role
from ietf.group.utils import save_milestone_in_history, can_manage_group_type, milestone_reviewer_for_group_type
from ietf.name.models import GroupMilestoneStateName
from ietf.wginfo.mails import email_milestones_changed
from ietf.group.mails import email_milestones_changed
def json_doc_names(docs):
return json.dumps([{"id": doc.pk, "name": doc.name } for doc in docs])
@ -108,23 +107,19 @@ class MilestoneForm(forms.Form):
return r
@role_required('WG Chair', 'Area Director', 'Secretariat')
def edit_milestones(request, acronym, milestone_set="current"):
@login_required
def edit_milestones(request, group_type, acronym, milestone_set="current"):
# milestones_set + needs_review: we have several paths into this view
# AD/Secr. -> all actions on current + add new
# 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.
login = request.user.person
group = get_object_or_404(Group, acronym=acronym)
group = get_object_or_404(Group, type=group_type, acronym=acronym)
needs_review = False
if not has_role(request.user, ("Area Director", "Secretariat")):
if group.role_set.filter(name="chair", person=login):
if not can_manage_group_type(request.user, group_type):
if group.role_set.filter(name="chair", person__user=request.user):
if milestone_set == "current":
needs_review = True
else:
@ -298,10 +293,10 @@ def edit_milestones(request, acronym, milestone_set="current"):
if milestone_set == "charter":
DocEvent.objects.create(doc=group.charter, type="changed_charter_milestone",
by=login, desc=change)
by=request.user.person, desc=change)
else:
MilestoneGroupEvent.objects.create(group=group, type="changed_milestone",
by=login, desc=change, milestone=f.milestone)
by=request.user.person, desc=change, milestone=f.milestone)
changes.append(change)
@ -311,7 +306,7 @@ def edit_milestones(request, acronym, milestone_set="current"):
if milestone_set == "charter":
return redirect('doc_view', name=group.charter.canonical_name())
else:
return redirect('group_charter', acronym=group.acronym)
return redirect('group_charter', group_type=group.type_id, acronym=group.acronym)
else:
for m in milestones:
forms.append(MilestoneForm(instance=m, needs_review=needs_review))
@ -322,27 +317,25 @@ def edit_milestones(request, acronym, milestone_set="current"):
forms.sort(key=lambda f: f.milestone.due if f.milestone else datetime.date.max)
return render_to_response('wginfo/edit_milestones.html',
dict(group=group,
title=title,
forms=forms,
form_errors=form_errors,
empty_form=empty_form,
milestone_set=milestone_set,
finished_milestone_text=finished_milestone_text,
needs_review=needs_review,
can_reset=can_reset),
context_instance=RequestContext(request))
return render(request, 'group/edit_milestones.html',
dict(group=group,
title=title,
forms=forms,
form_errors=form_errors,
empty_form=empty_form,
milestone_set=milestone_set,
finished_milestone_text=finished_milestone_text,
needs_review=needs_review,
reviewer=milestone_reviewer_for_group_type(group_type),
can_reset=can_reset))
@role_required('WG Chair', 'Area Director', 'Secretariat')
def reset_charter_milestones(request, acronym):
@login_required
def reset_charter_milestones(request, group_type, acronym):
"""Reset charter milestones to the currently in-use milestones."""
login = request.user.person
group = get_object_or_404(Group, type=group_type, acronym=acronym)
group = get_object_or_404(Group, acronym=acronym)
if (not has_role(request.user, ("Area Director", "Secretariat")) and
not group.role_set.filter(name="chair", person=login)):
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.")
current_milestones = group.groupmilestone_set.filter(state="active")
@ -364,7 +357,7 @@ def reset_charter_milestones(request, acronym):
DocEvent.objects.create(type="changed_charter_milestone",
doc=group.charter,
desc='Deleted milestone "%s"' % m.desc,
by=login,
by=request.user.person,
)
# add current
@ -380,20 +373,19 @@ def reset_charter_milestones(request, acronym):
DocEvent.objects.create(type="changed_charter_milestone",
doc=group.charter,
desc='Added milestone "%s", due %s, from current group milestones' % (new.desc, new.due.strftime("%B %Y")),
by=login,
by=request.user.person,
)
return redirect('wg_edit_charter_milestones', acronym=group.acronym)
return redirect('group_edit_charter_milestones', group_type=group.type_id, acronym=group.acronym)
return render_to_response('wginfo/reset_charter_milestones.html',
dict(group=group,
charter_milestones=charter_milestones,
current_milestones=current_milestones,
),
context_instance=RequestContext(request))
return render(request, 'group/reset_charter_milestones.html',
dict(group=group,
charter_milestones=charter_milestones,
current_milestones=current_milestones,
))
def ajax_search_docs(request, acronym):
def ajax_search_docs(request, group_type, acronym):
docs = Document.objects.filter(name__icontains=request.GET.get('q',''), type="draft").order_by('name').distinct()[:20]
return HttpResponse(json_doc_names(docs), content_type='application/json')

View file

@ -19,7 +19,7 @@ from ietf.utils.test_utils import TestCase
from ietf.utils.mail import outbox
from ietf.utils.test_data import make_test_data
from ietf.utils.test_utils import login_testing_unauthorized
from ietf.wginfo.mails import ( email_milestone_review_reminder, email_milestones_due,
from ietf.group.mails import ( email_milestone_review_reminder, email_milestones_due,
email_milestones_overdue, groups_needing_milestones_due_reminder,
groups_needing_milestones_overdue_reminder, groups_with_milestones_needing_review )
@ -32,11 +32,11 @@ class GroupPagesTests(TestCase):
def tearDown(self):
shutil.rmtree(self.charter_dir)
def test_active_wgs(self):
def test_active_groups(self):
draft = make_test_data()
group = draft.group
url = urlreverse('ietf.wginfo.views.active_wgs')
url = urlreverse('ietf.group.info.active_groups', kwargs=dict(group_type="wg"))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue(group.parent.name in r.content)
@ -44,6 +44,10 @@ class GroupPagesTests(TestCase):
self.assertTrue(group.name in r.content)
self.assertTrue(group.ad.plain_name() in r.content)
url = urlreverse('ietf.group.info.active_groups', kwargs=dict(group_type="rg"))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
def test_wg_summaries(self):
draft = make_test_data()
group = draft.group
@ -53,7 +57,7 @@ class GroupPagesTests(TestCase):
with open(os.path.join(self.charter_dir, "%s-%s.txt" % (group.charter.canonical_name(), group.charter.rev)), "w") as f:
f.write("This is a charter.")
url = urlreverse('ietf.wginfo.views.wg_summary_area')
url = urlreverse('ietf.group.info.wg_summary_area', kwargs=dict(group_type="wg"))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue(group.parent.name in r.content)
@ -61,14 +65,14 @@ class GroupPagesTests(TestCase):
self.assertTrue(group.name in r.content)
self.assertTrue(chair.address in r.content)
url = urlreverse('ietf.wginfo.views.wg_summary_acronym')
url = urlreverse('ietf.group.info.wg_summary_acronym', kwargs=dict(group_type="wg"))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue(group.acronym in r.content)
self.assertTrue(group.name in r.content)
self.assertTrue(chair.address in r.content)
url = urlreverse('ietf.wginfo.views.wg_charters')
url = urlreverse('ietf.group.info.wg_charters', kwargs=dict(group_type="wg"))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue(group.acronym in r.content)
@ -77,7 +81,7 @@ class GroupPagesTests(TestCase):
self.assertTrue(chair.address in r.content)
self.assertTrue("This is a charter." in r.content)
url = urlreverse('ietf.wginfo.views.wg_charters_by_acronym')
url = urlreverse('ietf.group.info.wg_charters_by_acronym', kwargs=dict(group_type="wg"))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue(group.acronym in r.content)
@ -86,24 +90,36 @@ class GroupPagesTests(TestCase):
self.assertTrue(chair.address in r.content)
self.assertTrue("This is a charter." in r.content)
def test_chartering_wgs(self):
def test_chartering_groups(self):
draft = make_test_data()
group = draft.group
group.charter.set_state(State.objects.get(used=True, type="charter", slug="intrev"))
url = urlreverse('ietf.wginfo.views.chartering_wgs')
url = urlreverse('ietf.group.info.chartering_groups')
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('table.ietf-doctable td.acronym a:contains("%s")' % group.acronym)), 1)
def test_concluded_groups(self):
draft = make_test_data()
group = draft.group
group.state = GroupStateName.objects.get(used=True, slug="conclude")
group.save()
url = urlreverse('ietf.group.info.concluded_groups')
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('table.concluded-groups a:contains("%s")' % group.acronym)), 1)
def test_bofs(self):
draft = make_test_data()
group = draft.group
group.state_id = "bof"
group.save()
url = urlreverse('ietf.wginfo.views.bofs')
url = urlreverse('ietf.group.info.bofs', kwargs=dict(group_type="wg"))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
@ -137,7 +153,7 @@ class GroupPagesTests(TestCase):
name=draft2.name,
)
url = urlreverse('ietf.wginfo.views.group_documents', kwargs=dict(acronym=group.acronym))
url = urlreverse('ietf.group.info.group_documents', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue(draft.name in r.content)
@ -147,7 +163,7 @@ class GroupPagesTests(TestCase):
self.assertTrue(draft2.name in r.content)
# test the txt version too while we're at it
url = urlreverse('ietf.wginfo.views.group_documents_txt', kwargs=dict(acronym=group.acronym))
url = urlreverse('ietf.group.info.group_documents_txt', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue(draft.name in r.content)
@ -167,7 +183,7 @@ class GroupPagesTests(TestCase):
due=datetime.date.today() + datetime.timedelta(days=100))
milestone.docs.add(draft)
url = urlreverse('ietf.wginfo.views.group_charter', kwargs=dict(acronym=group.acronym))
url = urlreverse('ietf.group.info.group_charter', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue(group.name in r.content)
@ -186,7 +202,7 @@ class GroupPagesTests(TestCase):
type="added_comment",
by=Person.objects.get(name="(System)"))
url = urlreverse('ietf.wginfo.views.history', kwargs=dict(acronym=group.acronym))
url = urlreverse('ietf.group.info.history', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue(e.desc in r.content)
@ -225,7 +241,7 @@ class GroupEditTests(TestCase):
def test_create(self):
make_test_data()
url = urlreverse('wg_create')
url = urlreverse('group_create', kwargs=dict(group_type="wg"))
login_testing_unauthorized(self, "secretary", url)
num_wgs = len(Group.objects.filter(type="wg"))
@ -273,7 +289,7 @@ class GroupEditTests(TestCase):
def test_create_based_on_existing(self):
make_test_data()
url = urlreverse('wg_create')
url = urlreverse('group_create', kwargs=dict(group_type="wg"))
login_testing_unauthorized(self, "secretary", url)
group = Group.objects.get(acronym="mars")
@ -308,7 +324,7 @@ class GroupEditTests(TestCase):
make_test_data()
group = Group.objects.get(acronym="mars")
url = urlreverse('group_edit', kwargs=dict(acronym=group.acronym))
url = urlreverse('group_edit', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
login_testing_unauthorized(self, "secretary", url)
# normal get
@ -380,7 +396,7 @@ class GroupEditTests(TestCase):
group = Group.objects.get(acronym="mars")
url = urlreverse('wg_conclude', kwargs=dict(acronym=group.acronym))
url = urlreverse('ietf.group.edit.conclude', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
login_testing_unauthorized(self, "secretary", url)
# normal get
@ -435,7 +451,7 @@ class MilestoneTests(TestCase):
def test_milestone_sets(self):
m1, m2, group = self.create_test_milestones()
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
url = urlreverse('group_edit_milestones', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
login_testing_unauthorized(self, "secretary", url)
r = self.client.get(url)
@ -443,7 +459,7 @@ class MilestoneTests(TestCase):
self.assertTrue(m1.desc in r.content)
self.assertTrue(m2.desc not in r.content)
url = urlreverse('wg_edit_charter_milestones', kwargs=dict(acronym=group.acronym))
url = urlreverse('group_edit_charter_milestones', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
@ -453,7 +469,7 @@ class MilestoneTests(TestCase):
def test_add_milestone(self):
m1, m2, group = self.create_test_milestones()
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
url = urlreverse('group_edit_milestones', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
login_testing_unauthorized(self, "secretary", url)
# normal get
@ -505,7 +521,7 @@ class MilestoneTests(TestCase):
def test_add_milestone_as_chair(self):
m1, m2, group = self.create_test_milestones()
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
url = urlreverse('group_edit_milestones', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
login_testing_unauthorized(self, "marschairman", url)
# normal get
@ -539,7 +555,7 @@ class MilestoneTests(TestCase):
m1.state_id = "review"
m1.save()
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
url = urlreverse('group_edit_milestones', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
login_testing_unauthorized(self, "ad", url)
# normal get
@ -569,7 +585,7 @@ class MilestoneTests(TestCase):
def test_delete_milestone(self):
m1, m2, group = self.create_test_milestones()
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
url = urlreverse('group_edit_milestones', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
login_testing_unauthorized(self, "secretary", url)
milestones_before = GroupMilestone.objects.count()
@ -597,7 +613,7 @@ class MilestoneTests(TestCase):
def test_edit_milestone(self):
m1, m2, group = self.create_test_milestones()
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
url = urlreverse('group_edit_milestones', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
login_testing_unauthorized(self, "secretary", url)
milestones_before = GroupMilestone.objects.count()
@ -654,7 +670,7 @@ class MilestoneTests(TestCase):
def test_reset_charter_milestones(self):
m1, m2, group = self.create_test_milestones()
url = urlreverse('wg_reset_charter_milestones', kwargs=dict(acronym=group.acronym))
url = urlreverse('group_reset_charter_milestones', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
login_testing_unauthorized(self, "secretary", url)
# normal get
@ -809,7 +825,7 @@ class MilestoneTests(TestCase):
def test_ajax_search_docs(self):
draft = make_test_data()
r = self.client.get(urlreverse("wg_ajax_search_docs", kwargs=dict(acronym=draft.group.acronym)),
r = self.client.get(urlreverse("group_ajax_search_docs", kwargs=dict(group_type=draft.group.type_id, acronym=draft.group.acronym)),
dict(q=draft.name))
self.assertEqual(r.status_code, 200)
data = json.loads(r.content)
@ -821,7 +837,7 @@ class CustomizeWorkflowTests(TestCase):
group = Group.objects.get(acronym="mars")
url = urlreverse('ietf.wginfo.edit.customize_workflow', kwargs=dict(acronym=group.acronym))
url = urlreverse('ietf.group.edit.customize_workflow', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
login_testing_unauthorized(self, "secretary", url)
state = State.objects.get(used=True, type="draft-stream-ietf", slug="wg-lc")

View file

@ -4,6 +4,9 @@ from django.conf.urls import patterns
urlpatterns = patterns('',
(r'^(?P<acronym>[a-z0-9]+).json$', 'ietf.group.ajax.group_json'),
(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'),
)

35
ietf/group/urls_info.py Normal file
View file

@ -0,0 +1,35 @@
# Copyright The IETF Trust 2008, All Rights Reserved
from django.conf.urls import patterns
from django.views.generic import RedirectView
from ietf.group import info, edit, milestones
urlpatterns = patterns('',
(r'^$', info.active_groups),
(r'^summary.txt', RedirectView.as_view(url='/wg/1wg-summary.txt')),
(r'^summary-by-area.txt', RedirectView.as_view(url='/wg/1wg-summary.txt')),
(r'^summary-by-acronym.txt', RedirectView.as_view(url='/wg/1wg-summary-by-acronym.txt')),
(r'^1wg-summary.txt', info.wg_summary_area),
(r'^1wg-summary-by-acronym.txt', info.wg_summary_acronym),
(r'^1wg-charters.txt', info.wg_charters),
(r'^1wg-charters-by-acronym.txt', info.wg_charters_by_acronym),
(r'^chartering/$', RedirectView.as_view(url='/group/chartering/')),
(r'^chartering/create/$', RedirectView.as_view(url='/group/chartering/create/%(group_type)s/')),
(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-._]+)/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),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/init-charter/', edit.submit_initial_charter),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/edit/$', edit.edit, {'action': "edit"}, "group_edit"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/conclude/$', edit.conclude),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/$', milestones.edit_milestones, {'milestone_set': "current"}, "group_edit_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/charter/$', milestones.edit_milestones, {'milestone_set': "charter"}, "group_edit_charter_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/charter/reset/$', milestones.reset_charter_milestones, None, "group_reset_charter_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/ajax/searchdocs/$', milestones.ajax_search_docs, None, "group_ajax_search_docs"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/workflow/$', edit.customize_workflow),
)

View file

@ -1,10 +1,9 @@
import os
from django.conf import settings
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
from ietf.ietfauth.utils import has_role
def save_group_in_history(group):
@ -27,28 +26,21 @@ def save_group_in_history(group):
def get_charter_text(group):
# get file path from settings. Syntesize file name from path, acronym, and suffix
c = group.charter
# find the latest, preferably approved, revision
for h in group.charter.history_set.exclude(rev="").order_by("time"):
h_appr = "-" not in h.rev
c_appr = "-" not in c.rev
if (h.rev > c.rev and not (c_appr and not h_appr)) or (h_appr and not c_appr):
c = h
filename = os.path.join(c.get_file_path(), "%s-%s.txt" % (c.canonical_name(), c.rev))
try:
# Try getting charter from new charter tool
c = group.charter
# find the latest, preferably approved, revision
for h in group.charter.history_set.exclude(rev="").order_by("time"):
h_appr = "-" not in h.rev
c_appr = "-" not in c.rev
if (h.rev > c.rev and not (c_appr and not h_appr)) or (h_appr and not c_appr):
c = h
filename = os.path.join(c.get_file_path(), "%s-%s.txt" % (c.canonical_name(), c.rev))
with open(filename) as f:
return f.read()
except IOError:
try:
filename = os.path.join(settings.IETFWG_DESCRIPTIONS_PATH, group.acronym) + ".desc.txt"
desc_file = open(filename)
desc = desc_file.read()
except BaseException:
desc = 'Error Loading Work Group Description'
return desc
return 'Error Loading Group Charter'
def get_area_ads_emails(area):
if area.acronym == 'none':
@ -98,3 +90,18 @@ def save_milestone_in_history(milestone):
copy_many_to_many_for_history(h, milestone)
return h
def can_manage_group_type(user, group_type):
if group_type == "rg":
return has_role(user, ('IRTF Chair', 'Secretariat'))
elif group_type == "wg":
return has_role(user, ('Area Director', 'Secretariat'))
return has_role(user, 'Secretariat')
def milestone_reviewer_for_group_type(group_type):
if group_type == "rg":
return "IRTF Chair"
else:
return "Area Director"

View file

@ -199,7 +199,6 @@ INSTALLED_APPS = (
'ietf.meeting',
'ietf.utils',
'ietf.redirects',
'ietf.wginfo',
'ietf.submit',
'ietf.sync',
'ietf.community',
@ -268,7 +267,6 @@ STATUS_CHANGE_PATH = '/a/www/ietf-ftp/status-changes'
STATUS_CHANGE_TXT_URL = 'http://www.ietf.org/sc/'
AGENDA_PATH = '/a/www/www6s/proceedings/'
IPR_DOCUMENT_PATH = '/a/www/ietf-ftp/ietf/IPR/'
IETFWG_DESCRIPTIONS_PATH = '/a/www/www6s/wg-descriptions/'
IESG_TASK_FILE = '/a/www/www6/iesg/internal/task.txt'
IESG_ROLL_CALL_FILE = '/a/www/www6/iesg/internal/rollcall.txt'
IESG_MINUTES_FILE = '/a/www/www6/iesg/internal/minutes.txt'

View file

@ -254,7 +254,7 @@ class EditSubmissionForm(forms.ModelForm):
return document_date
class PreapprovalForm(forms.Form):
name = forms.CharField(max_length=255, required=True, label="Pre-approved name", initial="draft-ietf-")
name = forms.CharField(max_length=255, required=True, label="Pre-approved name", initial="draft-")
def clean_name(self):
n = self.cleaned_data['name'].strip().lower()
@ -268,7 +268,7 @@ class PreapprovalForm(forms.Form):
if components[-1] == "00":
raise forms.ValidationError("Name appears to end with a revision number -00 - do not include the revision.")
if len(components) < 4:
raise forms.ValidationError("Name has less than four dash-delimited components - can't form a valid WG draft name.")
raise forms.ValidationError("Name has less than four dash-delimited components - can't form a valid group draft name.")
if not components[-1]:
raise forms.ValidationError("Name ends with a dash.")
acronym = components[2]

View file

@ -331,7 +331,7 @@ def preapprovals_for_user(user):
if has_role(user, "Secretariat"):
return res
acronyms = [g.acronym for g in Group.objects.filter(role__person__user=user, type="wg")]
acronyms = [g.acronym for g in Group.objects.filter(role__person__user=user, type__in=("wg", "rg"))]
res = res.filter(name__regex="draft-[^-]+-(%s)-.*" % "|".join(acronyms))

View file

@ -401,9 +401,9 @@ def approvals(request):
context_instance=RequestContext(request))
@role_required("Secretariat", "WG Chair")
@role_required("Secretariat", "WG Chair", "RG Chair")
def add_preapproval(request):
groups = Group.objects.filter(type="wg").exclude(state="conclude").order_by("acronym").distinct()
groups = Group.objects.filter(type__in=("wg", "rg")).exclude(state="conclude").order_by("acronym").distinct()
if not has_role(request.user, "Secretariat"):
groups = groups.filter(role__person__user=request.user,role__name__in=['ad','chair','delegate','secr'])
@ -427,7 +427,7 @@ def add_preapproval(request):
'form': form },
context_instance=RequestContext(request))
@role_required("Secretariat", "WG Chair")
@role_required("Secretariat", "WG Chair", "RG Chair")
def cancel_preapproval(request, preapproval_id):
preapproval = get_object_or_404(Preapproval, pk=preapproval_id)

View file

@ -72,10 +72,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</ul>
</div></div></li>
<li style="padding-top:0;"><a href="/wg/">Active WGs</a></li>
<li><a href="{% url "ietf.wginfo.views.chartering_wgs" %}">Chartering WGs</a></li>
<li><a href="{% url "ietf.wginfo.views.bofs" %}">BoFs</a></li>
<li><a href="http://tools.ietf.org/wg/concluded">Concluded WGs</a></li>
<li style="padding-top:0;"><a href="{% url "ietf.group.info.active_groups" group_type="wg" %}">Active WGs</a></li>
<li style="padding-top:0;"><a href="{% url "ietf.group.info.active_groups" group_type="rg" %}">Active RGs</a></li>
<li><a href="{% url "ietf.group.info.chartering_groups" %}">Chartering</a></li>
<li><a href="{% url "ietf.group.info.bofs" group_type="wg" %}">BoFs</a></li>
<li><a href="{% url "ietf.group.info.concluded_groups" %}">Concluded</a></li>
<li><a href="http://www.ietf.org/list/nonwg.html">Non-WG Lists</a></li>

View file

@ -14,7 +14,7 @@ Charter submission for {{ group.acronym }} {{ group.type.name }}
{% block content %}
<h1>Charter submission for {{ group.acronym }} {{ group.type.name }}</h1>
<p>The text will be submitted as <strong>charter-ietf-{{ group.acronym }}-{{ next_rev }}</strong></p>
<p>The text will be submitted as <strong>{{ name }}-{{ next_rev }}</strong></p>
<form class="edit-info" action="" enctype="multipart/form-data" method="post">{% csrf_token %}
<table>
{% for field in form.visible_fields %}

View file

@ -25,7 +25,7 @@
{% if snapshot %}Snapshot of{% endif %}
{% if doc.get_state_slug != "approved" %}Proposed{% endif %}
Charter for "{{ group.name }}"
(<a {% if group.type.slug == "wg" %}href="{% url "ietf.wginfo.views.group_charter" acronym=group.acronym %}"{% endif %}>{{ group.acronym }}</a>) {{ group.type.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 }}
</div>
<table id="metatable" width="100%">
@ -38,7 +38,7 @@
<td>
<div>
<a title="{{ doc.get_state.desc }}"
{% if not snapshot and user|has_role:"Area Director,Secretariat" %}
{% if not snapshot and can_manage %}
class="editlink" href="{% url "charter_change_state" name=doc.name %}"
{% endif %}>
{{ doc.get_state.name }}
@ -69,7 +69,7 @@
<tr>
<td>Responsible AD:</td>
<td><a {% if request.user|has_role:"Area Director,Secretariat" %}class="editlink" href="{% url "charter_edit_ad" name=doc.name %}"{% endif %}>{{ doc.ad|default:"none" }}</a> </td>
<td><a {% if user|has_role:"Area Director,Secretariat" %}class="editlink" href="{% url "charter_edit_ad" name=doc.name %}"{% endif %}>{{ doc.ad|default:"none" }}</a> </td>
</tr>
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
@ -94,13 +94,13 @@
</div>
<div class="actions">
{% if not snapshot and user|has_role:"Area Director,Secretariat" %}
{% if not snapshot and can_manage %}
{% if chartering %}
{% url "charter_startstop_process" name=doc.name option='abandon' as abandon_url %}{% if abandon_url %}<a class="button" href="{{ abandon_url }}">Abandon Effort</a>{% endif %}
{% if request.user|has_role:"Secretariat" %}
{% url "charter_approve" name=doc.name as approve_url %}{% if approve_url %}<a class="button" href="{{ approve_url }}">Approve Charter</a>{% endif %}
{% endif %}
{% if user|has_role:"Secretariat" %}
{% url "charter_approve" name=doc.name as approve_url %}{% if approve_url %}<a class="button" href="{{ approve_url }}">Approve Charter</a>{% endif %}
{% endif %}
{% else %}
{% if group.state_id == "proposed" or group.state_id == "bof" %}
@ -118,7 +118,7 @@
<h3>Charter {{ doc.canonical_name }}-{{ doc.rev }}
{% if not snapshot and user|has_role:"Area Director,Secretariat" and chartering and group.state_id != "conclude" %}
{% if not snapshot and can_manage and chartering and group.state_id != "conclude" %}
<a class="edit" href="{% url "charter_submit" name=doc.name %}">Change charter text</a>
{% endif %}
</h3>
@ -131,13 +131,13 @@
{% if not snapshot and chartering %}
<h3>Proposed Milestones
{% if user|has_role:"Area Director,Secretariat" %}
<a class="edit" href="{% url "wg_edit_charter_milestones" acronym=doc.group.acronym %}">Edit charter milestones</a>
{% if can_manage %}
<a class="edit" href="{% url "group_edit_charter_milestones" group_type=doc.group.type_id acronym=doc.group.acronym %}">Edit charter milestones</a>
{% endif %}
</h3>
{% if milestones %}
{% include "wginfo/milestones.html" %}
{% include "group/milestones.html" %}
{% else %}
<p>No milestones for charter found.</p>
{% endif %}

View file

@ -36,7 +36,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<div class="search-results">
{% if not docs %}
<h2>No documents match your query.</h2>
{% if not skip_no_matches_warning %}
<h2>No documents match your query.</h2>
{% endif %}
{% else %}
{% if meta.max %}

View file

@ -0,0 +1,4 @@
{% autoescape off %}{% load ietf_filters %}{% for group in groups %}{{ group.acronym }}
{% endfor %}
{% for group in groups %}{% include "group/group_entry_with_charter.txt" %}{% endfor %}{% endautoescape %}

View file

@ -1,6 +1,6 @@
{% autoescape off %}{% load ietf_filters %}{% for area in areas %}{% for group in area.groups %}{{ group.acronym }}
{% endfor %}{% endfor %}
{% for area in areas %}{% for group in area.groups %}{% include "wginfo/group_entry_with_charter.txt" %}{% endfor %}{% endfor %}{% endautoescape %}
{% for area in areas %}{% for group in area.groups %}{% include "group/group_entry_with_charter.txt" %}{% endfor %}{% endfor %}{% endautoescape %}

View file

@ -7,4 +7,4 @@ The following Area Abbreviations are used in this document
{{ area.acronym|upper }} - {{ area.name }}{% endfor %}
{% for group in groups %}
{{ group.name }} ({{ group.acronym }}) -- {{ group.parent.acronym|upper }}
{% include "wginfo/group_entry.txt" %}{% endfor %}{% endautoescape %}
{% include "group/group_entry.txt" %}{% endfor %}{% endautoescape %}

View file

@ -7,5 +7,5 @@
{{ ad.person.plain_name }} <{{ ad.email.address }}>{% endfor %}
{% for group in area.groups %}{{ group.name }} ({{ group.acronym }})
{% include "wginfo/group_entry.txt" %}
{% include "group/group_entry.txt" %}
{% endfor %}{% endfor %}{% endautoescape %}

View file

@ -0,0 +1,33 @@
{% extends "base.html" %}
{% block title %}Active IRTF Research Groups{% endblock %}
{% block morecss %}
.ietf-wg-table { width: 100%; max-width:50em; }
.ietf-wg-table tr { vertical-align:top; }
{% endblock morecss %}
{% block content %}
<h1>Active IRTF Research Groups</h1>
<p>IRTF Chair:</p>
<div style="margin-left:2em;">
<a href="mailto:irtf-chair@irtf.org">{{ irtf.chair.person.plain_name }}</a>
</div>
<p>Active Research Groups:</p>
<div style="margin-left:2em;">
<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="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>
{% endfor %}
</table>
</div>
{% endblock %}

View file

@ -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="/wg/{{ group.acronym }}/">{{ group.acronym }}</a></td>
<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="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>

View file

@ -10,7 +10,7 @@
<p>Groups in the BoF state</p>
{% if user|has_role:"Area Director,Secretariat" %}
<p><a href="{% url "bof_create" %}">Create a new BoF</a></p>
<p><a href="{% url "bof_create" group_type="wg" %}">Create a new BoF</a></p>
{% endif %}
{% if not groups %}
@ -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" acronym=g.acronym %}">{{ g.acronym }}</a>
<a href="{% url "group_charter" group_type=g.type_id acronym=g.acronym %}">{{ g.acronym }}</a>
</td>
<td class="title">
<a {%comment%}href="{% url "doc_view" name=g.charter.name %}"{%endcomment%}>{{ g.name }}</a>

View file

@ -0,0 +1,65 @@
{% extends "base.html" %}
{% block title %}Chartering or Re-Chartering Groups{% endblock %}
{% block content %}
{% load ietf_filters %}
{% load ballot_icon %}
<h1>Chartering or Re-Chartering Groups</h1>
<p>Groups with a charter in state
{% for s in charter_states %}{% if not forloop.first %}, {% if forloop.last %}or {% endif %}{% endif %}<i>{{ s.name }}</i>{% endfor %}.
</p>
{% for t in group_types %}
<h2>{{ t.name }}s</h2>
{% if t.can_manage %}
<p><a href="{% url "group_create" group_type=t.pk %}">Start chartering new {{ t.name }}</a></p>
{% endif %}
{% if not t.chartering_groups %}
<p><b>No groups found.</b></p>
{% else %}
<table class="ietf-table ietf-doctable">
<tr>
<th>Group</th>
<th>Charter</th>
<th>Date</th>
<th colspan="2">Status</th>
</tr>
{% 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>
</td>
<td class="title">
<a href="{% url "doc_view" name=g.charter.name %}">{{ g.name }}</a>
</td>
<td class="date">{{ g.charter.time|date:"Y-m-d" }}</td>
<td class="status">
{{ g.charter.get_state.name }}
{% if g.chartering_type == "initial" %}(Initial Chartering){% endif %}
{% if g.chartering_type == "recharter" %}(Rechartering){% endif %}
{% if not g.chartering_type and g.state_id != "active" %}({{ g.state.name }}){% endif %}
{% if g.charter.telechat_date %}<br/>IESG Telechat: {{ g.charter.telechat_date|date:"Y-m-d" }}{% endif %}
</td>
<td class="ballot">
{% ballot_icon g.charter %}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endfor %}
{% endblock %}
{% block js %}
<script type="text/javascript" src="/js/utils.js"></script>
<script type="text/javascript" src="/js/doc-search.js"></script>
{% endblock %}

View file

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Request closing of {{ wg.acronym }} {{ wg.type.name }}{% endblock %}
{% block title %}Request closing of {{ group.acronym }} {{ group.type.name }}{% endblock %}
{% block morecss %}
#id_instructions {
@ -14,7 +14,7 @@ form.conclude .actions {
{% endblock %}
{% block content %}
<h1>Request closing of {{ wg.acronym }} {{ wg.type.name }}</h1>
<h1>Request closing of {{ group.acronym }} {{ group.type.name }}</h1>
<p>
Please provide instructions regarding the disposition of each
@ -29,7 +29,7 @@ form.conclude .actions {
{{ form.as_table }}
<tr>
<td colspan="2" class="actions">
<a class="button" href="{% url "group_charter" acronym=wg.acronym %}">Cancel</a>
<a class="button" href="{% url "group_charter" group_type=group.type_id acronym=group.acronym %}">Cancel</a>
<input class="button" type="submit" value="Send request"/>
</td>
</tr>

View file

@ -0,0 +1,42 @@
{% extends "base.html" %}
{% block title %}Concluded Groups{% endblock %}
{% block morecss %}
.concluded-groups .active-period { display: inline-block; margin-left: 0.5em; font-style: italic; }
.note { font-style: italic; }
{% endblock %}
{% block content %}
<h1>Concluded Groups</h1>
<p>Note that the information on historical groups may be inaccurate.</p>
{% for t in group_types %}
<h2>{{ t.name }}s</h2>
{% if t.slug == "wg" %}<p class="note">Some additional concluded WGs may
be present at <a href="http://tools.ietf.org/wg/concluded">tools.ietf.org/wg/concluded/</a></p>{% endif %}
{% if not t.concluded_groups %}
<p><b>No groups found.</b></p>
{% else %}
<table class="concluded-groups">
{% for g in t.concluded_groups %}
<tr>
<td>
<a href="{% url "group_charter" group_type=g.type_id acronym=g.acronym %}">{{ g.acronym }}</a>
</td>
<td>{{ g.name }}
<span class="active-period">({% if g.start_date %}{{ g.start_date|date:"M. Y" }}{% else %}?{% endif %}
-
{% if g.conclude_date %}{{ g.conclude_date|date:"M. Y" }}{% else %}?{% endif %})</span>
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endfor %}
{% endblock %}

View file

@ -34,12 +34,13 @@
<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.acronym %}">{{ group.acronym }} {{ group.type.name }}</a>. Note that some states are
<a href="{% url "group_charter" group_type=group.type_id acronym=group.acronym %}">{{ 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" %}
<p>You can see the default Working Group I-D State Diagram
in <a href="http://tools.ietf.org/html/rfc6174#section-4.1">Section 4.1 of RFC6174</a>.</p>
{% endif %}
<h3>States</h3>

View file

@ -15,11 +15,11 @@ digraph draftdeps {
fontcolor=black,
label="Colors in\nthis row"];
key_wgdoc [color="#0AFE47",
label="Product of\nthis WG",
label="Product of\nthis group",
style=filled,
wg=this];
key_otherwgdoc [color="#9999FF",
label="Product of\nother WG",
label="Product of\nother group",
style=filled,
wg=blort];
key_individual [color="#FF800D",

View file

@ -1,10 +1,10 @@
{% extends "base.html" %}
{% block title %}
{% if wg %}
Edit WG {{ wg.acronym }}
{% if group %}
Edit {{ group.type.name }} {{ group.acronym }}
{% else %}
Start chartering new WG
Start chartering new group
{% endif %}
{% endblock %}
@ -28,12 +28,12 @@ form.edit #id_urls { height: 4em; }
{% load ietf_filters %}
<h1>
{% if action == "edit" %}
Edit WG {{ wg.acronym }}
Edit {{ group.type.name }} {{ group.acronym }}
{% else %}
{% if action == "charter" %}
Start chartering new WG
Start chartering new group
{% else %}
Create new WG or BoF
Create new group or BoF
{% endif %}
{% endif %}
</h1>
@ -43,13 +43,14 @@ chairs and delegates, need a Datatracker account to actually do
so. New accounts can be <a href="{% url "create_account" %}">created here</a>.</p>
<form class="edit" action="" method="post">{% csrf_token %}
{% for field in form.hidden_fields %}{{ field }}{% endfor %}
<table>
{% for field in form.visible_fields %}
<tr>
<th>{{ field.label_tag }} {% if field.field.required %}*{% endif %}</th>
<td>{{ field }}
{% if field.name == "ad" and user|has_role:"Area Director" %}
<label><input type="checkbox" name="ad" value="{{ login.pk }}" /> Assign to me</label>
{% if field.name == "ad" and request.user|has_role:"Area Director" %}
<label><input type="checkbox" name="ad" value="{{ request.user.person.pk }}" /> Assign to me</label>
{% endif %}
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
{{ field.errors }}
@ -68,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" acronym=wg.acronym %}">Cancel</a>
<a class="button" href="{% url "group_charter" group_type=group.type_id acronym=group.acronym %}">Cancel</a>
<input class="button" type="submit" value="Save"/>
{% else %}
{% if action == "charter" %}

View file

@ -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" acronym=group.acronym %}">{{ group.acronym }} {{ group.type.name }}</a>
<a href="{% url "group_charter" group_type=group.type_id acronym=group.acronym %}">{{ group.acronym }} {{ group.type.name }}</a>
- <a href="{% url "doc_view" name=group.charter.canonical_name %}">{{ group.charter.canonical_name }}</a>
</p>
@ -51,14 +51,13 @@ tr.milestone.add { font-style: italic; }
{% if needs_review %}
Note that as {{ group.type.name }} Chair you cannot edit descriptions of existing
milestones and milestones you add are subject to review by the Area
Director.
milestones and milestones you add are subject to review by the {{ reviewer }}.
{% endif %}
</p>
{% if can_reset %}
<p>
You can <a href="{% url "wg_reset_charter_milestones" acronym=group.acronym %}">reset
You can <a href="{% url "group_reset_charter_milestones" group_type=group.type_id acronym=group.acronym %}">reset
this list</a> to the milestones currently in use for the {{ group.acronym }} {{ group.type.name }}.
</p>
{% endif %}
@ -84,14 +83,14 @@ this list</a> to the milestones currently in use for the {{ group.acronym }} {{
</td>
</tr>
<tr class="edit-milestone{% if form.changed %} changed{% endif %}"><td colspan="2">{% include "wginfo/milestone_form.html" %}</td></tr>
<tr class="edit-milestone{% if form.changed %} changed{% endif %}"><td colspan="2">{% include "group/milestone_form.html" %}</td></tr>
{% endfor %}
<tr class="milestone add"><td></td><td>Add {% if milestone_set == "chartering" %}charter{% endif%} milestone {% if needs_review %}for AD review{% endif %}</td></tr>
<tr class="edit-milestone template"><td colspan="2">{% include "wginfo/milestone_form.html" with form=empty_form %}</td></tr>
<tr class="milestone add"><td></td><td>Add {% if milestone_set == "chartering" %}charter{% endif%} milestone {% if needs_review %}for {{ reviewer }} review{% endif %}</td></tr>
<tr class="edit-milestone template"><td colspan="2">{% include "group/milestone_form.html" with form=empty_form %}</td></tr>
</table>
<div class="actions">
<a class="button" href="{% if milestone_set == "charter" %}{% url "doc_view" name=group.charter.canonical_name %}{% else %}{% url "group_charter" acronym=group.acronym %}{% endif %}">Cancel</a>
<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>
<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>

View file

@ -66,15 +66,15 @@ 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.wginfo.views.group_documents" acronym=group.acronym %}"{% endif %}>Documents</a> |
<a {% if selected == "charter" %}class="selected"{% else %}href="{% url "ietf.wginfo.views.group_charter" acronym=group.acronym %}"{% endif %}>Charter</a> |
<a {% if selected == "history" %}class="selected"{% else %}href="{% url "ietf.wginfo.views.history" acronym=group.acronym %}"{% endif %}>History</a>
| <a href="{% url 'ietf.wginfo.views.dependencies_pdf' acronym=group.acronym %}">Dependency Graph</a>
<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 &raquo;</a>
{% endif %}
{% if group.has_tools_page %}
| <a href="http://tools.ietf.org/wg/{{ group.acronym }}/">Tools WG Page &raquo;</a>
| <a href="http://tools.ietf.org/{{ group.type_id }}/{{ group.acronym }}/">Tools {{ group.type.name }} Page &raquo;</a>
{% endif %}
</div>

View file

@ -1,4 +1,4 @@
{% extends "wginfo/group_base.html" %}
{% extends "group/group_base.html" %}
{% load ietf_filters %}
{% block group_subtitle %}Charter{% endblock %}
@ -12,7 +12,7 @@ h2 a.button { margin-left: 0.5em; font-size: 13px; }
<div class="ietf-box ietf-group-details">
{% if group.state_id == "conclude" %}
<span class="ietf-concluded-warning">Note: The data for concluded WGs
<span class="ietf-concluded-warning">Note: The data for concluded {{ group.type.name }}s
is occasionally incorrect.</span>
{% endif %}
@ -26,7 +26,7 @@ is occasionally incorrect.</span>
<tr><td>Acronym:</td><td>{{ group.acronym }}</td></tr>
{% if group.parent %}
{% 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 %}
@ -46,8 +46,8 @@ is occasionally incorrect.</span>
<a href="{% url "doc_view" name=group.charter.name %}">{{ group.charter.name }}-{{ group.charter.rev }}</a> ({{ group.charter.get_state.name }})
{% else %}
none
{% if user|has_role:"Area Director,Secretariat" %}
- <a href="{% url "ietf.wginfo.edit.submit_initial_charter" acronym=group.acronym %}">Submit Charter</a>
{% 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>
@ -64,6 +64,7 @@ is occasionally incorrect.</span>
</td>
</tr>
{% if group.parent.type_id == "area" %}
<tr><td>Area Director:</td>
<td>
{% if group.areadirector %}
@ -71,6 +72,7 @@ is occasionally incorrect.</span>
{% else %}?{% endif %}
</td>
</tr>
{% endif %}
{% if group.techadvisors %}
<tr>
@ -142,7 +144,7 @@ is occasionally incorrect.</span>
{% with group.groupurl_set.all as urls %}
{% if urls %}
<p>In addition to the charter maintained by the IETF Secretariat, there is additional information about this working group on the Web at:
<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 %}
@ -150,17 +152,17 @@ is occasionally incorrect.</span>
{% endif %}
{% endwith %}
<h2>Charter for {% if group.state_id == "proposed" %}Proposed{% endif %} Working Group</h2>
<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 "wginfo/milestones.html" with milestones=group.milestones %}
{% 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 Area Director review.</p>
currently in {{ milestone_reviewer }} review.</p>
{% endif %}
{% endblock %}

View file

@ -1,4 +1,4 @@
{% extends "wginfo/group_base.html" %}
{% extends "group/group_base.html" %}
{% block group_subtitle %}Documents{% endblock %}
@ -7,7 +7,7 @@
{% include "doc/search/search_results.html" %}
{% include "doc/search/search_results.html" with docs=docs_related meta=meta_related %}
{% include "doc/search/search_results.html" with docs=docs_related meta=meta_related skip_no_matches_warning=True %}
</div>
{% endblock group_content %}

View file

@ -1,4 +1,4 @@
{% extends "wginfo/group_base.html" %}
{% extends "group/group_base.html" %}
{% load ietf_filters %}
{% block group_subtitle %}History{% endblock %}

View file

@ -1,4 +1,4 @@
{# assumes group, form, needs_review are in the context #}
{# assumes group, form, needs_review, reviewer are in the context #}
<input type="hidden" name="prefix" value="{{ form.prefix|default:"" }}"/>
{{ form.id }}
@ -26,7 +26,7 @@
</tr>
<tr class="docs">
<td>Drafts:</td>
<td><input name="{{ form.docs.html_name }}" class="tokenized-field" data-ajax-url="{% url "wg_ajax_search_docs" group.acronym %}" data-pre="{{ form.docs_prepopulate }}"/>
<td><input name="{{ form.docs.html_name }}" class="tokenized-field" data-ajax-url="{% url "group_ajax_search_docs" group_type=group.type_id acronym=group.acronym %}" data-pre="{{ form.docs_prepopulate }}"/>
{{ form.docs.errors }}
</td>
</tr>
@ -35,7 +35,7 @@
<td>Review:</td>
<td class="accept">
This milestone is not active yet, awaiting
AD acceptance{% if needs_review %}.{% else %}: {{ form.accept }}{% endif %}
{{ reviewer }} acceptance{% if needs_review %}.{% else %}: {{ form.accept }}{% endif %}
</td>
</tr>
{% endif %}

View file

@ -1,4 +1,4 @@
{% autoescape off %}{% filter wordwrap:73 %}{{ milestones|length }} new milestone{{ milestones|pluralize }} in "{{ group.name }}" {% if milestones|length > 1 %}need{% else %}needs{%endif %} an AD review:
{% autoescape off %}{% filter wordwrap:73 %}{{ milestones|length }} new milestone{{ milestones|pluralize }} in "{{ group.name }}" {% if milestones|length > 1 %}need{% else %}needs{%endif %} review by the {{ reviewer }}:
{% for m in milestones %}"{{ m.desc }}"{% if m.days_ready != None %}
Waiting for {{ m.days_ready }} day{{ m.days_ready|pluralize }}.{% endif %}

View file

@ -29,7 +29,7 @@
{% endfor %}
<div class="actions">
<a href="{% url "wg_edit_charter_milestones" acronym=group.acronym %}">Back</a>
<a href="{% url "group_edit_charter_milestones" group_type=group.type_id acronym=group.acronym %}">Back</a>
<input type="submit" value="Reset charter milestones"/>
</div>
</form>

View file

@ -15,9 +15,9 @@ div.milestones-for-group { margin: 0.5em 0; }
{% for g in ad.groups_needing_review %}
<div class="milestones-for-group">{{ g.name }} ({{ g.acronym }}) <a href="{% url "wg_edit_milestones" acronym=g.acronym %}">has new milestones</a>:</div>
<div class="milestones-for-group">{{ g.name }} ({{ g.acronym }}) <a href="{% url "group_edit_milestones" group_type=g.type_id acronym=g.acronym %}">has new milestones</a>:</div>
{% include "wginfo/milestones.html" with milestones=g.milestones_needing_review %}
{% include "group/milestones.html" with milestones=g.milestones_needing_review %}
{% endfor %}
{% endfor %}

View file

@ -18,8 +18,8 @@ form .actions a { display: inline-block; margin-right: 1em; }
<h2>Add Pre-Approval</h2>
<p>You can register a pre-approved draft name. Then the WG Chair
approval step of WG -00 submissions is suspended for that draft name
<p>You can register a pre-approved draft name. Then the chair
approval step of group -00 submissions is suspended for that draft name
so a future submission is posted to the data tracker immediately.</p>
<p>When the revision 00 draft is submitted, the pre-approval will not
@ -33,12 +33,12 @@ later cancel the pre-approval to get rid of it.</p>
like <code>.txt</code> in the name.</p>
{% if user|has_role:"Secretariat" %}
<p>Only WG submissions are subject to approval and are thus pre-approvable.</p>
<p>Only group submissions are subject to approval and are thus pre-approvable.</p>
{% else %}
<p>As WG Chair{% if groups|length > 1 %} of {{ groups|length }} groups{% endif %} you can pre-approve draft names on the form:
<p>As chair{% if groups|length > 1 %} of {{ groups|length }} groups{% endif %} you can pre-approve draft names on the form (click to pre-fill):
<table>
{% for g in groups %}
<tr><td class="name-template">draft-ietf-{{ g.acronym }}-<i>something</i></td></tr>
<tr><td class="name-template">draft-{% if g.type_id == "rg"%}irtf{% else %}ietf{% endif %}-{{ g.acronym }}-<i>something</i></td></tr>
{% endfor %}
</table>
</p>
@ -56,8 +56,8 @@ like <code>.txt</code> in the name.</p>
<tr>
<td class="actions" colspan="2">
<a href="{% url "submit_approvals" %}#preapprovals">Back</a>
<input type="submit" value="Add pre-approval" />
<a class="button" href="{% url "submit_approvals" %}#preapprovals">Cancel</a>
<input class="button" type="submit" value="Add pre-approval" />
</td>
</tr>
</table>
@ -68,7 +68,8 @@ like <code>.txt</code> in the name.</p>
{% block scripts %}
jQuery(function () {
jQuery(".name-template").click(function (){
jQuery("form.add-preapproval input[name=name]").val(jQuery(this).text().replace("something", ""));
// clear text first to make sure the cursor ends up at the end
jQuery("form.add-preapproval input[name=name]").text("").focus().val(jQuery(this).text().replace("something", ""));
});
});
{% endblock %}

View file

@ -37,7 +37,7 @@ table.preapprovals tr:hover td a.cancel { visibility: visible; }
<h2 id="preapprovals">Pre-approved drafts not yet submitted</h2>
{% if user|has_role:"Secretariat,WG Chair" %}
{% if user|has_role:"Secretariat,WG Chair,RG Chair" %}
<p>You can <a href="{% url "submit_add_preapproval" %}">add a pre-approval</a>.</p>
{% endif %}

View file

@ -15,9 +15,9 @@ Pre-approval of <b>{{ preapproval.name }}</b> by {{ preapproval.by }}
on {{ preapproval.time }}.
<form class="actions" action="" method="post">{% csrf_token %}
<a href="{% url "submit_approvals" %}#preapprovals">Back</a>
<a class="button" href="{% url "submit_approvals" %}#preapprovals">Don't cancel</a>
<input type="hidden" name="action" value="cancel" />
<input type="submit" value="Cancel pre-approval" />
<input class="button" type="submit" value="Cancel pre-approval" />
</form>
{% endblock %}

View file

@ -1,4 +0,0 @@
{% autoescape off %}{% load ietf_filters %}{% for group in groups %}{{ group.acronym }}
{% endfor %}
{% for group in groups %}{% include "wginfo/group_entry_with_charter.txt" %}{% endfor %}{% endautoescape %}

View file

@ -1,58 +0,0 @@
{% extends "base.html" %}
{% block title %}Chartering or Re-Chartering Working Groups{% endblock %}
{% block content %}
{% load ietf_filters %}
{% load ballot_icon %}
<h1>Chartering or Re-Chartering Working Groups</h1>
<p>Groups with a charter in state
{% for s in charter_states %}{% if not forloop.first %}, {% if forloop.last %}or {% endif %}{% endif %}<i>{{ s.name }}</i>{% endfor %}.</p>
{% if user|has_role:"Area Director,Secretariat" %}
<p><a href="{% url "wg_create" %}">Start chartering new WG</a></p>
{% endif %}
{% if not groups %}
<p><b>No groups found.</b></p>
{% else %}
<table class="ietf-table ietf-doctable">
<tr>
<th>Group</th>
<th>Charter</th>
<th>Date</th>
<th colspan="2">Status</th>
</tr>
{% for g in groups %}
<tr class="{{ forloop.counter|divisibleby:2|yesno:"oddrow,evenrow" }}">
<td class="acronym">
<a href="{% url "group_charter" acronym=g.acronym %}">{{ g.acronym }}</a>
</td>
<td class="title">
<a href="{% url "doc_view" name=g.charter.name %}">{{ g.name }}</a>
</td>
<td class="date">{{ g.charter.time|date:"Y-m-d" }}</td>
<td class="status">
{{ g.charter.get_state.name }}
{% if g.chartering_type == "initial" %}(Initial Chartering){% endif %}
{% if g.chartering_type == "recharter" %}(Rechartering){% endif %}
{% if not g.chartering_type and g.state_id != "active" %}({{ g.state.name }}){% endif %}
{% if g.charter.telechat_date %}<br/>IESG Telechat: {{ g.charter.telechat_date|date:"Y-m-d" }}{% endif %}
</td>
<td class="ballot">
{% ballot_icon g.charter %}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endblock %}
{% block js %}
<script type="text/javascript" src="/js/utils.js"></script>
<script type="text/javascript" src="/js/doc-search.js"></script>
{% endblock %}

View file

@ -53,8 +53,8 @@ urlpatterns = patterns('',
(r'^sitemap.xml$', 'django.contrib.sitemaps.views.index', { 'sitemaps': sitemaps}),
(r'^submit/', include('ietf.submit.urls')),
(r'^sync/', include('ietf.sync.urls')),
(r'^wg/', include('ietf.wginfo.urls')),
(r'^stream/', include('ietf.group.stream_urls')),
(r'^(?P<group_type>(wg|rg))/', include('ietf.group.urls_info')),
(r'^stream/', include('ietf.group.urls_stream')),
(r'^nomcom/', include('ietf.nomcom.urls')),
(r'^templates/', include('ietf.dbtemplate.urls')),

View file

@ -1 +0,0 @@
/*.pyc

View file

@ -1,2 +0,0 @@
# Copyright The IETF Trust 2008, All Rights Reserved

View file

@ -1,2 +0,0 @@
# Copyright The IETF Trust 2008, All Rights Reserved

View file

@ -1,35 +0,0 @@
# Copyright The IETF Trust 2008, All Rights Reserved
from django.conf.urls import patterns
from django.views.generic import RedirectView
from ietf.wginfo import views, edit, milestones
urlpatterns = patterns('',
(r'^$', views.active_wgs),
(r'^summary.txt', RedirectView.as_view(url='/wg/1wg-summary.txt')),
(r'^summary-by-area.txt', RedirectView.as_view(url='/wg/1wg-summary.txt')),
(r'^summary-by-acronym.txt', RedirectView.as_view(url='/wg/1wg-summary-by-acronym.txt')),
(r'^1wg-summary.txt', views.wg_summary_area),
(r'^1wg-summary-by-acronym.txt', views.wg_summary_acronym),
(r'^1wg-charters.txt', views.wg_charters),
(r'^1wg-charters-by-acronym.txt', views.wg_charters_by_acronym),
(r'^chartering/$', views.chartering_wgs),
(r'^bofs/$', views.bofs),
(r'^chartering/create/$', edit.edit, {'action': "charter"}, "wg_create"),
(r'^bofs/create/$', edit.edit, {'action': "create"}, "bof_create"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/documents/txt/$', views.group_documents_txt),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/$', views.group_documents, None, "wg_docs"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/charter/$', views.group_charter, None, 'group_charter'),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/init-charter/', edit.submit_initial_charter, None, "wg_init_charter"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/history/$', views.history),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/deps/dot/$', views.dependencies_dot),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/deps/pdf/$', views.dependencies_pdf),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/edit/$', edit.edit, {'action': "edit"}, "group_edit"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/conclude/$', edit.conclude, None, "wg_conclude"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/milestones/$', milestones.edit_milestones, {'milestone_set': "current"}, "wg_edit_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/milestones/charter/$', milestones.edit_milestones, {'milestone_set': "charter"}, "wg_edit_charter_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/milestones/charter/reset/$', milestones.reset_charter_milestones, None, "wg_reset_charter_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/ajax/searchdocs/$', milestones.ajax_search_docs, None, "wg_ajax_search_docs"),
(r'^(?P<acronym>[a-zA-Z0-9-]+)/workflow/$', edit.customize_workflow),
)