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 ietfdb (5.4.3) ietf; urgency=medium
* Merged in [7772] from rjsparks@nostrum.com: * 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) 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() today = datetime.date.today()

View file

@ -88,7 +88,7 @@ class WGField(DisplayField):
if raw: if raw:
return document.group.acronym return document.group.acronym
else: 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): class ADField(DisplayField):

View file

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

View file

@ -1,6 +1,6 @@
import os, datetime, shutil, textwrap, json 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.shortcuts import render_to_response, get_object_or_404, redirect
from django.core.urlresolvers import reverse as urlreverse from django.core.urlresolvers import reverse as urlreverse
from django.template import RequestContext from django.template import RequestContext
@ -9,6 +9,7 @@ from django.utils.html import escape
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required
import debug # pyflakes:ignore 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, approved_revision, default_review_text, default_action_text, email_state_changed,
generate_ballot_writeup, generate_issue_ballot_mail, next_approved_revision, next_revision ) generate_ballot_writeup, generate_issue_ballot_mail, next_approved_revision, next_revision )
from ietf.group.models import ChangeStateGroupEvent, MilestoneGroupEvent 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.iesg.models import TelechatDate
from ietf.ietfauth.utils import has_role, role_required from ietf.ietfauth.utils import has_role, role_required
from ietf.name.models import GroupStateName 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.history import find_history_active_at
from ietf.utils.mail import send_mail_preformatted from ietf.utils.mail import send_mail_preformatted
from ietf.utils.textupload import get_cleaned_text_file_content 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): 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) 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")) 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) comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the charter history", required=False)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.hide = kwargs.pop('hide', None) self.hide = kwargs.pop('hide', None)
group = kwargs.pop('group')
super(ChangeStateForm, self).__init__(*args, **kwargs) 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 # hide requested fields
if self.hide: if self.hide:
for f in self.hide: for f in self.hide:
self.fields[f].widget = forms.HiddenInput self.fields[f].widget = forms.HiddenInput
@role_required("Area Director", "Secretariat") @login_required
def change_state(request, name, option=None): def change_state(request, name, option=None):
"""Change state of charter, notifying parties as necessary and """Change state of charter, notifying parties as necessary and
logging the change as a comment.""" logging the change as a comment."""
charter = get_object_or_404(Document, type="charter", name=name) charter = get_object_or_404(Document, type="charter", name=name)
group = charter.group 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) chartering_type = get_chartering_type(charter)
initial_review = charter.latest_event(InitialReviewDocEvent, type="initial_review") initial_review = charter.latest_event(InitialReviewDocEvent, type="initial_review")
@ -61,13 +71,17 @@ def change_state(request, name, option=None):
login = request.user.person login = request.user.person
if request.method == 'POST': if request.method == 'POST':
form = ChangeStateForm(request.POST) form = ChangeStateForm(request.POST, group=group)
if form.is_valid(): if form.is_valid():
clean = form.cleaned_data clean = form.cleaned_data
charter_rev = charter.rev charter_rev = charter.rev
if option in ("initcharter", "recharter"): 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 # make sure we have the latest revision set, if we
# abandoned a charter before, we could have reset the # abandoned a charter before, we could have reset the
# revision to latest approved # 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) 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"): if request.POST.get("ballot_wo_extern"):
create_ballot_if_not_open(charter, login, "r-wo-ext") create_ballot_if_not_open(charter, login, "r-wo-ext")
else: else:
@ -142,6 +156,9 @@ def change_state(request, name, option=None):
default_action_text(group, charter, login) default_action_text(group, charter, login)
elif charter_state.slug == "iesgrev": elif charter_state.slug == "iesgrev":
create_ballot_if_not_open(charter, login, "approve") 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: if charter_state.slug == "infrev" and clean["initial_time"] and clean["initial_time"] != 0:
e = InitialReviewDocEvent(type="initial_review", by=login, doc=charter) 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) return redirect('doc_view', name=charter.name)
else: else:
if option == "recharter": hide = ['initial_time']
hide = ['initial_time', 'charter_state', 'message'] s = charter.get_state()
init = dict() init = dict(charter_state=s.pk if s and option != "recharter" else None)
elif option == "initcharter":
hide = ['charter_state'] if option == "abandon":
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'] 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: if group.type_id == "wg":
hide = ['initial_time'] if option == "recharter":
s = charter.get_state() hide = ['initial_time', 'charter_state', 'message']
init = dict(charter_state=s.pk if s else None) init = dict()
form = ChangeStateForm(hide=hide, initial=init) 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 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] 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): def state_pk(slug):
return State.objects.get(used=True, type="charter", slug=slug).pk return State.objects.get(used=True, type="charter", slug=slug).pk
info_msg = { 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()), if group.type_id == "wg":
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("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("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[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', return render_to_response('doc/charter/change_state.html',
dict(form=form, dict(form=form,
@ -352,17 +375,16 @@ class UploadForm(forms.Form):
else: else:
destination.write(self.cleaned_data['content'].encode("utf-8")) destination.write(self.cleaned_data['content'].encode("utf-8"))
@role_required('Area Director','Secretariat') @login_required
def submit(request, name=None, acronym=None, option=None): def submit(request, name=None, option=None):
if name: if not name.startswith('charter-'):
if not name.startswith('charter-'): raise Http404
name = "charter-ietf-" + name
elif acronym:
name = "charter-ietf-" + acronym
charter = get_object_or_404(Document, type="charter", name=name) charter = get_object_or_404(Document, type="charter", name=name)
group = charter.group 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)) 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) 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 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.desc = "New version available: <b>%s-%s.txt</b>" % (charter.canonical_name(), charter.rev)
e.rev = charter.rev e.rev = charter.rev
e.save() e.save()
@ -423,7 +445,8 @@ def submit(request, name=None, acronym=None, option=None):
return render_to_response('doc/charter/submit.html', return render_to_response('doc/charter/submit.html',
{'form': form, {'form': form,
'next_rev': next_rev, 'next_rev': next_rev,
'group': group }, 'group': group,
'name': name },
context_instance=RequestContext(request)) context_instance=RequestContext(request))
class AnnouncementTextForm(forms.Form): class AnnouncementTextForm(forms.Form):
@ -568,6 +591,46 @@ def ballot_writeupnotes(request, name):
), ),
context_instance=RequestContext(request)) 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") @role_required("Secretariat")
def approve(request, name): def approve(request, name):
"""Approve charter, changing state, fixing revision, copying file to final location.""" """Approve charter, changing state, fixing revision, copying file to final location."""
@ -599,43 +662,13 @@ def approve(request, name):
change_description = e.desc change_description = e.desc
new_state = GroupStateName.objects.get(slug="active") group_state_change_event = change_group_state_after_charter_approval(group, login)
if group.state != new_state: if group_state_change_event:
save_group_in_history(group) change_description += " and group state has been changed to %s" % group.state.name
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)
add_state_change_event(charter, login, prev_charter_state, new_charter_state) 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 fix_charter_revision_after_approval(charter, login)
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()
email_secretariat(request, group, "Charter state changed to %s" % new_charter_state.name, change_description) 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.mails import email_ad
from ietf.doc.views_status_change import RELATION_SLUGS as status_change_relationships from ietf.doc.views_status_change import RELATION_SLUGS as status_change_relationships
from ietf.group.models import Role 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.ietfauth.utils import has_role, is_authorized_in_doc_stream, user_is_person, role_required
from ietf.name.models import StreamName, BallotPositionName from ietf.name.models import StreamName, BallotPositionName
from ietf.person.models import Email 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") ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
if doc.type_id in ("draft","conflrev", "statchg"): 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")) 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": 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 "IEST Review Ballot has not been created yet")) 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(("IESG Writeups", "writeup", urlreverse("doc_writeup", kwargs=dict(name=name)), True))
tabs.append(("History", "history", urlreverse("doc_history", 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"): elif group.type_id in ("rg", "wg"):
submission = "%s %s" % (group.acronym, group.type) submission = "%s %s" % (group.acronym, group.type)
if group.type_id == "wg": 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": if doc.stream_id and doc.get_state_slug("draft-stream-%s" % doc.stream_id) == "c-adopt":
submission = "candidate for %s" % submission submission = "candidate for %s" % submission
@ -410,6 +411,8 @@ def document_main(request, name, rev=None):
if chartering and not snapshot: if chartering and not snapshot:
milestones = doc.group.groupmilestone_set.filter(state="charter") 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", return render_to_response("doc/document_charter.html",
dict(doc=doc, dict(doc=doc,
top=top, top=top,
@ -422,6 +425,7 @@ def document_main(request, name, rev=None):
ballot_summary=ballot_summary, ballot_summary=ballot_summary,
group=group, group=group,
milestones=milestones, milestones=milestones,
can_manage=can_manage,
), ),
context_instance=RequestContext(request)) context_instance=RequestContext(request))

View file

@ -2,7 +2,7 @@ from django.conf.urls import patterns
from django.views.generic import RedirectView from django.views.generic import RedirectView
from ietf.doc.feeds import DocumentChangesFeed, InLastCallFeed 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.iesg.feeds import IESGAgendaFeed
from ietf.ipr.feeds import LatestIprDisclosuresFeed from ietf.ipr.feeds import LatestIprDisclosuresFeed
from ietf.liaisons.feeds import LiaisonStatementsFeed 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 re
import os import os
@ -6,11 +6,11 @@ import datetime
import shutil import shutil
from django import forms 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.http import HttpResponseForbidden
from django.template import RequestContext
from django.utils.html import mark_safe from django.utils.html import mark_safe
from django.http import Http404, HttpResponse from django.http import Http404, HttpResponse
from django.contrib.auth.decorators import login_required
import debug # pyflakes:ignore 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.doc.utils import get_tags_for_stream_id
from ietf.group.models import ( Group, Role, GroupEvent, GroupHistory, GroupStateName, from ietf.group.models import ( Group, Role, GroupEvent, GroupHistory, GroupStateName,
GroupStateTransitions, GroupTypeName, GroupURL, ChangeStateGroupEvent ) GroupStateTransitions, GroupTypeName, GroupURL, ChangeStateGroupEvent )
from ietf.group.utils import save_group_in_history from ietf.group.utils import save_group_in_history, can_manage_group_type
from ietf.ietfauth.utils import role_required, has_role from ietf.ietfauth.utils import has_role
from ietf.person.forms import EmailsField from ietf.person.forms import EmailsField
from ietf.person.models import Person, Email 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 MAX_GROUP_DELEGATES = 3
class WGForm(forms.Form): class GroupForm(forms.Form):
name = forms.CharField(max_length=255, label="Name", required=True) name = forms.CharField(max_length=255, label="Name", required=True)
acronym = forms.CharField(max_length=10, label="Acronym", required=True) acronym = forms.CharField(max_length=10, label="Acronym", required=True)
state = forms.ModelChoiceField(GroupStateName.objects.all(), label="State", 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) 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)) 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) 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_email = forms.CharField(max_length=64, required=False)
list_subscribe = forms.CharField(max_length=255, required=False) list_subscribe = forms.CharField(max_length=255, required=False)
list_archive = 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) 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): def __init__(self, *args, **kwargs):
self.wg = kwargs.pop('wg', None) self.group = kwargs.pop('group', None)
self.confirmed = kwargs.pop('confirmed', False) self.confirmed = kwargs.pop('confirmed', False)
self.group_type = kwargs.pop('group_type', False)
super(self.__class__, self).__init__(*args, **kwargs) 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 # if previous AD is now ex-AD, append that person to the list
ad_pk = self.initial.get('ad') ad_pk = self.initial.get('ad')
choices = self.fields['ad'].choices choices = self.fields['ad'].choices
@ -55,50 +59,58 @@ class WGForm(forms.Form):
self.confirm_msg = "" self.confirm_msg = ""
self.autoenable_confirm = False self.autoenable_confirm = False
if self.wg: if self.group:
self.fields['acronym'].widget.attrs['readonly'] = True 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): def clean_acronym(self):
self.confirm_msg = "" self.confirm_msg = ""
self.autoenable_confirm = False 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 # over the place, loose history, and generally muck up a lot of
# things, so we don't permit it # things, so we don't permit it
if self.wg: if self.group:
return self.wg.acronym # no change permitted return self.group.acronym # no change permitted
acronym = self.cleaned_data['acronym'].strip().lower() 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): 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.") 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) existing = Group.objects.filter(acronym__iexact=acronym)
if existing: if existing:
existing = existing[0] existing = existing[0]
if existing and existing.type_id == "wg": if existing and existing.type_id == self.group_type:
if self.confirmed: if self.confirmed:
return acronym # take over confirmed return acronym # take over confirmed
if existing.state_id == "bof": 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 self.autoenable_confirm = True
raise forms.ValidationError("Warning: Acronym used for an existing BoF (%s)." % existing.name) raise forms.ValidationError("Warning: Acronym used for an existing BoF (%s)." % existing.name)
else: 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 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: if existing:
raise forms.ValidationError("Acronym used for an existing group (%s)." % existing.name) 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: if old and not self.confirmed:
self.confirm_msg = "Confirm reusing acronym %s" % old[0].acronym self.confirm_msg = "Confirm reusing acronym %s" % old[0].acronym
self.autoenable_confirm = False 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 return acronym
@ -121,98 +133,107 @@ def format_urls(urls, fs="\n"):
res.append(u.url) res.append(u.url)
return fs.join(res) 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: try:
charter = Document.objects.get(docalias__name="charter-ietf-%s" % wg.acronym) charter = Document.objects.get(docalias__name=charter_name)
except Document.DoesNotExist: except Document.DoesNotExist:
charter = Document( charter = Document(
name="charter-ietf-" + wg.acronym, name=charter_name,
type_id="charter", type_id="charter",
title=wg.name, title=group.name,
group=wg, group=group,
abstract=wg.name, abstract=group.name,
rev="00-00", rev="00-00",
) )
charter.save() charter.save()
charter.set_state(State.objects.get(used=True, type="charter", slug="notrev")) charter.set_state(State.objects.get(used=True, type="charter", slug="notrev"))
# Create an alias as well # Create an alias as well
DocAlias.objects.create( DocAlias.objects.create(name=charter.name, document=charter)
name=charter.name,
document=charter
)
return charter return charter
@role_required('Area Director', 'Secretariat') @login_required
def submit_initial_charter(request, acronym=None): def submit_initial_charter(request, group_type, acronym=None):
wg = get_object_or_404(Group, acronym=acronym) if not can_manage_group_type(request.user, group_type):
if not wg.charter: return HttpResponseForbidden("You don't have permission to access this view")
wg.charter = get_or_create_initial_charter(wg)
wg.save() group = get_object_or_404(Group, acronym=acronym)
return redirect('charter_submit', name=wg.charter.name, option="initcharter") if not group.charter:
group.charter = get_or_create_initial_charter(group, group_type)
@role_required('Area Director', 'Secretariat') group.save()
def edit(request, acronym=None, action="edit"):
"""Edit or create a WG, notifying parties as 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.""" 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": if action == "edit":
wg = get_object_or_404(Group, acronym=acronym) group = get_object_or_404(Group, acronym=acronym)
new_wg = False new_group = False
elif action in ("create","charter"): elif action in ("create","charter"):
wg = None group = None
new_wg = True new_group = True
else: else:
raise Http404 raise Http404
login = request.user.person
if request.method == 'POST': 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(): if form.is_valid():
clean = form.cleaned_data clean = form.cleaned_data
if new_wg: if new_group:
try: try:
wg = Group.objects.get(acronym=clean["acronym"]) group = Group.objects.get(acronym=clean["acronym"])
save_group_in_history(wg) save_group_in_history(group)
wg.time = datetime.datetime.now() group.time = datetime.datetime.now()
wg.save() group.save()
except Group.DoesNotExist: except Group.DoesNotExist:
wg = Group.objects.create(name=clean["name"], group = Group.objects.create(name=clean["name"],
acronym=clean["acronym"], acronym=clean["acronym"],
type=GroupTypeName.objects.get(slug="wg"), type=GroupTypeName.objects.get(slug=group_type),
state=clean["state"] state=clean["state"]
) )
e = ChangeStateGroupEvent(group=wg, type="changed_state") e = ChangeStateGroupEvent(group=group, type="changed_state")
e.time = wg.time e.time = group.time
e.by = login e.by = request.user.person
e.state_id = clean["state"].slug e.state_id = clean["state"].slug
e.desc = "Group created in state %s" % clean["state"].name e.desc = "Group created in state %s" % clean["state"].name
e.save() e.save()
else: else:
save_group_in_history(wg) save_group_in_history(group)
if action=="charter" and not wg.charter: # make sure we have a charter if action == "charter" and not group.charter: # make sure we have a charter
wg.charter = get_or_create_initial_charter(wg) group.charter = get_or_create_initial_charter(group, group_type)
changes = [] changes = []
def desc(attr, new, old): def desc(attr, new, old):
entry = "%(attr)s changed to <b>%(new)s</b> from %(old)s" 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>" entry = "%(attr)s changed to <b>%(new)s</b>"
return entry % dict(attr=attr, new=new, old=old) return entry % dict(attr=attr, new=new, old=old)
def diff(attr, name): def diff(attr, name):
v = getattr(wg, attr) v = getattr(group, attr)
if clean[attr] != v: if clean[attr] != v:
changes.append(desc(name, 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 # update the attributes, keeping track of what we're doing
diff('name', "Name") diff('name', "Name")
@ -224,127 +245,130 @@ def edit(request, acronym=None, action="edit"):
diff('list_subscribe', "Mailing list subscribe address") diff('list_subscribe', "Mailing list subscribe address")
diff('list_archive', "Mailing list archive") diff('list_archive', "Mailing list archive")
if not new_wg and wg.acronym != prev_acronym and wg.charter: if not new_group and group.acronym != prev_acronym and group.charter:
save_document_in_history(wg.charter) save_document_in_history(group.charter)
DocAlias.objects.get_or_create( DocAlias.objects.get_or_create(
name="charter-ietf-%s" % wg.acronym, name="charter-ietf-%s" % group.acronym,
document=wg.charter, 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): 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) shutil.copy(old, new)
# update roles # update roles
for attr, slug, title in [('chairs', 'chair', "Chairs"), ('secretaries', 'secr', "Secretaries"), ('techadv', 'techadv', "Tech Advisors"), ('delegates', 'delegate', "Delegates")]: for attr, slug, title in [('chairs', 'chair', "Chairs"), ('secretaries', 'secr', "Secretaries"), ('techadv', 'techadv', "Tech Advisors"), ('delegates', 'delegate', "Delegates")]:
new = clean[attr] 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): if set(new) != set(old):
changes.append(desc(title, changes.append(desc(title,
", ".join(x.get_name() for x in new), ", ".join(x.get_name() for x in new),
", ".join(x.get_name() for x in old))) ", ".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: 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 # update urls
new_urls = clean['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: if ", ".join(sorted(new_urls)) != old_urls:
changes.append(desc('Urls', ", ".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 # Add new ones
for u in new_urls: for u in new_urls:
m = re.search('(?P<url>[\w\d:#@%/;$()~_?\+-=\\\.&]+)( \((?P<name>.+)\))?', u) m = re.search('(?P<url>[\w\d:#@%/;$()~_?\+-=\\\.&]+)( \((?P<name>.+)\))?', u)
if m: if m:
if m.group('name'): 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: else:
url = GroupURL(url=m.group('url'), name='', group=wg) url = GroupURL(url=m.group('url'), name='', group=group)
url.save() 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: 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": 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() else: # form.is_valid()
if not new_wg: if not new_group:
init = dict(name=wg.name, init = dict(name=group.name,
acronym=wg.acronym, acronym=group.acronym,
state=wg.state, state=group.state,
chairs=Email.objects.filter(role__group=wg, role__name="chair"), chairs=Email.objects.filter(role__group=group, role__name="chair"),
secretaries=Email.objects.filter(role__group=wg, role__name="secr"), secretaries=Email.objects.filter(role__group=group, role__name="secr"),
techadv=Email.objects.filter(role__group=wg, role__name="techadv"), techadv=Email.objects.filter(role__group=group, role__name="techadv"),
delegates=Email.objects.filter(role__group=wg, role__name="delegate"), delegates=Email.objects.filter(role__group=group, role__name="delegate"),
ad=wg.ad_id if wg.ad else None, ad=group.ad_id if group.ad else None,
parent=wg.parent.id if wg.parent else None, parent=group.parent.id if group.parent else None,
list_email=wg.list_email if wg.list_email else None, list_email=group.list_email if group.list_email else None,
list_subscribe=wg.list_subscribe if wg.list_subscribe else None, list_subscribe=group.list_subscribe if group.list_subscribe else None,
list_archive=wg.list_archive if wg.list_archive else None, list_archive=group.list_archive if group.list_archive else None,
urls=format_urls(wg.groupurl_set.all()), urls=format_urls(group.groupurl_set.all()),
) )
else: 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', return render(request, 'group/edit.html',
dict(wg=wg, dict(group=group,
form=form, form=form,
action=action, action=action))
user=request.user,
login=login),
context_instance=RequestContext(request))
class ConcludeForm(forms.Form): class ConcludeForm(forms.Form):
instructions = forms.CharField(widget=forms.Textarea(attrs={'rows': 30}), required=True) instructions = forms.CharField(widget=forms.Textarea(attrs={'rows': 30}), required=True)
@role_required('Area Director','Secretariat') @login_required
def conclude(request, acronym): def conclude(request, group_type, acronym):
"""Request the closing of a WG, prompting for instructions.""" """Request the closing of group, prompting for instructions."""
wg = get_object_or_404(Group, acronym=acronym) 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': if request.method == 'POST':
form = ConcludeForm(request.POST) form = ConcludeForm(request.POST)
if form.is_valid(): if form.is_valid():
instructions = form.cleaned_data['instructions'] 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.type = "requested_close"
e.desc = "Requested closing group" e.desc = "Requested closing group"
e.save() e.save()
return redirect('group_charter', acronym=wg.acronym) return redirect('group_charter', group_type=group.type_id, acronym=group.acronym)
else: else:
form = ConcludeForm() form = ConcludeForm()
return render_to_response('wginfo/conclude.html', return render(request, 'group/conclude.html',
dict(form=form, dict(form=form, group=group))
wg=wg),
context_instance=RequestContext(request))
def customize_workflow(request, acronym): @login_required
MANDATORY_STATES = ('c-adopt', 'wg-doc', 'sub-pub') def customize_workflow(request, group_type, acronym):
group = get_object_or_404(Group, type=group_type, acronym=acronym)
group = get_object_or_404(Group, acronym=acronym, type="wg") if (not has_role(request.user, "Secretariat") and
if not request.user.is_authenticated() or not (has_role(request.user, "Secretariat") or group.role_set.filter(name="chair", person__user=request.user)): not group.role_set.filter(name="chair", person__user=request.user)):
return HttpResponseForbidden("You don't have permission to access this view") 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': if request.method == 'POST':
action = request.POST.get("action") action = request.POST.get("action")
if action == "setstateactive": if action == "setstateactive":
@ -361,7 +385,7 @@ def customize_workflow(request, acronym):
# redirect so the back button works correctly, otherwise # redirect so the back button works correctly, otherwise
# repeated POSTs fills up the history # 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": if action == "setnextstates":
try: try:
@ -369,7 +393,7 @@ def customize_workflow(request, acronym):
except State.DoesNotExist: except State.DoesNotExist:
return HttpResponse("Invalid state %s" % request.POST.get("state")) 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() unused = group.unused_states.all()
if set(next_states.exclude(pk__in=unused)) == set(state.next_states.exclude(pk__in=unused)): if set(next_states.exclude(pk__in=unused)) == set(state.next_states.exclude(pk__in=unused)):
# just use the default # just use the default
@ -378,7 +402,7 @@ def customize_workflow(request, acronym):
transitions, _ = GroupStateTransitions.objects.get_or_create(group=group, state=state) transitions, _ = GroupStateTransitions.objects.get_or_create(group=group, state=state)
transitions.next_states = next_states 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": if action == "settagactive":
active = request.POST.get("active") == "1" active = request.POST.get("active") == "1"
@ -392,17 +416,16 @@ def customize_workflow(request, acronym):
else: else:
group.unused_tags.add(tag) 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 # put some info for the template on tags and states
unused_tags = group.unused_tags.all().values_list('slug', flat=True) 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: for t in tags:
t.used = t.slug not in unused_tags t.used = t.slug not in unused_tags
unused_states = group.unused_states.all().values_list('slug', flat=True) 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()) transitions = dict((o.state, o) for o in group.groupstatetransitions_set.all())
for s in states: for s in states:
s.used = s.slug not in unused_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.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] 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, 'group': group,
'states': states, 'states': states,
'tags': tags, 'tags': tags,
}, RequestContext(request)) })

View file

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

View file

@ -36,22 +36,21 @@ import os
import itertools import itertools
from tempfile import mkstemp 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.loader import render_to_string
from django.template import RequestContext from django.http import HttpResponse, Http404
from django.http import HttpResponse
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse as urlreverse from django.core.urlresolvers import reverse as urlreverse
from django.views.decorators.cache import cache_page from django.views.decorators.cache import cache_page
from django.db.models import Q from django.db.models import Q
from ietf.doc.views_search import SearchForm, retrieve_search_results 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.models import State, DocAlias, RelatedDocument
from ietf.doc.utils import get_chartering_type 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.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 from ietf.utils.pipe import pipe
def roles(group, role_name): def roles(group, role_name):
@ -86,7 +85,9 @@ def fill_in_charter_info(group, include_drafts=False):
def extract_last_name(role): def extract_last_name(role):
return role.person.name_parts()[3] 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") areas = Group.objects.filter(type="area", state="active").order_by("name")
for area in areas: for area in areas:
area.ads = sorted(roles(area, "ad"), key=extract_last_name) 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] areas = [a for a in areas if a.groups]
return render_to_response('wginfo/1wg-summary.txt', return render(request, 'group/1wg-summary.txt',
{ 'areas': areas }, { 'areas': areas },
content_type='text/plain; charset=UTF-8') 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") 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") groups = Group.objects.filter(type="wg", state="active").order_by("acronym").select_related("parent")
for group in groups: for group in groups:
group.chairs = sorted(roles(group, "chair"), key=extract_last_name) group.chairs = sorted(roles(group, "chair"), key=extract_last_name)
return render_to_response('wginfo/1wg-summary-by-acronym.txt', return render(request, 'group/1wg-summary-by-acronym.txt',
{ 'areas': areas, { 'areas': areas,
'groups': groups }, 'groups': groups },
content_type='text/plain; charset=UTF-8') 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") areas = Group.objects.filter(type="area", state="active").order_by("name")
for area in areas: for area in areas:
area.ads = sorted(roles(area, "ad"), key=extract_last_name) area.ads = sorted(roles(area, "ad"), key=extract_last_name)
@ -118,11 +123,13 @@ def wg_charters(request):
for group in area.groups: for group in area.groups:
fill_in_charter_info(group, include_drafts=True) fill_in_charter_info(group, include_drafts=True)
group.area = area group.area = area
return render_to_response('wginfo/1wg-charters.txt', return render(request, 'group/1wg-charters.txt',
{ 'areas': areas }, { 'areas': areas },
content_type='text/plain; charset=UTF-8') 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")) areas = dict((a.id, a) for a in Group.objects.filter(type="area", state="active").order_by("name"))
for area in areas.itervalues(): for area in areas.itervalues():
@ -132,9 +139,17 @@ def wg_charters_by_acronym(request):
for group in groups: for group in groups:
fill_in_charter_info(group, include_drafts=True) fill_in_charter_info(group, include_drafts=True)
group.area = areas.get(group.parent_id) group.area = areas.get(group.parent_id)
return render_to_response('wginfo/1wg-charters-by-acronym.txt', return render(request, 'group/1wg-charters-by-acronym.txt',
{ 'groups': groups }, { 'groups': groups },
content_type='text/plain; charset=UTF-8') 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): def active_wgs(request):
areas = Group.objects.filter(type="area", state="active").order_by("name") areas = Group.objects.filter(type="area", state="active").order_by("name")
@ -148,43 +163,77 @@ def active_wgs(request):
for group in area.groups: for group in area.groups:
group.chairs = sorted(roles(group, "chair"), key=extract_last_name) 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): def active_rgs(request):
groups = Group.objects.filter(type="wg", state="bof") irtf = Group.objects.get(acronym="irtf")
return render_to_response('wginfo/bofs.html',dict(groups=groups), RequestContext(request)) 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")) 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: group_types = GroupTypeName.objects.filter(slug__in=("wg", "rg"))
g.chartering_type = get_chartering_type(g.charter)
return render_to_response('wginfo/chartering_wgs.html', for t in group_types:
dict(charter_states=charter_states, t.chartering_groups = Group.objects.filter(type=t, charter__states__in=charter_states).select_related("state", "charter").order_by("acronym")
groups=groups), t.can_manage = can_manage_group_type(request.user, t.slug)
RequestContext(request))
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): def construct_group_menu_context(request, group, selected, others):
"""Return context with info for the group menu filled in.""" """Return context with info for the group menu filled in."""
actions = [] actions = []
is_chair = group.has_role(request.user, "chair") 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): if group.state_id != "proposed" and (is_chair or can_manage):
actions.append((u"Add or edit milestones", urlreverse("wg_edit_milestones", kwargs=dict(acronym=group.acronym)))) 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: if group.state_id != "conclude" and can_manage:
actions.append((u"Edit group", urlreverse("group_edit", kwargs=dict(acronym=group.acronym)))) 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: if is_chair or can_manage:
actions.append((u"Customize workflow", urlreverse("ietf.wginfo.edit.customize_workflow", kwargs=dict(acronym=group.acronym)))) 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: if group.state_id in ("active", "dormant") and can_manage:
actions.append((u"Request closing group", urlreverse("wg_conclude", kwargs=dict(acronym=group.acronym)))) actions.append((u"Request closing group", urlreverse("ietf.group.edit.conclude", kwargs=dict(group_type=group.type_id, acronym=group.acronym))))
d = { d = {
"group": group, "group": group,
@ -207,8 +256,8 @@ def search_for_group_documents(group):
docs_related = [] docs_related = []
for d in raw_docs_related: for d in raw_docs_related:
parts = d.name.split("-", 2); parts = d.name.split("-", 2);
# canonical form draft-<name|ietf>-wg-etc # canonical form draft-<name|ietf|irtf>-wg-etc
if len(parts) >= 3 and parts[1] != "ietf" and parts[2].startswith(group.acronym + "-"): if len(parts) >= 3 and parts[1] not in ("ietf", "irtf") and parts[2].startswith(group.acronym + "-"):
d.search_heading = "Related Internet-Draft" d.search_heading = "Related Internet-Draft"
docs_related.append(d) docs_related.append(d)
@ -229,22 +278,22 @@ def search_for_group_documents(group):
return docs, meta, docs_related, meta_related return docs, meta, docs_related, meta_related
def group_documents(request, acronym): def group_documents(request, group_type, acronym):
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) docs, meta, docs_related, meta_related = search_for_group_documents(group)
return render_to_response('wginfo/group_documents.html', return render(request, 'group/group_documents.html',
construct_group_menu_context(request, group, "documents", { construct_group_menu_context(request, group, "documents", {
'docs': docs, 'docs': docs,
'meta': meta, 'meta': meta,
'docs_related': docs_related, 'docs_related': docs_related,
'meta_related': meta_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.""" """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) 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') return HttpResponse(u"\n".join(rows), content_type='text/plain; charset=UTF-8')
def group_charter(request, acronym): def group_charter(request, group_type, acronym):
group = get_object_or_404(Group, type="wg", acronym=acronym) group = get_object_or_404(Group, type=group_type, acronym=acronym)
fill_in_charter_info(group, include_drafts=False) fill_in_charter_info(group, include_drafts=False)
group.delegates = roles(group, "delegate") group.delegates = roles(group, "delegate")
@ -276,22 +325,32 @@ def group_charter(request, acronym):
e = group.latest_event(type__in=("changed_state", "requested_close",)) e = group.latest_event(type__in=("changed_state", "requested_close",))
requested_close = group.state_id != "conclude" and e and e.type == "requested_close" requested_close = group.state_id != "conclude" and e and e.type == "requested_close"
return render_to_response('wginfo/group_charter.html', long_group_types = dict(
construct_group_menu_context(request, group, "charter", { wg="Working Group",
"milestones_in_review": group.groupmilestone_set.filter(state="review"), rg="Research Group",
"requested_close": requested_close, )
}), RequestContext(request))
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) group = get_object_or_404(Group, acronym=acronym)
events = group.groupevent_set.all().select_related('by').order_by('-time', '-id') events = group.groupevent_set.all().select_related('by').order_by('-time', '-id')
return render_to_response('wginfo/history.html', return render(request, 'group/history.html',
construct_group_menu_context(request, group, "history", { construct_group_menu_context(request, group, "history", {
"events": events, "events": events,
}), RequestContext(request)) }))
def nodename(name): def nodename(name):
@ -407,11 +466,11 @@ def make_dot(group):
node.nodename=nodename(node.name) node.nodename=nodename(node.name)
node.styles = get_node_styles(node,group) 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 ) 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) group = get_object_or_404(Group, acronym=acronym)
@ -420,7 +479,7 @@ def dependencies_dot(request, acronym):
) )
@cache_page ( 60 * 60 ) @cache_page ( 60 * 60 )
def dependencies_pdf(request, acronym): def dependencies_pdf(request, group_type, acronym):
group = get_object_or_404(Group, acronym=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 django.core.urlresolvers import reverse as urlreverse
from ietf.utils.mail import send_mail, send_mail_text from ietf.utils.mail import send_mail, send_mail_text
from ietf.group.models import Group from ietf.group.models import Group
from ietf.group.utils import milestone_reviewer_for_group_type
def email_secretariat(request, group, subject, text): def email_secretariat(request, group, subject, text):
to = ["iesg-secretary@ietf.org"] to = ["iesg-secretary@ietf.org"]
@ -19,10 +19,10 @@ def email_secretariat(request, group, subject, text):
text = strip_tags(text) text = strip_tags(text)
send_mail(request, to, None, full_subject, send_mail(request, to, None, full_subject,
"wginfo/email_secretariat.txt", "group/email_secretariat.txt",
dict(text=text, dict(text=text,
group=group, 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)), 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): def wrap_up_email(to, text):
text = wrap(strip_tags(text), 70) text = wrap(strip_tags(text), 70)
text += "\n\n" 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, send_mail_text(request, to, None,
u"Milestones changed for %s %s" % (group.acronym, group.type.name), u"Milestones changed for %s %s" % (group.acronym, group.type.name),
text) text)
# first send to AD and chairs # first send to management and chairs
to = [] to = []
if group.ad: if group.ad:
to.append(group.ad.role_email("ad").formatted_email()) 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"): for r in group.role_set.filter(name="chair"):
to.append(r.formatted_email()) to.append(r.formatted_email())
@ -48,7 +50,7 @@ def email_milestones_changed(request, group, changes):
if to: if to:
wrap_up_email(to, u"\n\n".join(c + "." for c in changes)) 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: if group.list_email:
review_re = re.compile("Added .* for review, due") review_re = re.compile("Added .* for review, due")
to = [ group.list_email ] to = [ group.list_email ]
@ -56,11 +58,17 @@ def email_milestones_changed(request, group, changes):
def email_milestone_review_reminder(group, grace_period=7): def email_milestone_review_reminder(group, grace_period=7):
"""Email reminders about milestones needing review to AD.""" """Email reminders about milestones needing review to management."""
if not group.ad: 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 return False
to = [group.ad.role_email("ad").formatted_email()]
cc = [r.formatted_email() for r in group.role_set.filter(name="chair")] cc = [r.formatted_email() for r in group.role_set.filter(name="chair")]
now = datetime.datetime.now() now = datetime.datetime.now()
@ -81,10 +89,11 @@ def email_milestone_review_reminder(group, grace_period=7):
send_mail(None, to, None, send_mail(None, to, None,
subject, subject,
"wginfo/reminder_milestones_need_review.txt", "group/reminder_milestones_need_review.txt",
dict(group=group, dict(group=group,
milestones=milestones, 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, cc=cc,
) )
) )
@ -107,12 +116,12 @@ def email_milestones_due(group, early_warning_days):
send_mail(None, to, None, send_mail(None, to, None,
subject, subject,
"wginfo/reminder_milestones_due.txt", "group/reminder_milestones_due.txt",
dict(group=group, dict(group=group,
milestones=milestones, milestones=milestones,
today=today, today=today,
early_warning_days=early_warning_days, 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): def groups_needing_milestones_due_reminder(early_warning_days):
@ -134,10 +143,10 @@ def email_milestones_overdue(group):
send_mail(None, to, None, send_mail(None, to, None,
subject, subject,
"wginfo/reminder_milestones_overdue.txt", "group/reminder_milestones_overdue.txt",
dict(group=group, dict(group=group,
milestones=milestones, 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): 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 datetime
import calendar import calendar
@ -6,16 +6,15 @@ import json
from django import forms from django import forms
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest
from django.shortcuts import render_to_response, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
from django.template import RequestContext from django.contrib.auth.decorators import login_required
from ietf.doc.models import Document, DocEvent from ietf.doc.models import Document, DocEvent
from ietf.doc.utils import get_chartering_type from ietf.doc.utils import get_chartering_type
from ietf.group.models import Group, GroupMilestone, MilestoneGroupEvent from ietf.group.models import Group, GroupMilestone, MilestoneGroupEvent
from ietf.group.utils import save_milestone_in_history from ietf.group.utils import save_milestone_in_history, can_manage_group_type, milestone_reviewer_for_group_type
from ietf.ietfauth.utils import role_required, has_role
from ietf.name.models import GroupMilestoneStateName 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): def json_doc_names(docs):
return json.dumps([{"id": doc.pk, "name": doc.name } for doc in docs]) return json.dumps([{"id": doc.pk, "name": doc.name } for doc in docs])
@ -108,23 +107,19 @@ class MilestoneForm(forms.Form):
return r return r
@login_required
@role_required('WG Chair', 'Area Director', 'Secretariat') def edit_milestones(request, group_type, acronym, milestone_set="current"):
def edit_milestones(request, acronym, milestone_set="current"):
# milestones_set + needs_review: we have several paths into this view # 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 # group chair -> limited actions on current + add new for review
# (re)charter -> all actions on existing in state charter + add new in state charter # (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. # For charters we store the history on the charter document to not confuse people.
group = get_object_or_404(Group, type=group_type, acronym=acronym)
login = request.user.person
group = get_object_or_404(Group, acronym=acronym)
needs_review = False needs_review = False
if not has_role(request.user, ("Area Director", "Secretariat")): if not can_manage_group_type(request.user, group_type):
if group.role_set.filter(name="chair", person=login): if group.role_set.filter(name="chair", person__user=request.user):
if milestone_set == "current": if milestone_set == "current":
needs_review = True needs_review = True
else: else:
@ -298,10 +293,10 @@ def edit_milestones(request, acronym, milestone_set="current"):
if milestone_set == "charter": if milestone_set == "charter":
DocEvent.objects.create(doc=group.charter, type="changed_charter_milestone", DocEvent.objects.create(doc=group.charter, type="changed_charter_milestone",
by=login, desc=change) by=request.user.person, desc=change)
else: else:
MilestoneGroupEvent.objects.create(group=group, type="changed_milestone", 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) changes.append(change)
@ -311,7 +306,7 @@ def edit_milestones(request, acronym, milestone_set="current"):
if milestone_set == "charter": if milestone_set == "charter":
return redirect('doc_view', name=group.charter.canonical_name()) return redirect('doc_view', name=group.charter.canonical_name())
else: else:
return redirect('group_charter', acronym=group.acronym) return redirect('group_charter', group_type=group.type_id, acronym=group.acronym)
else: else:
for m in milestones: for m in milestones:
forms.append(MilestoneForm(instance=m, needs_review=needs_review)) 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) forms.sort(key=lambda f: f.milestone.due if f.milestone else datetime.date.max)
return render_to_response('wginfo/edit_milestones.html', return render(request, 'group/edit_milestones.html',
dict(group=group, dict(group=group,
title=title, title=title,
forms=forms, forms=forms,
form_errors=form_errors, form_errors=form_errors,
empty_form=empty_form, empty_form=empty_form,
milestone_set=milestone_set, milestone_set=milestone_set,
finished_milestone_text=finished_milestone_text, finished_milestone_text=finished_milestone_text,
needs_review=needs_review, needs_review=needs_review,
can_reset=can_reset), reviewer=milestone_reviewer_for_group_type(group_type),
context_instance=RequestContext(request)) can_reset=can_reset))
@role_required('WG Chair', 'Area Director', 'Secretariat') @login_required
def reset_charter_milestones(request, acronym): def reset_charter_milestones(request, group_type, acronym):
"""Reset charter milestones to the currently in-use milestones.""" """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 can_manage_group_type(request.user, group_type) and
not group.role_set.filter(name="chair", person__user=request.user)):
if (not has_role(request.user, ("Area Director", "Secretariat")) and
not group.role_set.filter(name="chair", person=login)):
return HttpResponseForbidden("You are not chair of this group.") return HttpResponseForbidden("You are not chair of this group.")
current_milestones = group.groupmilestone_set.filter(state="active") 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", DocEvent.objects.create(type="changed_charter_milestone",
doc=group.charter, doc=group.charter,
desc='Deleted milestone "%s"' % m.desc, desc='Deleted milestone "%s"' % m.desc,
by=login, by=request.user.person,
) )
# add current # add current
@ -380,20 +373,19 @@ def reset_charter_milestones(request, acronym):
DocEvent.objects.create(type="changed_charter_milestone", DocEvent.objects.create(type="changed_charter_milestone",
doc=group.charter, doc=group.charter,
desc='Added milestone "%s", due %s, from current group milestones' % (new.desc, new.due.strftime("%B %Y")), 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', return render(request, 'group/reset_charter_milestones.html',
dict(group=group, dict(group=group,
charter_milestones=charter_milestones, charter_milestones=charter_milestones,
current_milestones=current_milestones, current_milestones=current_milestones,
), ))
context_instance=RequestContext(request))
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] 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') 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.mail import outbox
from ietf.utils.test_data import make_test_data from ietf.utils.test_data import make_test_data
from ietf.utils.test_utils import login_testing_unauthorized 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, email_milestones_overdue, groups_needing_milestones_due_reminder,
groups_needing_milestones_overdue_reminder, groups_with_milestones_needing_review ) groups_needing_milestones_overdue_reminder, groups_with_milestones_needing_review )
@ -32,11 +32,11 @@ class GroupPagesTests(TestCase):
def tearDown(self): def tearDown(self):
shutil.rmtree(self.charter_dir) shutil.rmtree(self.charter_dir)
def test_active_wgs(self): def test_active_groups(self):
draft = make_test_data() draft = make_test_data()
group = draft.group 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) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
self.assertTrue(group.parent.name in r.content) 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.name in r.content)
self.assertTrue(group.ad.plain_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): def test_wg_summaries(self):
draft = make_test_data() draft = make_test_data()
group = draft.group 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: 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.") 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) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
self.assertTrue(group.parent.name in r.content) self.assertTrue(group.parent.name in r.content)
@ -61,14 +65,14 @@ class GroupPagesTests(TestCase):
self.assertTrue(group.name in r.content) self.assertTrue(group.name in r.content)
self.assertTrue(chair.address 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) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
self.assertTrue(group.acronym in r.content) self.assertTrue(group.acronym in r.content)
self.assertTrue(group.name in r.content) self.assertTrue(group.name in r.content)
self.assertTrue(chair.address 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) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
self.assertTrue(group.acronym in r.content) self.assertTrue(group.acronym in r.content)
@ -77,7 +81,7 @@ class GroupPagesTests(TestCase):
self.assertTrue(chair.address in r.content) self.assertTrue(chair.address in r.content)
self.assertTrue("This is a charter." 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) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
self.assertTrue(group.acronym in r.content) self.assertTrue(group.acronym in r.content)
@ -86,24 +90,36 @@ class GroupPagesTests(TestCase):
self.assertTrue(chair.address in r.content) self.assertTrue(chair.address in r.content)
self.assertTrue("This is a charter." 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() draft = make_test_data()
group = draft.group group = draft.group
group.charter.set_state(State.objects.get(used=True, type="charter", slug="intrev")) 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) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
q = PyQuery(r.content) q = PyQuery(r.content)
self.assertEqual(len(q('table.ietf-doctable td.acronym a:contains("%s")' % group.acronym)), 1) 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): def test_bofs(self):
draft = make_test_data() draft = make_test_data()
group = draft.group group = draft.group
group.state_id = "bof" group.state_id = "bof"
group.save() group.save()
url = urlreverse('ietf.wginfo.views.bofs') url = urlreverse('ietf.group.info.bofs', kwargs=dict(group_type="wg"))
r = self.client.get(url) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
q = PyQuery(r.content) q = PyQuery(r.content)
@ -137,7 +153,7 @@ class GroupPagesTests(TestCase):
name=draft2.name, 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) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
self.assertTrue(draft.name in r.content) self.assertTrue(draft.name in r.content)
@ -147,7 +163,7 @@ class GroupPagesTests(TestCase):
self.assertTrue(draft2.name in r.content) self.assertTrue(draft2.name in r.content)
# test the txt version too while we're at it # 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) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
self.assertTrue(draft.name in r.content) self.assertTrue(draft.name in r.content)
@ -167,7 +183,7 @@ class GroupPagesTests(TestCase):
due=datetime.date.today() + datetime.timedelta(days=100)) due=datetime.date.today() + datetime.timedelta(days=100))
milestone.docs.add(draft) 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) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
self.assertTrue(group.name in r.content) self.assertTrue(group.name in r.content)
@ -186,7 +202,7 @@ class GroupPagesTests(TestCase):
type="added_comment", type="added_comment",
by=Person.objects.get(name="(System)")) 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) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
self.assertTrue(e.desc in r.content) self.assertTrue(e.desc in r.content)
@ -225,7 +241,7 @@ class GroupEditTests(TestCase):
def test_create(self): def test_create(self):
make_test_data() make_test_data()
url = urlreverse('wg_create') url = urlreverse('group_create', kwargs=dict(group_type="wg"))
login_testing_unauthorized(self, "secretary", url) login_testing_unauthorized(self, "secretary", url)
num_wgs = len(Group.objects.filter(type="wg")) num_wgs = len(Group.objects.filter(type="wg"))
@ -273,7 +289,7 @@ class GroupEditTests(TestCase):
def test_create_based_on_existing(self): def test_create_based_on_existing(self):
make_test_data() make_test_data()
url = urlreverse('wg_create') url = urlreverse('group_create', kwargs=dict(group_type="wg"))
login_testing_unauthorized(self, "secretary", url) login_testing_unauthorized(self, "secretary", url)
group = Group.objects.get(acronym="mars") group = Group.objects.get(acronym="mars")
@ -308,7 +324,7 @@ class GroupEditTests(TestCase):
make_test_data() make_test_data()
group = Group.objects.get(acronym="mars") 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) login_testing_unauthorized(self, "secretary", url)
# normal get # normal get
@ -380,7 +396,7 @@ class GroupEditTests(TestCase):
group = Group.objects.get(acronym="mars") 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) login_testing_unauthorized(self, "secretary", url)
# normal get # normal get
@ -435,7 +451,7 @@ class MilestoneTests(TestCase):
def test_milestone_sets(self): def test_milestone_sets(self):
m1, m2, group = self.create_test_milestones() 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) login_testing_unauthorized(self, "secretary", url)
r = self.client.get(url) r = self.client.get(url)
@ -443,7 +459,7 @@ class MilestoneTests(TestCase):
self.assertTrue(m1.desc in r.content) self.assertTrue(m1.desc in r.content)
self.assertTrue(m2.desc not 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) r = self.client.get(url)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
@ -453,7 +469,7 @@ class MilestoneTests(TestCase):
def test_add_milestone(self): def test_add_milestone(self):
m1, m2, group = self.create_test_milestones() 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) login_testing_unauthorized(self, "secretary", url)
# normal get # normal get
@ -505,7 +521,7 @@ class MilestoneTests(TestCase):
def test_add_milestone_as_chair(self): def test_add_milestone_as_chair(self):
m1, m2, group = self.create_test_milestones() 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) login_testing_unauthorized(self, "marschairman", url)
# normal get # normal get
@ -539,7 +555,7 @@ class MilestoneTests(TestCase):
m1.state_id = "review" m1.state_id = "review"
m1.save() 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) login_testing_unauthorized(self, "ad", url)
# normal get # normal get
@ -569,7 +585,7 @@ class MilestoneTests(TestCase):
def test_delete_milestone(self): def test_delete_milestone(self):
m1, m2, group = self.create_test_milestones() 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) login_testing_unauthorized(self, "secretary", url)
milestones_before = GroupMilestone.objects.count() milestones_before = GroupMilestone.objects.count()
@ -597,7 +613,7 @@ class MilestoneTests(TestCase):
def test_edit_milestone(self): def test_edit_milestone(self):
m1, m2, group = self.create_test_milestones() 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) login_testing_unauthorized(self, "secretary", url)
milestones_before = GroupMilestone.objects.count() milestones_before = GroupMilestone.objects.count()
@ -654,7 +670,7 @@ class MilestoneTests(TestCase):
def test_reset_charter_milestones(self): def test_reset_charter_milestones(self):
m1, m2, group = self.create_test_milestones() 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) login_testing_unauthorized(self, "secretary", url)
# normal get # normal get
@ -809,7 +825,7 @@ class MilestoneTests(TestCase):
def test_ajax_search_docs(self): def test_ajax_search_docs(self):
draft = make_test_data() 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)) dict(q=draft.name))
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
data = json.loads(r.content) data = json.loads(r.content)
@ -821,7 +837,7 @@ class CustomizeWorkflowTests(TestCase):
group = Group.objects.get(acronym="mars") 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) login_testing_unauthorized(self, "secretary", url)
state = State.objects.get(used=True, type="draft-stream-ietf", slug="wg-lc") 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('', urlpatterns = patterns('',
(r'^(?P<acronym>[a-z0-9]+).json$', 'ietf.group.ajax.group_json'), (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 import os
from django.conf import settings
from ietf.group.models import Group, RoleHistory from ietf.group.models import Group, RoleHistory
from ietf.person.models import Email from ietf.person.models import Email
from ietf.utils.history import get_history_object_for, copy_many_to_many_for_history 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): def save_group_in_history(group):
@ -27,28 +26,21 @@ def save_group_in_history(group):
def get_charter_text(group): def get_charter_text(group):
# get file path from settings. Syntesize file name from path, acronym, and suffix # 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:
# 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: with open(filename) as f:
return f.read() return f.read()
except IOError: except IOError:
try: return 'Error Loading Group Charter'
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
def get_area_ads_emails(area): def get_area_ads_emails(area):
if area.acronym == 'none': if area.acronym == 'none':
@ -98,3 +90,18 @@ def save_milestone_in_history(milestone):
copy_many_to_many_for_history(h, milestone) copy_many_to_many_for_history(h, milestone)
return h 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.meeting',
'ietf.utils', 'ietf.utils',
'ietf.redirects', 'ietf.redirects',
'ietf.wginfo',
'ietf.submit', 'ietf.submit',
'ietf.sync', 'ietf.sync',
'ietf.community', 'ietf.community',
@ -268,7 +267,6 @@ STATUS_CHANGE_PATH = '/a/www/ietf-ftp/status-changes'
STATUS_CHANGE_TXT_URL = 'http://www.ietf.org/sc/' STATUS_CHANGE_TXT_URL = 'http://www.ietf.org/sc/'
AGENDA_PATH = '/a/www/www6s/proceedings/' AGENDA_PATH = '/a/www/www6s/proceedings/'
IPR_DOCUMENT_PATH = '/a/www/ietf-ftp/ietf/IPR/' 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_TASK_FILE = '/a/www/www6/iesg/internal/task.txt'
IESG_ROLL_CALL_FILE = '/a/www/www6/iesg/internal/rollcall.txt' IESG_ROLL_CALL_FILE = '/a/www/www6/iesg/internal/rollcall.txt'
IESG_MINUTES_FILE = '/a/www/www6/iesg/internal/minutes.txt' IESG_MINUTES_FILE = '/a/www/www6/iesg/internal/minutes.txt'

View file

@ -254,7 +254,7 @@ class EditSubmissionForm(forms.ModelForm):
return document_date return document_date
class PreapprovalForm(forms.Form): 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): def clean_name(self):
n = self.cleaned_data['name'].strip().lower() n = self.cleaned_data['name'].strip().lower()
@ -268,7 +268,7 @@ class PreapprovalForm(forms.Form):
if components[-1] == "00": if components[-1] == "00":
raise forms.ValidationError("Name appears to end with a revision number -00 - do not include the revision.") raise forms.ValidationError("Name appears to end with a revision number -00 - do not include the revision.")
if len(components) < 4: 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]: if not components[-1]:
raise forms.ValidationError("Name ends with a dash.") raise forms.ValidationError("Name ends with a dash.")
acronym = components[2] acronym = components[2]

View file

@ -331,7 +331,7 @@ def preapprovals_for_user(user):
if has_role(user, "Secretariat"): if has_role(user, "Secretariat"):
return res 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)) res = res.filter(name__regex="draft-[^-]+-(%s)-.*" % "|".join(acronyms))

View file

@ -401,9 +401,9 @@ def approvals(request):
context_instance=RequestContext(request)) context_instance=RequestContext(request))
@role_required("Secretariat", "WG Chair") @role_required("Secretariat", "WG Chair", "RG Chair")
def add_preapproval(request): 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"): if not has_role(request.user, "Secretariat"):
groups = groups.filter(role__person__user=request.user,role__name__in=['ad','chair','delegate','secr']) 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 }, 'form': form },
context_instance=RequestContext(request)) context_instance=RequestContext(request))
@role_required("Secretariat", "WG Chair") @role_required("Secretariat", "WG Chair", "RG Chair")
def cancel_preapproval(request, preapproval_id): def cancel_preapproval(request, preapproval_id):
preapproval = get_object_or_404(Preapproval, pk=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> </ul>
</div></div></li> </div></div></li>
<li style="padding-top:0;"><a href="/wg/">Active WGs</a></li> <li style="padding-top:0;"><a href="{% url "ietf.group.info.active_groups" group_type="wg" %}">Active WGs</a></li>
<li><a href="{% url "ietf.wginfo.views.chartering_wgs" %}">Chartering 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.wginfo.views.bofs" %}">BoFs</a></li> <li><a href="{% url "ietf.group.info.chartering_groups" %}">Chartering</a></li>
<li><a href="http://tools.ietf.org/wg/concluded">Concluded WGs</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> <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 %} {% block content %}
<h1>Charter submission for {{ group.acronym }} {{ group.type.name }}</h1> <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 %} <form class="edit-info" action="" enctype="multipart/form-data" method="post">{% csrf_token %}
<table> <table>
{% for field in form.visible_fields %} {% for field in form.visible_fields %}

View file

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

View file

@ -36,7 +36,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<div class="search-results"> <div class="search-results">
{% if not docs %} {% 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 %} {% else %}
{% if meta.max %} {% 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 }} {% autoescape off %}{% load ietf_filters %}{% for area in areas %}{% for group in area.groups %}{{ group.acronym }}
{% endfor %}{% endfor %} {% 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 %} {{ area.acronym|upper }} - {{ area.name }}{% endfor %}
{% for group in groups %} {% for group in groups %}
{{ group.name }} ({{ group.acronym }}) -- {{ group.parent.acronym|upper }} {{ 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 %} {{ ad.person.plain_name }} <{{ ad.email.address }}>{% endfor %}
{% for group in area.groups %}{{ group.name }} ({{ group.acronym }}) {% for group in area.groups %}{{ group.name }} ({{ group.acronym }})
{% include "wginfo/group_entry.txt" %} {% include "group/group_entry.txt" %}
{% endfor %}{% endfor %}{% endautoescape %} {% 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"> <table class="ietf-wg-table">
{% for group in area.groups %} {% for group in area.groups %}
<tr> <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="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="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> <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> <p>Groups in the BoF state</p>
{% if user|has_role:"Area Director,Secretariat" %} {% 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 %} {% endif %}
{% if not groups %} {% if not groups %}
@ -25,7 +25,7 @@
{% for g in groups %} {% for g in groups %}
<tr class="{{ forloop.counter|divisibleby:2|yesno:"oddrow,evenrow" }}"> <tr class="{{ forloop.counter|divisibleby:2|yesno:"oddrow,evenrow" }}">
<td class="acronym"> <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>
<td class="title"> <td class="title">
<a {%comment%}href="{% url "doc_view" name=g.charter.name %}"{%endcomment%}>{{ g.name }}</a> <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" %} {% 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 %} {% block morecss %}
#id_instructions { #id_instructions {
@ -14,7 +14,7 @@ form.conclude .actions {
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<h1>Request closing of {{ wg.acronym }} {{ wg.type.name }}</h1> <h1>Request closing of {{ group.acronym }} {{ group.type.name }}</h1>
<p> <p>
Please provide instructions regarding the disposition of each Please provide instructions regarding the disposition of each
@ -29,7 +29,7 @@ form.conclude .actions {
{{ form.as_table }} {{ form.as_table }}
<tr> <tr>
<td colspan="2" class="actions"> <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"/> <input class="button" type="submit" value="Send request"/>
</td> </td>
</tr> </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> <h1>Customize Workflow for {{ group.acronym }} {{ group.type.name }}</h1>
<p>Below you can customize the draft states and tags used in the <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> 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 <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> in <a href="http://tools.ietf.org/html/rfc6174#section-4.1">Section 4.1 of RFC6174</a>.</p>
{% endif %}
<h3>States</h3> <h3>States</h3>

View file

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

View file

@ -1,10 +1,10 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %} {% block title %}
{% if wg %} {% if group %}
Edit WG {{ wg.acronym }} Edit {{ group.type.name }} {{ group.acronym }}
{% else %} {% else %}
Start chartering new WG Start chartering new group
{% endif %} {% endif %}
{% endblock %} {% endblock %}
@ -28,12 +28,12 @@ form.edit #id_urls { height: 4em; }
{% load ietf_filters %} {% load ietf_filters %}
<h1> <h1>
{% if action == "edit" %} {% if action == "edit" %}
Edit WG {{ wg.acronym }} Edit {{ group.type.name }} {{ group.acronym }}
{% else %} {% else %}
{% if action == "charter" %} {% if action == "charter" %}
Start chartering new WG Start chartering new group
{% else %} {% else %}
Create new WG or BoF Create new group or BoF
{% endif %} {% endif %}
{% endif %} {% endif %}
</h1> </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> so. New accounts can be <a href="{% url "create_account" %}">created here</a>.</p>
<form class="edit" action="" method="post">{% csrf_token %} <form class="edit" action="" method="post">{% csrf_token %}
{% for field in form.hidden_fields %}{{ field }}{% endfor %}
<table> <table>
{% for field in form.visible_fields %} {% for field in form.visible_fields %}
<tr> <tr>
<th>{{ field.label_tag }} {% if field.field.required %}*{% endif %}</th> <th>{{ field.label_tag }} {% if field.field.required %}*{% endif %}</th>
<td>{{ field }} <td>{{ field }}
{% if field.name == "ad" and user|has_role:"Area Director" %} {% if field.name == "ad" and request.user|has_role:"Area Director" %}
<label><input type="checkbox" name="ad" value="{{ login.pk }}" /> Assign to me</label> <label><input type="checkbox" name="ad" value="{{ request.user.person.pk }}" /> Assign to me</label>
{% endif %} {% endif %}
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %} {% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
{{ field.errors }} {{ field.errors }}
@ -68,7 +69,7 @@ so. New accounts can be <a href="{% url "create_account" %}">created here</a>.</
<td></td> <td></td>
<td class="actions"> <td class="actions">
{% if action == "edit" %} {% 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"/> <input class="button" type="submit" value="Save"/>
{% else %} {% else %}
{% if action == "charter" %} {% 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> <noscript>This page depends on Javascript being enabled to work properly.</noscript>
<p>Links: <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> - <a href="{% url "doc_view" name=group.charter.canonical_name %}">{{ group.charter.canonical_name }}</a>
</p> </p>
@ -51,14 +51,13 @@ tr.milestone.add { font-style: italic; }
{% if needs_review %} {% if needs_review %}
Note that as {{ group.type.name }} Chair you cannot edit descriptions of existing 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 milestones and milestones you add are subject to review by the {{ reviewer }}.
Director.
{% endif %} {% endif %}
</p> </p>
{% if can_reset %} {% if can_reset %}
<p> <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 }}. this list</a> to the milestones currently in use for the {{ group.acronym }} {{ group.type.name }}.
</p> </p>
{% endif %} {% endif %}
@ -84,14 +83,14 @@ this list</a> to the milestones currently in use for the {{ group.acronym }} {{
</td> </td>
</tr> </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 %} {% 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="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 "wginfo/milestone_form.html" with form=empty_form %}</td></tr> <tr class="edit-milestone template"><td colspan="2">{% include "group/milestone_form.html" with form=empty_form %}</td></tr>
</table> </table>
<div class="actions"> <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 class="button" type="submit" data-labelsave="Save" data-labelreview="Review changes" value="Save" style="display:none"/>
<input type="hidden" name="action" value="save"> <input type="hidden" name="action" value="save">
</div> </div>

View file

@ -66,15 +66,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<div class="ietf-navset"> <div class="ietf-navset">
<div> <div>
<a {% if selected == "documents" %}class="selected"{% else %}href="{% url "ietf.wginfo.views.group_documents" acronym=group.acronym %}"{% endif %}>Documents</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.wginfo.views.group_charter" acronym=group.acronym %}"{% endif %}>Charter</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.wginfo.views.history" acronym=group.acronym %}"{% endif %}>History</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.wginfo.views.dependencies_pdf' acronym=group.acronym %}">Dependency Graph</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:" %} {% 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> | <a href="{{ group.list_archive }}">List Archive &raquo;</a>
{% endif %} {% endif %}
{% if group.has_tools_page %} {% 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 %} {% endif %}
</div> </div>

View file

@ -1,4 +1,4 @@
{% extends "wginfo/group_base.html" %} {% extends "group/group_base.html" %}
{% load ietf_filters %} {% load ietf_filters %}
{% block group_subtitle %}Charter{% endblock %} {% 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"> <div class="ietf-box ietf-group-details">
{% if group.state_id == "conclude" %} {% 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> is occasionally incorrect.</span>
{% endif %} {% endif %}
@ -26,7 +26,7 @@ is occasionally incorrect.</span>
<tr><td>Acronym:</td><td>{{ group.acronym }}</td></tr> <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> <tr><td>{{ group.parent.type.name }}:</td><td>{{ group.parent.name }} ({{ group.parent.acronym }})</td></tr>
{% endif %} {% 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 }}) <a href="{% url "doc_view" name=group.charter.name %}">{{ group.charter.name }}-{{ group.charter.rev }}</a> ({{ group.charter.get_state.name }})
{% else %} {% else %}
none none
{% if user|has_role:"Area Director,Secretariat" %} {% if can_manage %}
- <a href="{% url "ietf.wginfo.edit.submit_initial_charter" acronym=group.acronym %}">Submit Charter</a> - <a href="{% url "ietf.group.edit.submit_initial_charter" group_type=group.type_id acronym=group.acronym %}">Submit Charter</a>
{% endif %} {% endif %}
{% endif %} {% endif %}
</td> </td>
@ -64,6 +64,7 @@ is occasionally incorrect.</span>
</td> </td>
</tr> </tr>
{% if group.parent.type_id == "area" %}
<tr><td>Area Director:</td> <tr><td>Area Director:</td>
<td> <td>
{% if group.areadirector %} {% if group.areadirector %}
@ -71,6 +72,7 @@ is occasionally incorrect.</span>
{% else %}?{% endif %} {% else %}?{% endif %}
</td> </td>
</tr> </tr>
{% endif %}
{% if group.techadvisors %} {% if group.techadvisors %}
<tr> <tr>
@ -142,7 +144,7 @@ is occasionally incorrect.</span>
{% with group.groupurl_set.all as urls %} {% with group.groupurl_set.all as urls %}
{% if 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 %} {% for url in urls %}
<a href="{{ url.url }}">{{ url.name }}</a>{% if not forloop.last %}, {% endif %} <a href="{{ url.url }}">{{ url.name }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %} {% endfor %}
@ -150,17 +152,17 @@ is occasionally incorrect.</span>
{% endif %} {% endif %}
{% endwith %} {% 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> <p>{{ group.charter_text|escape|format_charter|safe }}</p>
<h2>{% if group.state_id == "proposed" %}Proposed{% endif %} Milestones</h2> <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 %} {% if milestones_in_review %}
<p>+ {{ milestones_in_review|length }} new milestone{{ milestones_in_review|pluralize }} <p>+ {{ milestones_in_review|length }} new milestone{{ milestones_in_review|pluralize }}
currently in Area Director review.</p> currently in {{ milestone_reviewer }} review.</p>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -1,4 +1,4 @@
{% extends "wginfo/group_base.html" %} {% extends "group/group_base.html" %}
{% block group_subtitle %}Documents{% endblock %} {% block group_subtitle %}Documents{% endblock %}
@ -7,7 +7,7 @@
{% include "doc/search/search_results.html" %} {% 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> </div>
{% endblock group_content %} {% endblock group_content %}

View file

@ -1,4 +1,4 @@
{% extends "wginfo/group_base.html" %} {% extends "group/group_base.html" %}
{% load ietf_filters %} {% load ietf_filters %}
{% block group_subtitle %}History{% endblock %} {% 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:"" }}"/> <input type="hidden" name="prefix" value="{{ form.prefix|default:"" }}"/>
{{ form.id }} {{ form.id }}
@ -26,7 +26,7 @@
</tr> </tr>
<tr class="docs"> <tr class="docs">
<td>Drafts:</td> <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 }} {{ form.docs.errors }}
</td> </td>
</tr> </tr>
@ -35,7 +35,7 @@
<td>Review:</td> <td>Review:</td>
<td class="accept"> <td class="accept">
This milestone is not active yet, awaiting 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> </td>
</tr> </tr>
{% endif %} {% 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 %} {% for m in milestones %}"{{ m.desc }}"{% if m.days_ready != None %}
Waiting for {{ m.days_ready }} day{{ m.days_ready|pluralize }}.{% endif %} Waiting for {{ m.days_ready }} day{{ m.days_ready|pluralize }}.{% endif %}

View file

@ -29,7 +29,7 @@
{% endfor %} {% endfor %}
<div class="actions"> <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"/> <input type="submit" value="Reset charter milestones"/>
</div> </div>
</form> </form>

View file

@ -15,9 +15,9 @@ div.milestones-for-group { margin: 0.5em 0; }
{% for g in ad.groups_needing_review %} {% 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 %}
{% endfor %} {% endfor %}

View file

@ -18,8 +18,8 @@ form .actions a { display: inline-block; margin-right: 1em; }
<h2>Add Pre-Approval</h2> <h2>Add Pre-Approval</h2>
<p>You can register a pre-approved draft name. Then the WG Chair <p>You can register a pre-approved draft name. Then the chair
approval step of WG -00 submissions is suspended for that draft name approval step of group -00 submissions is suspended for that draft name
so a future submission is posted to the data tracker immediately.</p> 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 <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> like <code>.txt</code> in the name.</p>
{% if user|has_role:"Secretariat" %} {% 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 %} {% 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> <table>
{% for g in groups %} {% 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 %} {% endfor %}
</table> </table>
</p> </p>
@ -56,8 +56,8 @@ like <code>.txt</code> in the name.</p>
<tr> <tr>
<td class="actions" colspan="2"> <td class="actions" colspan="2">
<a href="{% url "submit_approvals" %}#preapprovals">Back</a> <a class="button" href="{% url "submit_approvals" %}#preapprovals">Cancel</a>
<input type="submit" value="Add pre-approval" /> <input class="button" type="submit" value="Add pre-approval" />
</td> </td>
</tr> </tr>
</table> </table>
@ -68,7 +68,8 @@ like <code>.txt</code> in the name.</p>
{% block scripts %} {% block scripts %}
jQuery(function () { jQuery(function () {
jQuery(".name-template").click(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 %} {% 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> <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> <p>You can <a href="{% url "submit_add_preapproval" %}">add a pre-approval</a>.</p>
{% endif %} {% endif %}

View file

@ -15,9 +15,9 @@ Pre-approval of <b>{{ preapproval.name }}</b> by {{ preapproval.by }}
on {{ preapproval.time }}. on {{ preapproval.time }}.
<form class="actions" action="" method="post">{% csrf_token %} <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="hidden" name="action" value="cancel" />
<input type="submit" value="Cancel pre-approval" /> <input class="button" type="submit" value="Cancel pre-approval" />
</form> </form>
{% endblock %} {% 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'^sitemap.xml$', 'django.contrib.sitemaps.views.index', { 'sitemaps': sitemaps}),
(r'^submit/', include('ietf.submit.urls')), (r'^submit/', include('ietf.submit.urls')),
(r'^sync/', include('ietf.sync.urls')), (r'^sync/', include('ietf.sync.urls')),
(r'^wg/', include('ietf.wginfo.urls')), (r'^(?P<group_type>(wg|rg))/', include('ietf.group.urls_info')),
(r'^stream/', include('ietf.group.stream_urls')), (r'^stream/', include('ietf.group.urls_stream')),
(r'^nomcom/', include('ietf.nomcom.urls')), (r'^nomcom/', include('ietf.nomcom.urls')),
(r'^templates/', include('ietf.dbtemplate.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),
)