Merged in support for RG pages, from branch/iola/rg-support.
- Legacy-Id: 7866
This commit is contained in:
commit
9ff2b9ff9a
11
changelog
11
changelog
|
@ -1,3 +1,14 @@
|
|||
ietfdb (5.5.0) ietf; urgency=medium
|
||||
|
||||
This is a feature release, which introduces pages and workflow support for
|
||||
IRTF RGs, similar to what is available for IETF WGs. You'll find the rg pages
|
||||
under /rg/, for instance https://datatracker.ietf.org/rg/cfrg . Having this
|
||||
new baseline in place, I'm sure we'll get requests for refinements, but
|
||||
that's the name of the game :-)
|
||||
|
||||
-- Henrik Levkowetz <henrik@levkowetz.com> 03 Jun 2014 23:12:44 -0200
|
||||
|
||||
|
||||
ietfdb (5.4.3) ietf; urgency=medium
|
||||
|
||||
* Merged in [7772] from rjsparks@nostrum.com:
|
||||
|
|
|
@ -11,7 +11,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ietf.settings")
|
|||
|
||||
syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_LOCAL0)
|
||||
|
||||
from ietf.wginfo.mails import *
|
||||
from ietf.group.mails import *
|
||||
|
||||
today = datetime.date.today()
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ class WGField(DisplayField):
|
|||
if raw:
|
||||
return document.group.acronym
|
||||
else:
|
||||
return '<a href="%s">%s</a>' % (urlreverse('wg_docs', kwargs={'acronym':document.group.acronym}), document.group.acronym) if (document.group and document.group.acronym != 'none') else ''
|
||||
return '<a href="%s">%s</a>' % (urlreverse('group_docs', kwargs=dict(group_type=document.group.type_id, acronym=document.group.acronym)), document.group.acronym) if (document.group and document.group.acronym != 'none') else ''
|
||||
|
||||
|
||||
class ADField(DisplayField):
|
||||
|
|
|
@ -270,8 +270,12 @@ class Document(DocumentInfo):
|
|||
a = self.docalias_set.filter(name__startswith="rfc")
|
||||
if a:
|
||||
name = a[0].name
|
||||
elif self.type_id == "charter":
|
||||
return "charter-ietf-%s" % self.chartered_group.acronym
|
||||
# elif self.type_id == "charter":
|
||||
# if self.group.type.slug == "rg":
|
||||
# top_org = "irtf"
|
||||
# else:
|
||||
# top_org = "ietf"
|
||||
# return "charter-%s-%s" % (top_org, self.chartered_group.acronym)
|
||||
return name
|
||||
|
||||
def canonical_docalias(self):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import os, datetime, shutil, textwrap, json
|
||||
|
||||
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound, Http404
|
||||
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound, HttpResponseForbidden, Http404
|
||||
from django.shortcuts import render_to_response, get_object_or_404, redirect
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
from django.template import RequestContext
|
||||
|
@ -9,6 +9,7 @@ from django.utils.html import escape
|
|||
from django.utils.safestring import mark_safe
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
|
@ -21,7 +22,7 @@ from ietf.doc.utils_charter import ( historic_milestones_for_charter,
|
|||
approved_revision, default_review_text, default_action_text, email_state_changed,
|
||||
generate_ballot_writeup, generate_issue_ballot_mail, next_approved_revision, next_revision )
|
||||
from ietf.group.models import ChangeStateGroupEvent, MilestoneGroupEvent
|
||||
from ietf.group.utils import save_group_in_history, save_milestone_in_history
|
||||
from ietf.group.utils import save_group_in_history, save_milestone_in_history, can_manage_group_type
|
||||
from ietf.iesg.models import TelechatDate
|
||||
from ietf.ietfauth.utils import has_role, role_required
|
||||
from ietf.name.models import GroupStateName
|
||||
|
@ -29,29 +30,38 @@ from ietf.person.models import Person
|
|||
from ietf.utils.history import find_history_active_at
|
||||
from ietf.utils.mail import send_mail_preformatted
|
||||
from ietf.utils.textupload import get_cleaned_text_file_content
|
||||
from ietf.wginfo.mails import email_secretariat
|
||||
from ietf.group.mails import email_secretariat
|
||||
|
||||
|
||||
class ChangeStateForm(forms.Form):
|
||||
charter_state = forms.ModelChoiceField(State.objects.filter(used=True, type="charter", slug__in=["infrev", "intrev", "extrev", "iesgrev"]), label="Charter state", empty_label=None, required=False)
|
||||
charter_state = forms.ModelChoiceField(State.objects.filter(used=True, type="charter"), label="Charter state", empty_label=None, required=False)
|
||||
initial_time = forms.IntegerField(initial=0, label="Review time", help_text="(in weeks)", required=False)
|
||||
message = forms.CharField(widget=forms.Textarea, help_text="Leave blank to change state without notifying the Secretariat", required=False, label=mark_safe("Message to<br> Secretariat"))
|
||||
comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the charter history", required=False)
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.hide = kwargs.pop('hide', None)
|
||||
group = kwargs.pop('group')
|
||||
super(ChangeStateForm, self).__init__(*args, **kwargs)
|
||||
state_field = self.fields["charter_state"]
|
||||
if group.type_id == "wg":
|
||||
state_field.queryset = state_field.queryset.filter(slug__in=("infrev", "intrev", "extrev", "iesgrev"))
|
||||
else:
|
||||
state_field.queryset = state_field.queryset.filter(slug__in=("intrev", "extrev", "approved"))
|
||||
# hide requested fields
|
||||
if self.hide:
|
||||
for f in self.hide:
|
||||
self.fields[f].widget = forms.HiddenInput
|
||||
|
||||
@role_required("Area Director", "Secretariat")
|
||||
@login_required
|
||||
def change_state(request, name, option=None):
|
||||
"""Change state of charter, notifying parties as necessary and
|
||||
logging the change as a comment."""
|
||||
charter = get_object_or_404(Document, type="charter", name=name)
|
||||
group = charter.group
|
||||
|
||||
if not can_manage_group_type(request.user, group.type_id):
|
||||
return HttpResponseForbidden("You don't have permission to access this view")
|
||||
|
||||
chartering_type = get_chartering_type(charter)
|
||||
|
||||
initial_review = charter.latest_event(InitialReviewDocEvent, type="initial_review")
|
||||
|
@ -61,13 +71,17 @@ def change_state(request, name, option=None):
|
|||
login = request.user.person
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ChangeStateForm(request.POST)
|
||||
form = ChangeStateForm(request.POST, group=group)
|
||||
if form.is_valid():
|
||||
clean = form.cleaned_data
|
||||
charter_rev = charter.rev
|
||||
|
||||
if option in ("initcharter", "recharter"):
|
||||
charter_state = State.objects.get(used=True, type="charter", slug="infrev")
|
||||
if group.type_id == "wg":
|
||||
charter_state = State.objects.get(used=True, type="charter", slug="infrev")
|
||||
else:
|
||||
charter_state = clean['charter_state']
|
||||
|
||||
# make sure we have the latest revision set, if we
|
||||
# abandoned a charter before, we could have reset the
|
||||
# revision to latest approved
|
||||
|
@ -133,7 +147,7 @@ def change_state(request, name, option=None):
|
|||
|
||||
email_state_changed(request, charter, "State changed to %s." % charter_state)
|
||||
|
||||
if charter_state.slug == "intrev":
|
||||
if charter_state.slug == "intrev" and group.type_id == "wg":
|
||||
if request.POST.get("ballot_wo_extern"):
|
||||
create_ballot_if_not_open(charter, login, "r-wo-ext")
|
||||
else:
|
||||
|
@ -142,6 +156,9 @@ def change_state(request, name, option=None):
|
|||
default_action_text(group, charter, login)
|
||||
elif charter_state.slug == "iesgrev":
|
||||
create_ballot_if_not_open(charter, login, "approve")
|
||||
elif charter_state.slug == "approved":
|
||||
change_group_state_after_charter_approval(group, login)
|
||||
fix_charter_revision_after_approval(charter, login)
|
||||
|
||||
if charter_state.slug == "infrev" and clean["initial_time"] and clean["initial_time"] != 0:
|
||||
e = InitialReviewDocEvent(type="initial_review", by=login, doc=charter)
|
||||
|
@ -151,20 +168,24 @@ def change_state(request, name, option=None):
|
|||
|
||||
return redirect('doc_view', name=charter.name)
|
||||
else:
|
||||
if option == "recharter":
|
||||
hide = ['initial_time', 'charter_state', 'message']
|
||||
init = dict()
|
||||
elif option == "initcharter":
|
||||
hide = ['charter_state']
|
||||
init = dict(initial_time=1, message='%s has initiated chartering of the proposed %s:\n "%s" (%s).' % (login.plain_name(), group.type.name, group.name, group.acronym))
|
||||
elif option == "abandon":
|
||||
hide = ['initial_time']
|
||||
s = charter.get_state()
|
||||
init = dict(charter_state=s.pk if s and option != "recharter" else None)
|
||||
|
||||
if option == "abandon":
|
||||
hide = ['initial_time', 'charter_state']
|
||||
init = dict(message='%s has abandoned the chartering effort on the %s:\n "%s" (%s).' % (login.plain_name(), group.type.name, group.name, group.acronym))
|
||||
else:
|
||||
hide = ['initial_time']
|
||||
s = charter.get_state()
|
||||
init = dict(charter_state=s.pk if s else None)
|
||||
form = ChangeStateForm(hide=hide, initial=init)
|
||||
|
||||
if group.type_id == "wg":
|
||||
if option == "recharter":
|
||||
hide = ['initial_time', 'charter_state', 'message']
|
||||
init = dict()
|
||||
elif option == "initcharter":
|
||||
hide = ['charter_state']
|
||||
init = dict(initial_time=1, message='%s has initiated chartering of the proposed %s:\n "%s" (%s).' % (login.plain_name(), group.type.name, group.name, group.acronym))
|
||||
elif option == "abandon":
|
||||
hide = ['initial_time', 'charter_state']
|
||||
init = dict(message='%s has abandoned the chartering effort on the %s:\n "%s" (%s).' % (login.plain_name(), group.type.name, group.name, group.acronym))
|
||||
form = ChangeStateForm(hide=hide, initial=init, group=group)
|
||||
|
||||
prev_charter_state = None
|
||||
charter_hists = DocHistory.objects.filter(doc=charter).exclude(states__type="charter", states__slug=charter.get_state_slug()).order_by("-time")[:1]
|
||||
|
@ -182,13 +203,15 @@ def change_state(request, name, option=None):
|
|||
def state_pk(slug):
|
||||
return State.objects.get(used=True, type="charter", slug=slug).pk
|
||||
|
||||
info_msg = {
|
||||
state_pk("infrev"): 'The %s "%s" (%s) has been set to Informal IESG review by %s.' % (group.type.name, group.name, group.acronym, login.plain_name()),
|
||||
state_pk("intrev"): 'The %s "%s" (%s) has been set to Internal review by %s.\nPlease place it on the next IESG telechat and inform the IAB.' % (group.type.name, group.name, group.acronym, login.plain_name()),
|
||||
state_pk("extrev"): 'The %s "%s" (%s) has been set to External review by %s.\nPlease send out the external review announcement to the appropriate lists.\n\nSend the announcement to other SDOs: Yes\nAdditional recipients of the announcement: ' % (group.type.name, group.name, group.acronym, login.plain_name()),
|
||||
}
|
||||
info_msg = {}
|
||||
if group.type_id == "wg":
|
||||
info_msg[state_pk("infrev")] = 'The %s "%s" (%s) has been set to Informal IESG review by %s.' % (group.type.name, group.name, group.acronym, login.plain_name())
|
||||
info_msg[state_pk("intrev")] = 'The %s "%s" (%s) has been set to Internal review by %s.\nPlease place it on the next IESG telechat and inform the IAB.' % (group.type.name, group.name, group.acronym, login.plain_name())
|
||||
info_msg[state_pk("extrev")] = 'The %s "%s" (%s) has been set to External review by %s.\nPlease send out the external review announcement to the appropriate lists.\n\nSend the announcement to other SDOs: Yes\nAdditional recipients of the announcement: ' % (group.type.name, group.name, group.acronym, login.plain_name())
|
||||
|
||||
states_for_ballot_wo_extern = State.objects.filter(used=True, type="charter", slug="intrev").values_list("pk", flat=True)
|
||||
states_for_ballot_wo_extern = State.objects.none()
|
||||
if group.type_id == "wg":
|
||||
states_for_ballot_wo_extern = State.objects.filter(used=True, type="charter", slug="intrev").values_list("pk", flat=True)
|
||||
|
||||
return render_to_response('doc/charter/change_state.html',
|
||||
dict(form=form,
|
||||
|
@ -352,17 +375,16 @@ class UploadForm(forms.Form):
|
|||
else:
|
||||
destination.write(self.cleaned_data['content'].encode("utf-8"))
|
||||
|
||||
@role_required('Area Director','Secretariat')
|
||||
def submit(request, name=None, acronym=None, option=None):
|
||||
if name:
|
||||
if not name.startswith('charter-'):
|
||||
name = "charter-ietf-" + name
|
||||
elif acronym:
|
||||
name = "charter-ietf-" + acronym
|
||||
@login_required
|
||||
def submit(request, name=None, option=None):
|
||||
if not name.startswith('charter-'):
|
||||
raise Http404
|
||||
|
||||
charter = get_object_or_404(Document, type="charter", name=name)
|
||||
group = charter.group
|
||||
|
||||
login = request.user.person
|
||||
if not can_manage_group_type(request.user, group.type_id):
|
||||
return HttpResponseForbidden("You don't have permission to access this view")
|
||||
|
||||
path = os.path.join(settings.CHARTER_PATH, '%s-%s.txt' % (charter.canonical_name(), charter.rev))
|
||||
not_uploaded_yet = charter.rev.endswith("-00") and not os.path.exists(path)
|
||||
|
@ -386,7 +408,7 @@ def submit(request, name=None, acronym=None, option=None):
|
|||
|
||||
charter.rev = next_rev
|
||||
|
||||
e = NewRevisionDocEvent(doc=charter, by=login, type="new_revision")
|
||||
e = NewRevisionDocEvent(doc=charter, by=request.user.person, type="new_revision")
|
||||
e.desc = "New version available: <b>%s-%s.txt</b>" % (charter.canonical_name(), charter.rev)
|
||||
e.rev = charter.rev
|
||||
e.save()
|
||||
|
@ -423,7 +445,8 @@ def submit(request, name=None, acronym=None, option=None):
|
|||
return render_to_response('doc/charter/submit.html',
|
||||
{'form': form,
|
||||
'next_rev': next_rev,
|
||||
'group': group },
|
||||
'group': group,
|
||||
'name': name },
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
class AnnouncementTextForm(forms.Form):
|
||||
|
@ -568,6 +591,46 @@ def ballot_writeupnotes(request, name):
|
|||
),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
def change_group_state_after_charter_approval(group, by):
|
||||
new_state = GroupStateName.objects.get(slug="active")
|
||||
if group.state == new_state:
|
||||
return None
|
||||
|
||||
save_group_in_history(group)
|
||||
group.state = new_state
|
||||
group.time = datetime.datetime.now()
|
||||
group.save()
|
||||
|
||||
# create an event for the group state change, too
|
||||
e = ChangeStateGroupEvent(group=group, type="changed_state")
|
||||
e.time = group.time
|
||||
e.by = by
|
||||
e.state_id = "active"
|
||||
e.desc = "Charter approved, group active"
|
||||
e.save()
|
||||
|
||||
return e
|
||||
|
||||
def fix_charter_revision_after_approval(charter, by):
|
||||
# according to spec, 00-02 becomes 01, so copy file and record new revision
|
||||
try:
|
||||
old = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), charter.rev))
|
||||
new = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), next_approved_revision(charter.rev)))
|
||||
shutil.copy(old, new)
|
||||
except IOError:
|
||||
return HttpResponse("There was an error copying %s to %s" %
|
||||
('%s-%s.txt' % (charter.canonical_name(), charter.rev),
|
||||
'%s-%s.txt' % (charter.canonical_name(), next_approved_revision(charter.rev))))
|
||||
|
||||
e = NewRevisionDocEvent(doc=charter, by=by, type="new_revision")
|
||||
e.rev = next_approved_revision(charter.rev)
|
||||
e.desc = "New version available: <b>%s-%s.txt</b>" % (charter.canonical_name(), e.rev)
|
||||
e.save()
|
||||
|
||||
charter.rev = e.rev
|
||||
charter.time = e.time
|
||||
charter.save()
|
||||
|
||||
@role_required("Secretariat")
|
||||
def approve(request, name):
|
||||
"""Approve charter, changing state, fixing revision, copying file to final location."""
|
||||
|
@ -599,43 +662,13 @@ def approve(request, name):
|
|||
|
||||
change_description = e.desc
|
||||
|
||||
new_state = GroupStateName.objects.get(slug="active")
|
||||
if group.state != new_state:
|
||||
save_group_in_history(group)
|
||||
group.state = new_state
|
||||
group.time = e.time
|
||||
group.save()
|
||||
|
||||
# create an event for the wg state change, too
|
||||
e = ChangeStateGroupEvent(group=group, type="changed_state")
|
||||
e.time = group.time
|
||||
e.by = login
|
||||
e.state_id = "active"
|
||||
e.desc = "Charter approved, group active"
|
||||
e.save()
|
||||
|
||||
change_description += " and %s state has been changed to %s" % (group.type.name, new_state.name)
|
||||
group_state_change_event = change_group_state_after_charter_approval(group, login)
|
||||
if group_state_change_event:
|
||||
change_description += " and group state has been changed to %s" % group.state.name
|
||||
|
||||
add_state_change_event(charter, login, prev_charter_state, new_charter_state)
|
||||
|
||||
# according to spec, 00-02 becomes 01, so copy file and record new revision
|
||||
try:
|
||||
old = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), charter.rev))
|
||||
new = os.path.join(charter.get_file_path(), '%s-%s.txt' % (charter.canonical_name(), next_approved_revision(charter.rev)))
|
||||
shutil.copy(old, new)
|
||||
except IOError:
|
||||
return HttpResponse("There was an error copying %s to %s" %
|
||||
('%s-%s.txt' % (charter.canonical_name(), charter.rev),
|
||||
'%s-%s.txt' % (charter.canonical_name(), next_approved_revision(charter.rev))))
|
||||
|
||||
e = NewRevisionDocEvent(doc=charter, by=login, type="new_revision")
|
||||
e.rev = next_approved_revision(charter.rev)
|
||||
e.desc = "New version available: <b>%s-%s.txt</b>" % (charter.canonical_name(), e.rev)
|
||||
e.save()
|
||||
|
||||
charter.rev = e.rev
|
||||
charter.time = e.time
|
||||
charter.save()
|
||||
fix_charter_revision_after_approval(charter, login)
|
||||
|
||||
email_secretariat(request, group, "Charter state changed to %s" % new_charter_state.name, change_description)
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ from ietf.community.models import CommunityList
|
|||
from ietf.doc.mails import email_ad
|
||||
from ietf.doc.views_status_change import RELATION_SLUGS as status_change_relationships
|
||||
from ietf.group.models import Role
|
||||
from ietf.group.utils import can_manage_group_type
|
||||
from ietf.ietfauth.utils import has_role, is_authorized_in_doc_stream, user_is_person, role_required
|
||||
from ietf.name.models import StreamName, BallotPositionName
|
||||
from ietf.person.models import Email
|
||||
|
@ -63,10 +64,10 @@ def render_document_top(request, doc, tab, name):
|
|||
ballot = doc.latest_event(BallotDocEvent, type="created_ballot")
|
||||
if doc.type_id in ("draft","conflrev", "statchg"):
|
||||
tabs.append(("IESG Evaluation Record", "ballot", urlreverse("doc_ballot", kwargs=dict(name=name)), ballot, None if ballot else "IESG Evaluation Ballot has not been created yet"))
|
||||
elif doc.type_id == "charter":
|
||||
tabs.append(("IESG Review", "ballot", urlreverse("doc_ballot", kwargs=dict(name=name)), ballot, None if ballot else "IEST Review Ballot has not been created yet"))
|
||||
elif doc.type_id == "charter" and doc.group.type_id == "wg":
|
||||
tabs.append(("IESG Review", "ballot", urlreverse("doc_ballot", kwargs=dict(name=name)), ballot, None if ballot else "IESG Review Ballot has not been created yet"))
|
||||
|
||||
if doc.type_id not in ["conflrev", "statchg"]:
|
||||
if doc.type_id == "draft" or (doc.type_id == "charter" and doc.group.type_id == "wg"):
|
||||
tabs.append(("IESG Writeups", "writeup", urlreverse("doc_writeup", kwargs=dict(name=name)), True))
|
||||
|
||||
tabs.append(("History", "history", urlreverse("doc_history", kwargs=dict(name=name)), True))
|
||||
|
@ -239,7 +240,7 @@ def document_main(request, name, rev=None):
|
|||
elif group.type_id in ("rg", "wg"):
|
||||
submission = "%s %s" % (group.acronym, group.type)
|
||||
if group.type_id == "wg":
|
||||
submission = "<a href=\"%s\">%s</a>" % (urlreverse("wg_docs", kwargs=dict(acronym=doc.group.acronym)), submission)
|
||||
submission = "<a href=\"%s\">%s</a>" % (urlreverse("group_docs", kwargs=dict(group_type=doc.group.type_id, acronym=doc.group.acronym)), submission)
|
||||
if doc.stream_id and doc.get_state_slug("draft-stream-%s" % doc.stream_id) == "c-adopt":
|
||||
submission = "candidate for %s" % submission
|
||||
|
||||
|
@ -410,6 +411,8 @@ def document_main(request, name, rev=None):
|
|||
if chartering and not snapshot:
|
||||
milestones = doc.group.groupmilestone_set.filter(state="charter")
|
||||
|
||||
can_manage = can_manage_group_type(request.user, doc.group.type_id)
|
||||
|
||||
return render_to_response("doc/document_charter.html",
|
||||
dict(doc=doc,
|
||||
top=top,
|
||||
|
@ -422,6 +425,7 @@ def document_main(request, name, rev=None):
|
|||
ballot_summary=ballot_summary,
|
||||
group=group,
|
||||
milestones=milestones,
|
||||
can_manage=can_manage,
|
||||
),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ from django.conf.urls import patterns
|
|||
from django.views.generic import RedirectView
|
||||
|
||||
from ietf.doc.feeds import DocumentChangesFeed, InLastCallFeed
|
||||
from ietf.wginfo.feeds import GroupChangesFeed
|
||||
from ietf.group.feeds import GroupChangesFeed
|
||||
from ietf.iesg.feeds import IESGAgendaFeed
|
||||
from ietf.ipr.feeds import LatestIprDisclosuresFeed
|
||||
from ietf.liaisons.feeds import LiaisonStatementsFeed
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# edit/create view for WGs
|
||||
# edit/create view for groups
|
||||
|
||||
import re
|
||||
import os
|
||||
|
@ -6,11 +6,11 @@ import datetime
|
|||
import shutil
|
||||
|
||||
from django import forms
|
||||
from django.shortcuts import render_to_response, get_object_or_404, redirect
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.template import RequestContext
|
||||
from django.utils.html import mark_safe
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
|
@ -18,15 +18,15 @@ from ietf.doc.models import DocAlias, DocTagName, Document, State, save_document
|
|||
from ietf.doc.utils import get_tags_for_stream_id
|
||||
from ietf.group.models import ( Group, Role, GroupEvent, GroupHistory, GroupStateName,
|
||||
GroupStateTransitions, GroupTypeName, GroupURL, ChangeStateGroupEvent )
|
||||
from ietf.group.utils import save_group_in_history
|
||||
from ietf.ietfauth.utils import role_required, has_role
|
||||
from ietf.group.utils import save_group_in_history, can_manage_group_type
|
||||
from ietf.ietfauth.utils import has_role
|
||||
from ietf.person.forms import EmailsField
|
||||
from ietf.person.models import Person, Email
|
||||
from ietf.wginfo.mails import email_secretariat
|
||||
from ietf.group.mails import email_secretariat
|
||||
|
||||
MAX_GROUP_DELEGATES = 3
|
||||
|
||||
class WGForm(forms.Form):
|
||||
class GroupForm(forms.Form):
|
||||
name = forms.CharField(max_length=255, label="Name", required=True)
|
||||
acronym = forms.CharField(max_length=10, label="Acronym", required=True)
|
||||
state = forms.ModelChoiceField(GroupStateName.objects.all(), label="State", required=True)
|
||||
|
@ -35,18 +35,22 @@ class WGForm(forms.Form):
|
|||
techadv = EmailsField(label="Technical Advisors", required=False)
|
||||
delegates = EmailsField(label="Delegates", required=False, help_text=mark_safe("Type in name to search for person<br>Chairs can delegate the authority to update the state of group documents - max %s persons at a given time" % MAX_GROUP_DELEGATES))
|
||||
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active").order_by('name'), label="Shepherding AD", empty_label="(None)", required=False)
|
||||
parent = forms.ModelChoiceField(Group.objects.filter(type="area", state="active").order_by('name'), label="IETF Area", empty_label="(None)", required=False)
|
||||
parent = forms.ModelChoiceField(Group.objects.filter(state="active").order_by('name'), empty_label="(None)", required=False)
|
||||
list_email = forms.CharField(max_length=64, required=False)
|
||||
list_subscribe = forms.CharField(max_length=255, required=False)
|
||||
list_archive = forms.CharField(max_length=255, required=False)
|
||||
urls = forms.CharField(widget=forms.Textarea, label="Additional URLs", help_text="Format: http://site/path (Optional description). Separate multiple entries with newline.", required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.wg = kwargs.pop('wg', None)
|
||||
self.group = kwargs.pop('group', None)
|
||||
self.confirmed = kwargs.pop('confirmed', False)
|
||||
self.group_type = kwargs.pop('group_type', False)
|
||||
|
||||
super(self.__class__, self).__init__(*args, **kwargs)
|
||||
|
||||
if self.group_type == "rg":
|
||||
self.fields["state"].queryset = self.fields["state"].queryset.exclude(slug__in=("bof", "bof-conc"))
|
||||
|
||||
# if previous AD is now ex-AD, append that person to the list
|
||||
ad_pk = self.initial.get('ad')
|
||||
choices = self.fields['ad'].choices
|
||||
|
@ -55,50 +59,58 @@ class WGForm(forms.Form):
|
|||
|
||||
self.confirm_msg = ""
|
||||
self.autoenable_confirm = False
|
||||
if self.wg:
|
||||
if self.group:
|
||||
self.fields['acronym'].widget.attrs['readonly'] = True
|
||||
|
||||
if self.group_type == "rg":
|
||||
self.fields['ad'].widget = forms.HiddenInput()
|
||||
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(acronym="irtf")
|
||||
self.fields['parent'].widget = forms.HiddenInput()
|
||||
else:
|
||||
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(type="area")
|
||||
self.fields['parent'].label = "IETF Area"
|
||||
|
||||
def clean_acronym(self):
|
||||
self.confirm_msg = ""
|
||||
self.autoenable_confirm = False
|
||||
|
||||
# Changing the acronym of an already existing WG will cause 404s all
|
||||
# Changing the acronym of an already existing group will cause 404s all
|
||||
# over the place, loose history, and generally muck up a lot of
|
||||
# things, so we don't permit it
|
||||
if self.wg:
|
||||
return self.wg.acronym # no change permitted
|
||||
if self.group:
|
||||
return self.group.acronym # no change permitted
|
||||
|
||||
acronym = self.cleaned_data['acronym'].strip().lower()
|
||||
|
||||
# be careful with acronyms, requiring confirmation to take existing or override historic
|
||||
if not re.match(r'^[a-z][a-z0-9]+$', acronym):
|
||||
raise forms.ValidationError("Acronym is invalid, must be at least two characters and only contain lowercase letters and numbers starting with a letter.")
|
||||
|
||||
# be careful with acronyms, requiring confirmation to take existing or override historic
|
||||
existing = Group.objects.filter(acronym__iexact=acronym)
|
||||
if existing:
|
||||
existing = existing[0]
|
||||
|
||||
if existing and existing.type_id == "wg":
|
||||
if existing and existing.type_id == self.group_type:
|
||||
if self.confirmed:
|
||||
return acronym # take over confirmed
|
||||
|
||||
if existing.state_id == "bof":
|
||||
self.confirm_msg = "Turn BoF %s into proposed WG and start chartering it" % existing.acronym
|
||||
self.confirm_msg = "Turn BoF %s into proposed %s and start chartering it" % (existing.acronym, existing.type.name)
|
||||
self.autoenable_confirm = True
|
||||
raise forms.ValidationError("Warning: Acronym used for an existing BoF (%s)." % existing.name)
|
||||
else:
|
||||
self.confirm_msg = "Set state of %s WG to proposed and start chartering it" % existing.acronym
|
||||
self.confirm_msg = "Set state of %s %s to proposed and start chartering it" % (existing.acronym, existing.type.name)
|
||||
self.autoenable_confirm = False
|
||||
raise forms.ValidationError("Warning: Acronym used for an existing WG (%s, %s)." % (existing.name, existing.state.name if existing.state else "unknown state"))
|
||||
raise forms.ValidationError("Warning: Acronym used for an existing %s (%s, %s)." % (existing.type.name, existing.name, existing.state.name if existing.state else "unknown state"))
|
||||
|
||||
if existing:
|
||||
raise forms.ValidationError("Acronym used for an existing group (%s)." % existing.name)
|
||||
|
||||
old = GroupHistory.objects.filter(acronym__iexact=acronym, type="wg")
|
||||
old = GroupHistory.objects.filter(acronym__iexact=acronym, type__in=("wg", "rg"))
|
||||
if old and not self.confirmed:
|
||||
self.confirm_msg = "Confirm reusing acronym %s" % old[0].acronym
|
||||
self.autoenable_confirm = False
|
||||
raise forms.ValidationError("Warning: Acronym used for a historic WG.")
|
||||
raise forms.ValidationError("Warning: Acronym used for a historic group.")
|
||||
|
||||
return acronym
|
||||
|
||||
|
@ -121,98 +133,107 @@ def format_urls(urls, fs="\n"):
|
|||
res.append(u.url)
|
||||
return fs.join(res)
|
||||
|
||||
def get_or_create_initial_charter(wg):
|
||||
def get_or_create_initial_charter(group, group_type):
|
||||
if group_type == "rg":
|
||||
top_org = "irtf"
|
||||
else:
|
||||
top_org = "ietf"
|
||||
|
||||
charter_name = "charter-%s-%s" % (top_org, group.acronym)
|
||||
|
||||
try:
|
||||
charter = Document.objects.get(docalias__name="charter-ietf-%s" % wg.acronym)
|
||||
charter = Document.objects.get(docalias__name=charter_name)
|
||||
except Document.DoesNotExist:
|
||||
charter = Document(
|
||||
name="charter-ietf-" + wg.acronym,
|
||||
name=charter_name,
|
||||
type_id="charter",
|
||||
title=wg.name,
|
||||
group=wg,
|
||||
abstract=wg.name,
|
||||
title=group.name,
|
||||
group=group,
|
||||
abstract=group.name,
|
||||
rev="00-00",
|
||||
)
|
||||
charter.save()
|
||||
charter.set_state(State.objects.get(used=True, type="charter", slug="notrev"))
|
||||
|
||||
# Create an alias as well
|
||||
DocAlias.objects.create(
|
||||
name=charter.name,
|
||||
document=charter
|
||||
)
|
||||
# Create an alias as well
|
||||
DocAlias.objects.create(name=charter.name, document=charter)
|
||||
|
||||
return charter
|
||||
|
||||
@role_required('Area Director', 'Secretariat')
|
||||
def submit_initial_charter(request, acronym=None):
|
||||
wg = get_object_or_404(Group, acronym=acronym)
|
||||
if not wg.charter:
|
||||
wg.charter = get_or_create_initial_charter(wg)
|
||||
wg.save()
|
||||
return redirect('charter_submit', name=wg.charter.name, option="initcharter")
|
||||
|
||||
@role_required('Area Director', 'Secretariat')
|
||||
def edit(request, acronym=None, action="edit"):
|
||||
"""Edit or create a WG, notifying parties as
|
||||
@login_required
|
||||
def submit_initial_charter(request, group_type, acronym=None):
|
||||
if not can_manage_group_type(request.user, group_type):
|
||||
return HttpResponseForbidden("You don't have permission to access this view")
|
||||
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
if not group.charter:
|
||||
group.charter = get_or_create_initial_charter(group, group_type)
|
||||
group.save()
|
||||
|
||||
return redirect('charter_submit', name=group.charter.name, option="initcharter")
|
||||
|
||||
@login_required
|
||||
def edit(request, group_type=None, acronym=None, action="edit"):
|
||||
"""Edit or create a group, notifying parties as
|
||||
necessary and logging changes as group events."""
|
||||
if not can_manage_group_type(request.user, group_type):
|
||||
return HttpResponseForbidden("You don't have permission to access this view")
|
||||
|
||||
if action == "edit":
|
||||
wg = get_object_or_404(Group, acronym=acronym)
|
||||
new_wg = False
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
new_group = False
|
||||
elif action in ("create","charter"):
|
||||
wg = None
|
||||
new_wg = True
|
||||
group = None
|
||||
new_group = True
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
login = request.user.person
|
||||
|
||||
if request.method == 'POST':
|
||||
form = WGForm(request.POST, wg=wg, confirmed=request.POST.get("confirmed", False))
|
||||
form = GroupForm(request.POST, group=group, confirmed=request.POST.get("confirmed", False), group_type=group_type)
|
||||
if form.is_valid():
|
||||
clean = form.cleaned_data
|
||||
if new_wg:
|
||||
if new_group:
|
||||
try:
|
||||
wg = Group.objects.get(acronym=clean["acronym"])
|
||||
save_group_in_history(wg)
|
||||
wg.time = datetime.datetime.now()
|
||||
wg.save()
|
||||
group = Group.objects.get(acronym=clean["acronym"])
|
||||
save_group_in_history(group)
|
||||
group.time = datetime.datetime.now()
|
||||
group.save()
|
||||
except Group.DoesNotExist:
|
||||
wg = Group.objects.create(name=clean["name"],
|
||||
group = Group.objects.create(name=clean["name"],
|
||||
acronym=clean["acronym"],
|
||||
type=GroupTypeName.objects.get(slug="wg"),
|
||||
type=GroupTypeName.objects.get(slug=group_type),
|
||||
state=clean["state"]
|
||||
)
|
||||
|
||||
e = ChangeStateGroupEvent(group=wg, type="changed_state")
|
||||
e.time = wg.time
|
||||
e.by = login
|
||||
e = ChangeStateGroupEvent(group=group, type="changed_state")
|
||||
e.time = group.time
|
||||
e.by = request.user.person
|
||||
e.state_id = clean["state"].slug
|
||||
e.desc = "Group created in state %s" % clean["state"].name
|
||||
e.save()
|
||||
else:
|
||||
save_group_in_history(wg)
|
||||
save_group_in_history(group)
|
||||
|
||||
|
||||
if action=="charter" and not wg.charter: # make sure we have a charter
|
||||
wg.charter = get_or_create_initial_charter(wg)
|
||||
if action == "charter" and not group.charter: # make sure we have a charter
|
||||
group.charter = get_or_create_initial_charter(group, group_type)
|
||||
|
||||
changes = []
|
||||
|
||||
def desc(attr, new, old):
|
||||
entry = "%(attr)s changed to <b>%(new)s</b> from %(old)s"
|
||||
if new_wg:
|
||||
if new_group:
|
||||
entry = "%(attr)s changed to <b>%(new)s</b>"
|
||||
|
||||
return entry % dict(attr=attr, new=new, old=old)
|
||||
|
||||
def diff(attr, name):
|
||||
v = getattr(wg, attr)
|
||||
v = getattr(group, attr)
|
||||
if clean[attr] != v:
|
||||
changes.append(desc(name, clean[attr], v))
|
||||
setattr(wg, attr, clean[attr])
|
||||
setattr(group, attr, clean[attr])
|
||||
|
||||
prev_acronym = wg.acronym
|
||||
prev_acronym = group.acronym
|
||||
|
||||
# update the attributes, keeping track of what we're doing
|
||||
diff('name', "Name")
|
||||
|
@ -224,127 +245,130 @@ def edit(request, acronym=None, action="edit"):
|
|||
diff('list_subscribe', "Mailing list subscribe address")
|
||||
diff('list_archive', "Mailing list archive")
|
||||
|
||||
if not new_wg and wg.acronym != prev_acronym and wg.charter:
|
||||
save_document_in_history(wg.charter)
|
||||
if not new_group and group.acronym != prev_acronym and group.charter:
|
||||
save_document_in_history(group.charter)
|
||||
DocAlias.objects.get_or_create(
|
||||
name="charter-ietf-%s" % wg.acronym,
|
||||
document=wg.charter,
|
||||
name="charter-ietf-%s" % group.acronym,
|
||||
document=group.charter,
|
||||
)
|
||||
old = os.path.join(wg.charter.get_file_path(), 'charter-ietf-%s-%s.txt' % (prev_acronym, wg.charter.rev))
|
||||
old = os.path.join(group.charter.get_file_path(), 'charter-ietf-%s-%s.txt' % (prev_acronym, group.charter.rev))
|
||||
if os.path.exists(old):
|
||||
new = os.path.join(wg.charter.get_file_path(), 'charter-ietf-%s-%s.txt' % (wg.acronym, wg.charter.rev))
|
||||
new = os.path.join(group.charter.get_file_path(), 'charter-ietf-%s-%s.txt' % (group.acronym, group.charter.rev))
|
||||
shutil.copy(old, new)
|
||||
|
||||
# update roles
|
||||
for attr, slug, title in [('chairs', 'chair', "Chairs"), ('secretaries', 'secr', "Secretaries"), ('techadv', 'techadv', "Tech Advisors"), ('delegates', 'delegate', "Delegates")]:
|
||||
new = clean[attr]
|
||||
old = Email.objects.filter(role__group=wg, role__name=slug).select_related("person")
|
||||
old = Email.objects.filter(role__group=group, role__name=slug).select_related("person")
|
||||
if set(new) != set(old):
|
||||
changes.append(desc(title,
|
||||
", ".join(x.get_name() for x in new),
|
||||
", ".join(x.get_name() for x in old)))
|
||||
wg.role_set.filter(name=slug).delete()
|
||||
group.role_set.filter(name=slug).delete()
|
||||
for e in new:
|
||||
Role.objects.get_or_create(name_id=slug, email=e, group=wg, person=e.person)
|
||||
Role.objects.get_or_create(name_id=slug, email=e, group=group, person=e.person)
|
||||
|
||||
# update urls
|
||||
new_urls = clean['urls']
|
||||
old_urls = format_urls(wg.groupurl_set.order_by('url'), ", ")
|
||||
old_urls = format_urls(group.groupurl_set.order_by('url'), ", ")
|
||||
if ", ".join(sorted(new_urls)) != old_urls:
|
||||
changes.append(desc('Urls', ", ".join(sorted(new_urls)), old_urls))
|
||||
wg.groupurl_set.all().delete()
|
||||
group.groupurl_set.all().delete()
|
||||
# Add new ones
|
||||
for u in new_urls:
|
||||
m = re.search('(?P<url>[\w\d:#@%/;$()~_?\+-=\\\.&]+)( \((?P<name>.+)\))?', u)
|
||||
if m:
|
||||
if m.group('name'):
|
||||
url = GroupURL(url=m.group('url'), name=m.group('name'), group=wg)
|
||||
url = GroupURL(url=m.group('url'), name=m.group('name'), group=group)
|
||||
else:
|
||||
url = GroupURL(url=m.group('url'), name='', group=wg)
|
||||
url = GroupURL(url=m.group('url'), name='', group=group)
|
||||
url.save()
|
||||
|
||||
wg.time = datetime.datetime.now()
|
||||
group.time = datetime.datetime.now()
|
||||
|
||||
if changes and not new_wg:
|
||||
if changes and not new_group:
|
||||
for c in changes:
|
||||
GroupEvent.objects.create(group=wg, by=login, type="info_changed", desc=c)
|
||||
GroupEvent.objects.create(group=group, by=request.user.person, type="info_changed", desc=c)
|
||||
|
||||
wg.save()
|
||||
group.save()
|
||||
|
||||
if action=="charter":
|
||||
return redirect('charter_submit', name=wg.charter.name, option="initcharter")
|
||||
return redirect('charter_submit', name=group.charter.name, option="initcharter")
|
||||
|
||||
return redirect('group_charter', acronym=wg.acronym)
|
||||
return redirect('group_charter', group_type=group.type_id, acronym=group.acronym)
|
||||
else: # form.is_valid()
|
||||
if not new_wg:
|
||||
init = dict(name=wg.name,
|
||||
acronym=wg.acronym,
|
||||
state=wg.state,
|
||||
chairs=Email.objects.filter(role__group=wg, role__name="chair"),
|
||||
secretaries=Email.objects.filter(role__group=wg, role__name="secr"),
|
||||
techadv=Email.objects.filter(role__group=wg, role__name="techadv"),
|
||||
delegates=Email.objects.filter(role__group=wg, role__name="delegate"),
|
||||
ad=wg.ad_id if wg.ad else None,
|
||||
parent=wg.parent.id if wg.parent else None,
|
||||
list_email=wg.list_email if wg.list_email else None,
|
||||
list_subscribe=wg.list_subscribe if wg.list_subscribe else None,
|
||||
list_archive=wg.list_archive if wg.list_archive else None,
|
||||
urls=format_urls(wg.groupurl_set.all()),
|
||||
if not new_group:
|
||||
init = dict(name=group.name,
|
||||
acronym=group.acronym,
|
||||
state=group.state,
|
||||
chairs=Email.objects.filter(role__group=group, role__name="chair"),
|
||||
secretaries=Email.objects.filter(role__group=group, role__name="secr"),
|
||||
techadv=Email.objects.filter(role__group=group, role__name="techadv"),
|
||||
delegates=Email.objects.filter(role__group=group, role__name="delegate"),
|
||||
ad=group.ad_id if group.ad else None,
|
||||
parent=group.parent.id if group.parent else None,
|
||||
list_email=group.list_email if group.list_email else None,
|
||||
list_subscribe=group.list_subscribe if group.list_subscribe else None,
|
||||
list_archive=group.list_archive if group.list_archive else None,
|
||||
urls=format_urls(group.groupurl_set.all()),
|
||||
)
|
||||
else:
|
||||
init = dict(ad=login.id if has_role(request.user, "Area Director") else None,
|
||||
init = dict(ad=request.user.person.id if group_type == "wg" and has_role(request.user, "Area Director") else None,
|
||||
)
|
||||
form = WGForm(initial=init, wg=wg)
|
||||
form = GroupForm(initial=init, group=group, group_type=group_type)
|
||||
|
||||
return render_to_response('wginfo/edit.html',
|
||||
dict(wg=wg,
|
||||
form=form,
|
||||
action=action,
|
||||
user=request.user,
|
||||
login=login),
|
||||
context_instance=RequestContext(request))
|
||||
return render(request, 'group/edit.html',
|
||||
dict(group=group,
|
||||
form=form,
|
||||
action=action))
|
||||
|
||||
|
||||
|
||||
class ConcludeForm(forms.Form):
|
||||
instructions = forms.CharField(widget=forms.Textarea(attrs={'rows': 30}), required=True)
|
||||
|
||||
@role_required('Area Director','Secretariat')
|
||||
def conclude(request, acronym):
|
||||
"""Request the closing of a WG, prompting for instructions."""
|
||||
wg = get_object_or_404(Group, acronym=acronym)
|
||||
@login_required
|
||||
def conclude(request, group_type, acronym):
|
||||
"""Request the closing of group, prompting for instructions."""
|
||||
group = get_object_or_404(Group, type=group_type, acronym=acronym)
|
||||
|
||||
login = request.user.person
|
||||
if not can_manage_group_type(request.user, group_type):
|
||||
return HttpResponseForbidden("You don't have permission to access this view")
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ConcludeForm(request.POST)
|
||||
if form.is_valid():
|
||||
instructions = form.cleaned_data['instructions']
|
||||
|
||||
email_secretariat(request, wg, "Request closing of group", instructions)
|
||||
email_secretariat(request, group, "Request closing of group", instructions)
|
||||
|
||||
e = GroupEvent(group=wg, by=login)
|
||||
e = GroupEvent(group=group, by=request.user.person)
|
||||
e.type = "requested_close"
|
||||
e.desc = "Requested closing group"
|
||||
e.save()
|
||||
|
||||
return redirect('group_charter', acronym=wg.acronym)
|
||||
return redirect('group_charter', group_type=group.type_id, acronym=group.acronym)
|
||||
else:
|
||||
form = ConcludeForm()
|
||||
|
||||
return render_to_response('wginfo/conclude.html',
|
||||
dict(form=form,
|
||||
wg=wg),
|
||||
context_instance=RequestContext(request))
|
||||
return render(request, 'group/conclude.html',
|
||||
dict(form=form, group=group))
|
||||
|
||||
|
||||
def customize_workflow(request, acronym):
|
||||
MANDATORY_STATES = ('c-adopt', 'wg-doc', 'sub-pub')
|
||||
|
||||
group = get_object_or_404(Group, acronym=acronym, type="wg")
|
||||
if not request.user.is_authenticated() or not (has_role(request.user, "Secretariat") or group.role_set.filter(name="chair", person__user=request.user)):
|
||||
@login_required
|
||||
def customize_workflow(request, group_type, acronym):
|
||||
group = get_object_or_404(Group, type=group_type, acronym=acronym)
|
||||
if (not has_role(request.user, "Secretariat") and
|
||||
not group.role_set.filter(name="chair", person__user=request.user)):
|
||||
return HttpResponseForbidden("You don't have permission to access this view")
|
||||
|
||||
if group_type == "rg":
|
||||
stream_id = "irtf"
|
||||
MANDATORY_STATES = ('candidat', 'active', 'rfc-edit', 'pub', 'dead')
|
||||
else:
|
||||
stream_id = "ietf"
|
||||
MANDATORY_STATES = ('c-adopt', 'wg-doc', 'sub-pub')
|
||||
|
||||
if request.method == 'POST':
|
||||
action = request.POST.get("action")
|
||||
if action == "setstateactive":
|
||||
|
@ -361,7 +385,7 @@ def customize_workflow(request, acronym):
|
|||
|
||||
# redirect so the back button works correctly, otherwise
|
||||
# repeated POSTs fills up the history
|
||||
return redirect("ietf.wginfo.edit.customize_workflow", acronym=group.acronym)
|
||||
return redirect("ietf.group.edit.customize_workflow", group_type=group.type_id, acronym=group.acronym)
|
||||
|
||||
if action == "setnextstates":
|
||||
try:
|
||||
|
@ -369,7 +393,7 @@ def customize_workflow(request, acronym):
|
|||
except State.DoesNotExist:
|
||||
return HttpResponse("Invalid state %s" % request.POST.get("state"))
|
||||
|
||||
next_states = State.objects.filter(used=True, type='draft-stream-ietf', pk__in=request.POST.getlist("next_states"))
|
||||
next_states = State.objects.filter(used=True, type='draft-stream-%s' % stream_id, pk__in=request.POST.getlist("next_states"))
|
||||
unused = group.unused_states.all()
|
||||
if set(next_states.exclude(pk__in=unused)) == set(state.next_states.exclude(pk__in=unused)):
|
||||
# just use the default
|
||||
|
@ -378,7 +402,7 @@ def customize_workflow(request, acronym):
|
|||
transitions, _ = GroupStateTransitions.objects.get_or_create(group=group, state=state)
|
||||
transitions.next_states = next_states
|
||||
|
||||
return redirect("ietf.wginfo.edit.customize_workflow", acronym=group.acronym)
|
||||
return redirect("ietf.group.edit.customize_workflow", group_type=group.type_id, acronym=group.acronym)
|
||||
|
||||
if action == "settagactive":
|
||||
active = request.POST.get("active") == "1"
|
||||
|
@ -392,17 +416,16 @@ def customize_workflow(request, acronym):
|
|||
else:
|
||||
group.unused_tags.add(tag)
|
||||
|
||||
return redirect("ietf.wginfo.edit.customize_workflow", acronym=group.acronym)
|
||||
|
||||
return redirect("ietf.group.edit.customize_workflow", group_type=group.type_id, acronym=group.acronym)
|
||||
|
||||
# put some info for the template on tags and states
|
||||
unused_tags = group.unused_tags.all().values_list('slug', flat=True)
|
||||
tags = DocTagName.objects.filter(slug__in=get_tags_for_stream_id("ietf"))
|
||||
tags = DocTagName.objects.filter(slug__in=get_tags_for_stream_id(stream_id))
|
||||
for t in tags:
|
||||
t.used = t.slug not in unused_tags
|
||||
|
||||
unused_states = group.unused_states.all().values_list('slug', flat=True)
|
||||
states = State.objects.filter(used=True, type="draft-stream-ietf")
|
||||
states = State.objects.filter(used=True, type="draft-stream-%s" % stream_id)
|
||||
transitions = dict((o.state, o) for o in group.groupstatetransitions_set.all())
|
||||
for s in states:
|
||||
s.used = s.slug not in unused_states
|
||||
|
@ -417,8 +440,8 @@ def customize_workflow(request, acronym):
|
|||
s.next_states_checkboxes = [(x in n, x in default_n, x) for x in states]
|
||||
s.used_next_states = [x for x in n if x.slug not in unused_states]
|
||||
|
||||
return render_to_response('wginfo/customize_workflow.html', {
|
||||
return render(request, 'group/customize_workflow.html', {
|
||||
'group': group,
|
||||
'states': states,
|
||||
'tags': tags,
|
||||
}, RequestContext(request))
|
||||
})
|
|
@ -11,7 +11,7 @@ from ietf.doc.models import DocEvent
|
|||
|
||||
class GroupChangesFeed(Feed):
|
||||
feed_type = Atom1Feed
|
||||
description_template = "wginfo/feed_item_description.html"
|
||||
description_template = "group/feed_item_description.html"
|
||||
|
||||
def get_object(self, request, acronym):
|
||||
return Group.objects.get(acronym=acronym)
|
||||
|
@ -22,7 +22,7 @@ class GroupChangesFeed(Feed):
|
|||
def link(self, obj):
|
||||
if not obj:
|
||||
raise FeedDoesNotExist
|
||||
return urlreverse('group_charter', kwargs={'acronym': obj.acronym})
|
||||
return urlreverse('group_charter', kwargs=dict(group_type=obj.type_id, acronym=obj.acronym))
|
||||
|
||||
def description(self, obj):
|
||||
return self.title(obj)
|
||||
|
@ -40,7 +40,7 @@ class GroupChangesFeed(Feed):
|
|||
if isinstance(obj, DocEvent):
|
||||
return urlreverse("doc_view", kwargs={'name': obj.doc_id })
|
||||
elif isinstance(obj, GroupEvent):
|
||||
return urlreverse('group_charter', kwargs={'acronym': obj.group.acronym })
|
||||
return urlreverse('group_charter', kwargs=dict(group_type=obj.group.type_id, acronym=obj.group.acronym))
|
||||
|
||||
def item_pubdate(self, obj):
|
||||
return obj.time
|
|
@ -36,22 +36,21 @@ import os
|
|||
import itertools
|
||||
from tempfile import mkstemp
|
||||
|
||||
from django.shortcuts import get_object_or_404, render_to_response
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.template.loader import render_to_string
|
||||
from django.template import RequestContext
|
||||
from django.http import HttpResponse
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.db.models import Q
|
||||
|
||||
from ietf.doc.views_search import SearchForm, retrieve_search_results
|
||||
from ietf.group.models import Group, Role
|
||||
from ietf.doc.models import State, DocAlias, RelatedDocument
|
||||
from ietf.doc.utils import get_chartering_type
|
||||
from ietf.group.utils import get_charter_text
|
||||
from ietf.doc.templatetags.ietf_filters import clean_whitespace
|
||||
from ietf.ietfauth.utils import has_role
|
||||
from ietf.group.models import Group, Role, ChangeStateGroupEvent
|
||||
from ietf.name.models import GroupTypeName
|
||||
from ietf.group.utils import get_charter_text, can_manage_group_type, milestone_reviewer_for_group_type
|
||||
from ietf.utils.pipe import pipe
|
||||
|
||||
def roles(group, role_name):
|
||||
|
@ -86,7 +85,9 @@ def fill_in_charter_info(group, include_drafts=False):
|
|||
def extract_last_name(role):
|
||||
return role.person.name_parts()[3]
|
||||
|
||||
def wg_summary_area(request):
|
||||
def wg_summary_area(request, group_type):
|
||||
if group_type != "wg":
|
||||
raise Http404
|
||||
areas = Group.objects.filter(type="area", state="active").order_by("name")
|
||||
for area in areas:
|
||||
area.ads = sorted(roles(area, "ad"), key=extract_last_name)
|
||||
|
@ -96,21 +97,25 @@ def wg_summary_area(request):
|
|||
|
||||
areas = [a for a in areas if a.groups]
|
||||
|
||||
return render_to_response('wginfo/1wg-summary.txt',
|
||||
{ 'areas': areas },
|
||||
content_type='text/plain; charset=UTF-8')
|
||||
return render(request, 'group/1wg-summary.txt',
|
||||
{ 'areas': areas },
|
||||
content_type='text/plain; charset=UTF-8')
|
||||
|
||||
def wg_summary_acronym(request):
|
||||
def wg_summary_acronym(request, group_type):
|
||||
if group_type != "wg":
|
||||
raise Http404
|
||||
areas = Group.objects.filter(type="area", state="active").order_by("name")
|
||||
groups = Group.objects.filter(type="wg", state="active").order_by("acronym").select_related("parent")
|
||||
for group in groups:
|
||||
group.chairs = sorted(roles(group, "chair"), key=extract_last_name)
|
||||
return render_to_response('wginfo/1wg-summary-by-acronym.txt',
|
||||
{ 'areas': areas,
|
||||
'groups': groups },
|
||||
content_type='text/plain; charset=UTF-8')
|
||||
return render(request, 'group/1wg-summary-by-acronym.txt',
|
||||
{ 'areas': areas,
|
||||
'groups': groups },
|
||||
content_type='text/plain; charset=UTF-8')
|
||||
|
||||
def wg_charters(request):
|
||||
def wg_charters(request, group_type):
|
||||
if group_type != "wg":
|
||||
raise Http404
|
||||
areas = Group.objects.filter(type="area", state="active").order_by("name")
|
||||
for area in areas:
|
||||
area.ads = sorted(roles(area, "ad"), key=extract_last_name)
|
||||
|
@ -118,11 +123,13 @@ def wg_charters(request):
|
|||
for group in area.groups:
|
||||
fill_in_charter_info(group, include_drafts=True)
|
||||
group.area = area
|
||||
return render_to_response('wginfo/1wg-charters.txt',
|
||||
{ 'areas': areas },
|
||||
content_type='text/plain; charset=UTF-8')
|
||||
return render(request, 'group/1wg-charters.txt',
|
||||
{ 'areas': areas },
|
||||
content_type='text/plain; charset=UTF-8')
|
||||
|
||||
def wg_charters_by_acronym(request):
|
||||
def wg_charters_by_acronym(request, group_type):
|
||||
if group_type != "wg":
|
||||
raise Http404
|
||||
areas = dict((a.id, a) for a in Group.objects.filter(type="area", state="active").order_by("name"))
|
||||
|
||||
for area in areas.itervalues():
|
||||
|
@ -132,9 +139,17 @@ def wg_charters_by_acronym(request):
|
|||
for group in groups:
|
||||
fill_in_charter_info(group, include_drafts=True)
|
||||
group.area = areas.get(group.parent_id)
|
||||
return render_to_response('wginfo/1wg-charters-by-acronym.txt',
|
||||
{ 'groups': groups },
|
||||
content_type='text/plain; charset=UTF-8')
|
||||
return render(request, 'group/1wg-charters-by-acronym.txt',
|
||||
{ 'groups': groups },
|
||||
content_type='text/plain; charset=UTF-8')
|
||||
|
||||
def active_groups(request, group_type):
|
||||
if group_type == "wg":
|
||||
return active_wgs(request)
|
||||
elif group_type == "rg":
|
||||
return active_rgs(request)
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
def active_wgs(request):
|
||||
areas = Group.objects.filter(type="area", state="active").order_by("name")
|
||||
|
@ -148,43 +163,77 @@ def active_wgs(request):
|
|||
for group in area.groups:
|
||||
group.chairs = sorted(roles(group, "chair"), key=extract_last_name)
|
||||
|
||||
return render_to_response('wginfo/active_wgs.html', {'areas':areas}, RequestContext(request))
|
||||
return render(request, 'group/active_wgs.html', { 'areas':areas })
|
||||
|
||||
def bofs(request):
|
||||
groups = Group.objects.filter(type="wg", state="bof")
|
||||
return render_to_response('wginfo/bofs.html',dict(groups=groups), RequestContext(request))
|
||||
def active_rgs(request):
|
||||
irtf = Group.objects.get(acronym="irtf")
|
||||
irtf.chair = roles(irtf, "chair").first()
|
||||
|
||||
def chartering_wgs(request):
|
||||
groups = Group.objects.filter(type="rg", state="active").order_by("acronym")
|
||||
for group in groups:
|
||||
group.chairs = sorted(roles(group, "chair"), key=extract_last_name)
|
||||
|
||||
return render(request, 'group/active_rgs.html', { 'irtf': irtf, 'groups': groups })
|
||||
|
||||
def bofs(request, group_type):
|
||||
groups = Group.objects.filter(type=group_type, state="bof")
|
||||
return render(request, 'group/bofs.html',dict(groups=groups))
|
||||
|
||||
def chartering_groups(request):
|
||||
charter_states = State.objects.filter(used=True, type="charter").exclude(slug__in=("approved", "notrev"))
|
||||
groups = Group.objects.filter(type="wg", charter__states__in=charter_states).select_related("state", "charter")
|
||||
|
||||
for g in groups:
|
||||
g.chartering_type = get_chartering_type(g.charter)
|
||||
group_types = GroupTypeName.objects.filter(slug__in=("wg", "rg"))
|
||||
|
||||
return render_to_response('wginfo/chartering_wgs.html',
|
||||
dict(charter_states=charter_states,
|
||||
groups=groups),
|
||||
RequestContext(request))
|
||||
for t in group_types:
|
||||
t.chartering_groups = Group.objects.filter(type=t, charter__states__in=charter_states).select_related("state", "charter").order_by("acronym")
|
||||
t.can_manage = can_manage_group_type(request.user, t.slug)
|
||||
|
||||
for g in t.chartering_groups:
|
||||
g.chartering_type = get_chartering_type(g.charter)
|
||||
|
||||
return render(request, 'group/chartering_groups.html',
|
||||
dict(charter_states=charter_states,
|
||||
group_types=group_types))
|
||||
|
||||
def concluded_groups(request):
|
||||
group_types = GroupTypeName.objects.filter(slug__in=("wg", "rg"))
|
||||
|
||||
for t in group_types:
|
||||
t.concluded_groups = Group.objects.filter(type=t, state__in=("conclude", "bof-conc")).select_related("state", "charter").order_by("acronym")
|
||||
|
||||
# add start/conclusion date
|
||||
d = dict((g.pk, g) for g in t.concluded_groups)
|
||||
|
||||
for g in t.concluded_groups:
|
||||
g.start_date = g.conclude_date = None
|
||||
|
||||
for e in ChangeStateGroupEvent.objects.filter(group__in=t.concluded_groups, state="active").order_by("-time"):
|
||||
d[e.group_id].start_date = e.time
|
||||
|
||||
for e in ChangeStateGroupEvent.objects.filter(group__in=t.concluded_groups, state="conclude").order_by("time"):
|
||||
d[e.group_id].conclude_date = e.time
|
||||
|
||||
return render(request, 'group/concluded_groups.html',
|
||||
dict(group_types=group_types))
|
||||
|
||||
def construct_group_menu_context(request, group, selected, others):
|
||||
"""Return context with info for the group menu filled in."""
|
||||
actions = []
|
||||
|
||||
is_chair = group.has_role(request.user, "chair")
|
||||
is_ad_or_secretariat = has_role(request.user, ("Area Director", "Secretariat"))
|
||||
can_manage = can_manage_group_type(request.user, group.type_id)
|
||||
|
||||
if group.state_id != "proposed" and (is_chair or is_ad_or_secretariat):
|
||||
actions.append((u"Add or edit milestones", urlreverse("wg_edit_milestones", kwargs=dict(acronym=group.acronym))))
|
||||
if group.state_id != "proposed" and (is_chair or can_manage):
|
||||
actions.append((u"Add or edit milestones", urlreverse("group_edit_milestones", kwargs=dict(group_type=group.type_id, acronym=group.acronym))))
|
||||
|
||||
if group.state_id != "conclude" and is_ad_or_secretariat:
|
||||
actions.append((u"Edit group", urlreverse("group_edit", kwargs=dict(acronym=group.acronym))))
|
||||
if group.state_id != "conclude" and can_manage:
|
||||
actions.append((u"Edit group", urlreverse("group_edit", kwargs=dict(group_type=group.type_id, acronym=group.acronym))))
|
||||
|
||||
if is_chair or is_ad_or_secretariat:
|
||||
actions.append((u"Customize workflow", urlreverse("ietf.wginfo.edit.customize_workflow", kwargs=dict(acronym=group.acronym))))
|
||||
if is_chair or can_manage:
|
||||
actions.append((u"Customize workflow", urlreverse("ietf.group.edit.customize_workflow", kwargs=dict(group_type=group.type_id, acronym=group.acronym))))
|
||||
|
||||
if group.state_id in ("active", "dormant") and is_ad_or_secretariat:
|
||||
actions.append((u"Request closing group", urlreverse("wg_conclude", kwargs=dict(acronym=group.acronym))))
|
||||
if group.state_id in ("active", "dormant") and can_manage:
|
||||
actions.append((u"Request closing group", urlreverse("ietf.group.edit.conclude", kwargs=dict(group_type=group.type_id, acronym=group.acronym))))
|
||||
|
||||
d = {
|
||||
"group": group,
|
||||
|
@ -207,8 +256,8 @@ def search_for_group_documents(group):
|
|||
docs_related = []
|
||||
for d in raw_docs_related:
|
||||
parts = d.name.split("-", 2);
|
||||
# canonical form draft-<name|ietf>-wg-etc
|
||||
if len(parts) >= 3 and parts[1] != "ietf" and parts[2].startswith(group.acronym + "-"):
|
||||
# canonical form draft-<name|ietf|irtf>-wg-etc
|
||||
if len(parts) >= 3 and parts[1] not in ("ietf", "irtf") and parts[2].startswith(group.acronym + "-"):
|
||||
d.search_heading = "Related Internet-Draft"
|
||||
docs_related.append(d)
|
||||
|
||||
|
@ -229,22 +278,22 @@ def search_for_group_documents(group):
|
|||
|
||||
return docs, meta, docs_related, meta_related
|
||||
|
||||
def group_documents(request, acronym):
|
||||
group = get_object_or_404(Group, type="wg", acronym=acronym)
|
||||
def group_documents(request, group_type, acronym):
|
||||
group = get_object_or_404(Group, type=group_type, acronym=acronym)
|
||||
|
||||
docs, meta, docs_related, meta_related = search_for_group_documents(group)
|
||||
|
||||
return render_to_response('wginfo/group_documents.html',
|
||||
construct_group_menu_context(request, group, "documents", {
|
||||
return render(request, 'group/group_documents.html',
|
||||
construct_group_menu_context(request, group, "documents", {
|
||||
'docs': docs,
|
||||
'meta': meta,
|
||||
'docs_related': docs_related,
|
||||
'meta_related': meta_related
|
||||
}), RequestContext(request))
|
||||
}))
|
||||
|
||||
def group_documents_txt(request, acronym):
|
||||
def group_documents_txt(request, group_type, acronym):
|
||||
"""Return tabulator-separated rows with documents for group."""
|
||||
group = get_object_or_404(Group, type="wg", acronym=acronym)
|
||||
group = get_object_or_404(Group, type=group_type, acronym=acronym)
|
||||
|
||||
docs, meta, docs_related, meta_related = search_for_group_documents(group)
|
||||
|
||||
|
@ -267,8 +316,8 @@ def group_documents_txt(request, acronym):
|
|||
return HttpResponse(u"\n".join(rows), content_type='text/plain; charset=UTF-8')
|
||||
|
||||
|
||||
def group_charter(request, acronym):
|
||||
group = get_object_or_404(Group, type="wg", acronym=acronym)
|
||||
def group_charter(request, group_type, acronym):
|
||||
group = get_object_or_404(Group, type=group_type, acronym=acronym)
|
||||
|
||||
fill_in_charter_info(group, include_drafts=False)
|
||||
group.delegates = roles(group, "delegate")
|
||||
|
@ -276,22 +325,32 @@ def group_charter(request, acronym):
|
|||
e = group.latest_event(type__in=("changed_state", "requested_close",))
|
||||
requested_close = group.state_id != "conclude" and e and e.type == "requested_close"
|
||||
|
||||
return render_to_response('wginfo/group_charter.html',
|
||||
construct_group_menu_context(request, group, "charter", {
|
||||
"milestones_in_review": group.groupmilestone_set.filter(state="review"),
|
||||
"requested_close": requested_close,
|
||||
}), RequestContext(request))
|
||||
long_group_types = dict(
|
||||
wg="Working Group",
|
||||
rg="Research Group",
|
||||
)
|
||||
|
||||
can_manage = can_manage_group_type(request.user, group.type_id)
|
||||
|
||||
return render(request, 'group/group_charter.html',
|
||||
construct_group_menu_context(request, group, "charter", {
|
||||
"milestones_in_review": group.groupmilestone_set.filter(state="review"),
|
||||
"milestone_reviewer": milestone_reviewer_for_group_type(group_type),
|
||||
"requested_close": requested_close,
|
||||
"long_group_type":long_group_types.get(group_type, "Group"),
|
||||
"can_manage": can_manage,
|
||||
}))
|
||||
|
||||
|
||||
def history(request, acronym):
|
||||
def history(request, group_type, acronym):
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
|
||||
events = group.groupevent_set.all().select_related('by').order_by('-time', '-id')
|
||||
|
||||
return render_to_response('wginfo/history.html',
|
||||
construct_group_menu_context(request, group, "history", {
|
||||
return render(request, 'group/history.html',
|
||||
construct_group_menu_context(request, group, "history", {
|
||||
"events": events,
|
||||
}), RequestContext(request))
|
||||
}))
|
||||
|
||||
|
||||
def nodename(name):
|
||||
|
@ -407,11 +466,11 @@ def make_dot(group):
|
|||
node.nodename=nodename(node.name)
|
||||
node.styles = get_node_styles(node,group)
|
||||
|
||||
return render_to_string('wginfo/dot.txt',
|
||||
return render_to_string('group/dot.txt',
|
||||
dict( nodes=nodes, edges=edges )
|
||||
)
|
||||
|
||||
def dependencies_dot(request, acronym):
|
||||
def dependencies_dot(request, group_type, acronym):
|
||||
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
|
||||
|
@ -420,7 +479,7 @@ def dependencies_dot(request, acronym):
|
|||
)
|
||||
|
||||
@cache_page ( 60 * 60 )
|
||||
def dependencies_pdf(request, acronym):
|
||||
def dependencies_pdf(request, group_type, acronym):
|
||||
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
|
|
@ -10,8 +10,8 @@ from django.conf import settings
|
|||
from django.core.urlresolvers import reverse as urlreverse
|
||||
|
||||
from ietf.utils.mail import send_mail, send_mail_text
|
||||
|
||||
from ietf.group.models import Group
|
||||
from ietf.group.utils import milestone_reviewer_for_group_type
|
||||
|
||||
def email_secretariat(request, group, subject, text):
|
||||
to = ["iesg-secretary@ietf.org"]
|
||||
|
@ -19,10 +19,10 @@ def email_secretariat(request, group, subject, text):
|
|||
text = strip_tags(text)
|
||||
|
||||
send_mail(request, to, None, full_subject,
|
||||
"wginfo/email_secretariat.txt",
|
||||
"group/email_secretariat.txt",
|
||||
dict(text=text,
|
||||
group=group,
|
||||
group_url=settings.IDTRACKER_BASE_URL + urlreverse('group_charter', kwargs=dict(acronym=group.acronym)),
|
||||
group_url=settings.IDTRACKER_BASE_URL + urlreverse('group_charter', kwargs=dict(group_type=group.type_id, acronym=group.acronym)),
|
||||
charter_url=settings.IDTRACKER_BASE_URL + urlreverse('doc_view', kwargs=dict(name=group.charter.name)),
|
||||
)
|
||||
)
|
||||
|
@ -31,16 +31,18 @@ def email_milestones_changed(request, group, changes):
|
|||
def wrap_up_email(to, text):
|
||||
text = wrap(strip_tags(text), 70)
|
||||
text += "\n\n"
|
||||
text += u"URL: %s" % (settings.IDTRACKER_BASE_URL + urlreverse("group_charter", kwargs=dict(acronym=group.acronym)))
|
||||
text += u"URL: %s" % (settings.IDTRACKER_BASE_URL + urlreverse("group_charter", kwargs=dict(group_type=group.type_id, acronym=group.acronym)))
|
||||
|
||||
send_mail_text(request, to, None,
|
||||
u"Milestones changed for %s %s" % (group.acronym, group.type.name),
|
||||
text)
|
||||
|
||||
# first send to AD and chairs
|
||||
# first send to management and chairs
|
||||
to = []
|
||||
if group.ad:
|
||||
to.append(group.ad.role_email("ad").formatted_email())
|
||||
elif group.type_id == "rg":
|
||||
to.append("IRTF Chair <irtf-chair@irtf.org>")
|
||||
|
||||
for r in group.role_set.filter(name="chair"):
|
||||
to.append(r.formatted_email())
|
||||
|
@ -48,7 +50,7 @@ def email_milestones_changed(request, group, changes):
|
|||
if to:
|
||||
wrap_up_email(to, u"\n\n".join(c + "." for c in changes))
|
||||
|
||||
# then send to WG
|
||||
# then send to group
|
||||
if group.list_email:
|
||||
review_re = re.compile("Added .* for review, due")
|
||||
to = [ group.list_email ]
|
||||
|
@ -56,11 +58,17 @@ def email_milestones_changed(request, group, changes):
|
|||
|
||||
|
||||
def email_milestone_review_reminder(group, grace_period=7):
|
||||
"""Email reminders about milestones needing review to AD."""
|
||||
if not group.ad:
|
||||
"""Email reminders about milestones needing review to management."""
|
||||
to = []
|
||||
|
||||
if group.ad:
|
||||
to.append(group.ad.role_email("ad").formatted_email())
|
||||
elif group.type_id == "rg":
|
||||
to.append("IRTF Chair <irtf-chair@irtf.org>")
|
||||
|
||||
if not to:
|
||||
return False
|
||||
|
||||
to = [group.ad.role_email("ad").formatted_email()]
|
||||
cc = [r.formatted_email() for r in group.role_set.filter(name="chair")]
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
@ -81,10 +89,11 @@ def email_milestone_review_reminder(group, grace_period=7):
|
|||
|
||||
send_mail(None, to, None,
|
||||
subject,
|
||||
"wginfo/reminder_milestones_need_review.txt",
|
||||
"group/reminder_milestones_need_review.txt",
|
||||
dict(group=group,
|
||||
milestones=milestones,
|
||||
url=settings.IDTRACKER_BASE_URL + urlreverse("wg_edit_milestones", kwargs=dict(acronym=group.acronym)),
|
||||
reviewer=milestone_reviewer_for_group_type(group.type_id),
|
||||
url=settings.IDTRACKER_BASE_URL + urlreverse("group_edit_milestones", kwargs=dict(group_type=group.type_id, acronym=group.acronym)),
|
||||
cc=cc,
|
||||
)
|
||||
)
|
||||
|
@ -107,12 +116,12 @@ def email_milestones_due(group, early_warning_days):
|
|||
|
||||
send_mail(None, to, None,
|
||||
subject,
|
||||
"wginfo/reminder_milestones_due.txt",
|
||||
"group/reminder_milestones_due.txt",
|
||||
dict(group=group,
|
||||
milestones=milestones,
|
||||
today=today,
|
||||
early_warning_days=early_warning_days,
|
||||
url=settings.IDTRACKER_BASE_URL + urlreverse("group_charter", kwargs=dict(acronym=group.acronym))
|
||||
url=settings.IDTRACKER_BASE_URL + urlreverse("group_charter", kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
))
|
||||
|
||||
def groups_needing_milestones_due_reminder(early_warning_days):
|
||||
|
@ -134,10 +143,10 @@ def email_milestones_overdue(group):
|
|||
|
||||
send_mail(None, to, None,
|
||||
subject,
|
||||
"wginfo/reminder_milestones_overdue.txt",
|
||||
"group/reminder_milestones_overdue.txt",
|
||||
dict(group=group,
|
||||
milestones=milestones,
|
||||
url=settings.IDTRACKER_BASE_URL + urlreverse("group_charter", kwargs=dict(acronym=group.acronym))
|
||||
url=settings.IDTRACKER_BASE_URL + urlreverse("group_charter", kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
))
|
||||
|
||||
def groups_needing_milestones_overdue_reminder(grace_period=30):
|
|
@ -1,4 +1,4 @@
|
|||
# WG milestone editing views
|
||||
# group milestone editing views
|
||||
|
||||
import datetime
|
||||
import calendar
|
||||
|
@ -6,16 +6,15 @@ import json
|
|||
|
||||
from django import forms
|
||||
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest
|
||||
from django.shortcuts import render_to_response, get_object_or_404, redirect
|
||||
from django.template import RequestContext
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
from ietf.doc.models import Document, DocEvent
|
||||
from ietf.doc.utils import get_chartering_type
|
||||
from ietf.group.models import Group, GroupMilestone, MilestoneGroupEvent
|
||||
from ietf.group.utils import save_milestone_in_history
|
||||
from ietf.ietfauth.utils import role_required, has_role
|
||||
from ietf.group.utils import save_milestone_in_history, can_manage_group_type, milestone_reviewer_for_group_type
|
||||
from ietf.name.models import GroupMilestoneStateName
|
||||
from ietf.wginfo.mails import email_milestones_changed
|
||||
from ietf.group.mails import email_milestones_changed
|
||||
|
||||
def json_doc_names(docs):
|
||||
return json.dumps([{"id": doc.pk, "name": doc.name } for doc in docs])
|
||||
|
@ -108,23 +107,19 @@ class MilestoneForm(forms.Form):
|
|||
|
||||
return r
|
||||
|
||||
|
||||
@role_required('WG Chair', 'Area Director', 'Secretariat')
|
||||
def edit_milestones(request, acronym, milestone_set="current"):
|
||||
@login_required
|
||||
def edit_milestones(request, group_type, acronym, milestone_set="current"):
|
||||
# milestones_set + needs_review: we have several paths into this view
|
||||
# AD/Secr. -> all actions on current + add new
|
||||
# management (IRTF chair/AD/...)/Secr. -> all actions on current + add new
|
||||
# group chair -> limited actions on current + add new for review
|
||||
# (re)charter -> all actions on existing in state charter + add new in state charter
|
||||
#
|
||||
# For charters we store the history on the charter document to not confuse people.
|
||||
|
||||
login = request.user.person
|
||||
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
group = get_object_or_404(Group, type=group_type, acronym=acronym)
|
||||
|
||||
needs_review = False
|
||||
if not has_role(request.user, ("Area Director", "Secretariat")):
|
||||
if group.role_set.filter(name="chair", person=login):
|
||||
if not can_manage_group_type(request.user, group_type):
|
||||
if group.role_set.filter(name="chair", person__user=request.user):
|
||||
if milestone_set == "current":
|
||||
needs_review = True
|
||||
else:
|
||||
|
@ -298,10 +293,10 @@ def edit_milestones(request, acronym, milestone_set="current"):
|
|||
|
||||
if milestone_set == "charter":
|
||||
DocEvent.objects.create(doc=group.charter, type="changed_charter_milestone",
|
||||
by=login, desc=change)
|
||||
by=request.user.person, desc=change)
|
||||
else:
|
||||
MilestoneGroupEvent.objects.create(group=group, type="changed_milestone",
|
||||
by=login, desc=change, milestone=f.milestone)
|
||||
by=request.user.person, desc=change, milestone=f.milestone)
|
||||
|
||||
changes.append(change)
|
||||
|
||||
|
@ -311,7 +306,7 @@ def edit_milestones(request, acronym, milestone_set="current"):
|
|||
if milestone_set == "charter":
|
||||
return redirect('doc_view', name=group.charter.canonical_name())
|
||||
else:
|
||||
return redirect('group_charter', acronym=group.acronym)
|
||||
return redirect('group_charter', group_type=group.type_id, acronym=group.acronym)
|
||||
else:
|
||||
for m in milestones:
|
||||
forms.append(MilestoneForm(instance=m, needs_review=needs_review))
|
||||
|
@ -322,27 +317,25 @@ def edit_milestones(request, acronym, milestone_set="current"):
|
|||
|
||||
forms.sort(key=lambda f: f.milestone.due if f.milestone else datetime.date.max)
|
||||
|
||||
return render_to_response('wginfo/edit_milestones.html',
|
||||
dict(group=group,
|
||||
title=title,
|
||||
forms=forms,
|
||||
form_errors=form_errors,
|
||||
empty_form=empty_form,
|
||||
milestone_set=milestone_set,
|
||||
finished_milestone_text=finished_milestone_text,
|
||||
needs_review=needs_review,
|
||||
can_reset=can_reset),
|
||||
context_instance=RequestContext(request))
|
||||
return render(request, 'group/edit_milestones.html',
|
||||
dict(group=group,
|
||||
title=title,
|
||||
forms=forms,
|
||||
form_errors=form_errors,
|
||||
empty_form=empty_form,
|
||||
milestone_set=milestone_set,
|
||||
finished_milestone_text=finished_milestone_text,
|
||||
needs_review=needs_review,
|
||||
reviewer=milestone_reviewer_for_group_type(group_type),
|
||||
can_reset=can_reset))
|
||||
|
||||
@role_required('WG Chair', 'Area Director', 'Secretariat')
|
||||
def reset_charter_milestones(request, acronym):
|
||||
@login_required
|
||||
def reset_charter_milestones(request, group_type, acronym):
|
||||
"""Reset charter milestones to the currently in-use milestones."""
|
||||
login = request.user.person
|
||||
group = get_object_or_404(Group, type=group_type, acronym=acronym)
|
||||
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
|
||||
if (not has_role(request.user, ("Area Director", "Secretariat")) and
|
||||
not group.role_set.filter(name="chair", person=login)):
|
||||
if (not can_manage_group_type(request.user, group_type) and
|
||||
not group.role_set.filter(name="chair", person__user=request.user)):
|
||||
return HttpResponseForbidden("You are not chair of this group.")
|
||||
|
||||
current_milestones = group.groupmilestone_set.filter(state="active")
|
||||
|
@ -364,7 +357,7 @@ def reset_charter_milestones(request, acronym):
|
|||
DocEvent.objects.create(type="changed_charter_milestone",
|
||||
doc=group.charter,
|
||||
desc='Deleted milestone "%s"' % m.desc,
|
||||
by=login,
|
||||
by=request.user.person,
|
||||
)
|
||||
|
||||
# add current
|
||||
|
@ -380,20 +373,19 @@ def reset_charter_milestones(request, acronym):
|
|||
DocEvent.objects.create(type="changed_charter_milestone",
|
||||
doc=group.charter,
|
||||
desc='Added milestone "%s", due %s, from current group milestones' % (new.desc, new.due.strftime("%B %Y")),
|
||||
by=login,
|
||||
by=request.user.person,
|
||||
)
|
||||
|
||||
|
||||
return redirect('wg_edit_charter_milestones', acronym=group.acronym)
|
||||
return redirect('group_edit_charter_milestones', group_type=group.type_id, acronym=group.acronym)
|
||||
|
||||
return render_to_response('wginfo/reset_charter_milestones.html',
|
||||
dict(group=group,
|
||||
charter_milestones=charter_milestones,
|
||||
current_milestones=current_milestones,
|
||||
),
|
||||
context_instance=RequestContext(request))
|
||||
return render(request, 'group/reset_charter_milestones.html',
|
||||
dict(group=group,
|
||||
charter_milestones=charter_milestones,
|
||||
current_milestones=current_milestones,
|
||||
))
|
||||
|
||||
|
||||
def ajax_search_docs(request, acronym):
|
||||
def ajax_search_docs(request, group_type, acronym):
|
||||
docs = Document.objects.filter(name__icontains=request.GET.get('q',''), type="draft").order_by('name').distinct()[:20]
|
||||
return HttpResponse(json_doc_names(docs), content_type='application/json')
|
|
@ -19,7 +19,7 @@ from ietf.utils.test_utils import TestCase
|
|||
from ietf.utils.mail import outbox
|
||||
from ietf.utils.test_data import make_test_data
|
||||
from ietf.utils.test_utils import login_testing_unauthorized
|
||||
from ietf.wginfo.mails import ( email_milestone_review_reminder, email_milestones_due,
|
||||
from ietf.group.mails import ( email_milestone_review_reminder, email_milestones_due,
|
||||
email_milestones_overdue, groups_needing_milestones_due_reminder,
|
||||
groups_needing_milestones_overdue_reminder, groups_with_milestones_needing_review )
|
||||
|
||||
|
@ -32,11 +32,11 @@ class GroupPagesTests(TestCase):
|
|||
def tearDown(self):
|
||||
shutil.rmtree(self.charter_dir)
|
||||
|
||||
def test_active_wgs(self):
|
||||
def test_active_groups(self):
|
||||
draft = make_test_data()
|
||||
group = draft.group
|
||||
|
||||
url = urlreverse('ietf.wginfo.views.active_wgs')
|
||||
url = urlreverse('ietf.group.info.active_groups', kwargs=dict(group_type="wg"))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(group.parent.name in r.content)
|
||||
|
@ -44,6 +44,10 @@ class GroupPagesTests(TestCase):
|
|||
self.assertTrue(group.name in r.content)
|
||||
self.assertTrue(group.ad.plain_name() in r.content)
|
||||
|
||||
url = urlreverse('ietf.group.info.active_groups', kwargs=dict(group_type="rg"))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_wg_summaries(self):
|
||||
draft = make_test_data()
|
||||
group = draft.group
|
||||
|
@ -53,7 +57,7 @@ class GroupPagesTests(TestCase):
|
|||
with open(os.path.join(self.charter_dir, "%s-%s.txt" % (group.charter.canonical_name(), group.charter.rev)), "w") as f:
|
||||
f.write("This is a charter.")
|
||||
|
||||
url = urlreverse('ietf.wginfo.views.wg_summary_area')
|
||||
url = urlreverse('ietf.group.info.wg_summary_area', kwargs=dict(group_type="wg"))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(group.parent.name in r.content)
|
||||
|
@ -61,14 +65,14 @@ class GroupPagesTests(TestCase):
|
|||
self.assertTrue(group.name in r.content)
|
||||
self.assertTrue(chair.address in r.content)
|
||||
|
||||
url = urlreverse('ietf.wginfo.views.wg_summary_acronym')
|
||||
url = urlreverse('ietf.group.info.wg_summary_acronym', kwargs=dict(group_type="wg"))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(group.acronym in r.content)
|
||||
self.assertTrue(group.name in r.content)
|
||||
self.assertTrue(chair.address in r.content)
|
||||
|
||||
url = urlreverse('ietf.wginfo.views.wg_charters')
|
||||
url = urlreverse('ietf.group.info.wg_charters', kwargs=dict(group_type="wg"))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(group.acronym in r.content)
|
||||
|
@ -77,7 +81,7 @@ class GroupPagesTests(TestCase):
|
|||
self.assertTrue(chair.address in r.content)
|
||||
self.assertTrue("This is a charter." in r.content)
|
||||
|
||||
url = urlreverse('ietf.wginfo.views.wg_charters_by_acronym')
|
||||
url = urlreverse('ietf.group.info.wg_charters_by_acronym', kwargs=dict(group_type="wg"))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(group.acronym in r.content)
|
||||
|
@ -86,24 +90,36 @@ class GroupPagesTests(TestCase):
|
|||
self.assertTrue(chair.address in r.content)
|
||||
self.assertTrue("This is a charter." in r.content)
|
||||
|
||||
def test_chartering_wgs(self):
|
||||
def test_chartering_groups(self):
|
||||
draft = make_test_data()
|
||||
group = draft.group
|
||||
group.charter.set_state(State.objects.get(used=True, type="charter", slug="intrev"))
|
||||
|
||||
url = urlreverse('ietf.wginfo.views.chartering_wgs')
|
||||
url = urlreverse('ietf.group.info.chartering_groups')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('table.ietf-doctable td.acronym a:contains("%s")' % group.acronym)), 1)
|
||||
|
||||
def test_concluded_groups(self):
|
||||
draft = make_test_data()
|
||||
group = draft.group
|
||||
group.state = GroupStateName.objects.get(used=True, slug="conclude")
|
||||
group.save()
|
||||
|
||||
url = urlreverse('ietf.group.info.concluded_groups')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('table.concluded-groups a:contains("%s")' % group.acronym)), 1)
|
||||
|
||||
def test_bofs(self):
|
||||
draft = make_test_data()
|
||||
group = draft.group
|
||||
group.state_id = "bof"
|
||||
group.save()
|
||||
|
||||
url = urlreverse('ietf.wginfo.views.bofs')
|
||||
url = urlreverse('ietf.group.info.bofs', kwargs=dict(group_type="wg"))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
|
@ -137,7 +153,7 @@ class GroupPagesTests(TestCase):
|
|||
name=draft2.name,
|
||||
)
|
||||
|
||||
url = urlreverse('ietf.wginfo.views.group_documents', kwargs=dict(acronym=group.acronym))
|
||||
url = urlreverse('ietf.group.info.group_documents', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(draft.name in r.content)
|
||||
|
@ -147,7 +163,7 @@ class GroupPagesTests(TestCase):
|
|||
self.assertTrue(draft2.name in r.content)
|
||||
|
||||
# test the txt version too while we're at it
|
||||
url = urlreverse('ietf.wginfo.views.group_documents_txt', kwargs=dict(acronym=group.acronym))
|
||||
url = urlreverse('ietf.group.info.group_documents_txt', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(draft.name in r.content)
|
||||
|
@ -167,7 +183,7 @@ class GroupPagesTests(TestCase):
|
|||
due=datetime.date.today() + datetime.timedelta(days=100))
|
||||
milestone.docs.add(draft)
|
||||
|
||||
url = urlreverse('ietf.wginfo.views.group_charter', kwargs=dict(acronym=group.acronym))
|
||||
url = urlreverse('ietf.group.info.group_charter', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(group.name in r.content)
|
||||
|
@ -186,7 +202,7 @@ class GroupPagesTests(TestCase):
|
|||
type="added_comment",
|
||||
by=Person.objects.get(name="(System)"))
|
||||
|
||||
url = urlreverse('ietf.wginfo.views.history', kwargs=dict(acronym=group.acronym))
|
||||
url = urlreverse('ietf.group.info.history', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(e.desc in r.content)
|
||||
|
@ -225,7 +241,7 @@ class GroupEditTests(TestCase):
|
|||
def test_create(self):
|
||||
make_test_data()
|
||||
|
||||
url = urlreverse('wg_create')
|
||||
url = urlreverse('group_create', kwargs=dict(group_type="wg"))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
num_wgs = len(Group.objects.filter(type="wg"))
|
||||
|
@ -273,7 +289,7 @@ class GroupEditTests(TestCase):
|
|||
def test_create_based_on_existing(self):
|
||||
make_test_data()
|
||||
|
||||
url = urlreverse('wg_create')
|
||||
url = urlreverse('group_create', kwargs=dict(group_type="wg"))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
group = Group.objects.get(acronym="mars")
|
||||
|
@ -308,7 +324,7 @@ class GroupEditTests(TestCase):
|
|||
make_test_data()
|
||||
group = Group.objects.get(acronym="mars")
|
||||
|
||||
url = urlreverse('group_edit', kwargs=dict(acronym=group.acronym))
|
||||
url = urlreverse('group_edit', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
|
@ -380,7 +396,7 @@ class GroupEditTests(TestCase):
|
|||
|
||||
group = Group.objects.get(acronym="mars")
|
||||
|
||||
url = urlreverse('wg_conclude', kwargs=dict(acronym=group.acronym))
|
||||
url = urlreverse('ietf.group.edit.conclude', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
|
@ -435,7 +451,7 @@ class MilestoneTests(TestCase):
|
|||
def test_milestone_sets(self):
|
||||
m1, m2, group = self.create_test_milestones()
|
||||
|
||||
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
|
||||
url = urlreverse('group_edit_milestones', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
r = self.client.get(url)
|
||||
|
@ -443,7 +459,7 @@ class MilestoneTests(TestCase):
|
|||
self.assertTrue(m1.desc in r.content)
|
||||
self.assertTrue(m2.desc not in r.content)
|
||||
|
||||
url = urlreverse('wg_edit_charter_milestones', kwargs=dict(acronym=group.acronym))
|
||||
url = urlreverse('group_edit_charter_milestones', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
@ -453,7 +469,7 @@ class MilestoneTests(TestCase):
|
|||
def test_add_milestone(self):
|
||||
m1, m2, group = self.create_test_milestones()
|
||||
|
||||
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
|
||||
url = urlreverse('group_edit_milestones', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
|
@ -505,7 +521,7 @@ class MilestoneTests(TestCase):
|
|||
def test_add_milestone_as_chair(self):
|
||||
m1, m2, group = self.create_test_milestones()
|
||||
|
||||
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
|
||||
url = urlreverse('group_edit_milestones', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
login_testing_unauthorized(self, "marschairman", url)
|
||||
|
||||
# normal get
|
||||
|
@ -539,7 +555,7 @@ class MilestoneTests(TestCase):
|
|||
m1.state_id = "review"
|
||||
m1.save()
|
||||
|
||||
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
|
||||
url = urlreverse('group_edit_milestones', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
login_testing_unauthorized(self, "ad", url)
|
||||
|
||||
# normal get
|
||||
|
@ -569,7 +585,7 @@ class MilestoneTests(TestCase):
|
|||
def test_delete_milestone(self):
|
||||
m1, m2, group = self.create_test_milestones()
|
||||
|
||||
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
|
||||
url = urlreverse('group_edit_milestones', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
milestones_before = GroupMilestone.objects.count()
|
||||
|
@ -597,7 +613,7 @@ class MilestoneTests(TestCase):
|
|||
def test_edit_milestone(self):
|
||||
m1, m2, group = self.create_test_milestones()
|
||||
|
||||
url = urlreverse('wg_edit_milestones', kwargs=dict(acronym=group.acronym))
|
||||
url = urlreverse('group_edit_milestones', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
milestones_before = GroupMilestone.objects.count()
|
||||
|
@ -654,7 +670,7 @@ class MilestoneTests(TestCase):
|
|||
def test_reset_charter_milestones(self):
|
||||
m1, m2, group = self.create_test_milestones()
|
||||
|
||||
url = urlreverse('wg_reset_charter_milestones', kwargs=dict(acronym=group.acronym))
|
||||
url = urlreverse('group_reset_charter_milestones', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
|
@ -809,7 +825,7 @@ class MilestoneTests(TestCase):
|
|||
def test_ajax_search_docs(self):
|
||||
draft = make_test_data()
|
||||
|
||||
r = self.client.get(urlreverse("wg_ajax_search_docs", kwargs=dict(acronym=draft.group.acronym)),
|
||||
r = self.client.get(urlreverse("group_ajax_search_docs", kwargs=dict(group_type=draft.group.type_id, acronym=draft.group.acronym)),
|
||||
dict(q=draft.name))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
data = json.loads(r.content)
|
||||
|
@ -821,7 +837,7 @@ class CustomizeWorkflowTests(TestCase):
|
|||
|
||||
group = Group.objects.get(acronym="mars")
|
||||
|
||||
url = urlreverse('ietf.wginfo.edit.customize_workflow', kwargs=dict(acronym=group.acronym))
|
||||
url = urlreverse('ietf.group.edit.customize_workflow', kwargs=dict(group_type=group.type_id, acronym=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
state = State.objects.get(used=True, type="draft-stream-ietf", slug="wg-lc")
|
|
@ -4,6 +4,9 @@ from django.conf.urls import patterns
|
|||
|
||||
urlpatterns = patterns('',
|
||||
(r'^(?P<acronym>[a-z0-9]+).json$', 'ietf.group.ajax.group_json'),
|
||||
(r'^chartering/$', 'ietf.group.info.chartering_groups'),
|
||||
(r'^chartering/create/(?P<group_type>(wg|rg))/$', 'ietf.group.edit.edit', {'action': "charter"}, "group_create"),
|
||||
(r'^concluded/$', 'ietf.group.info.concluded_groups'),
|
||||
)
|
||||
|
||||
|
||||
|
|
35
ietf/group/urls_info.py
Normal file
35
ietf/group/urls_info.py
Normal 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),
|
||||
)
|
|
@ -1,10 +1,9 @@
|
|||
import os
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from ietf.group.models import Group, RoleHistory
|
||||
from ietf.person.models import Email
|
||||
from ietf.utils.history import get_history_object_for, copy_many_to_many_for_history
|
||||
from ietf.ietfauth.utils import has_role
|
||||
|
||||
|
||||
def save_group_in_history(group):
|
||||
|
@ -27,28 +26,21 @@ def save_group_in_history(group):
|
|||
|
||||
def get_charter_text(group):
|
||||
# get file path from settings. Syntesize file name from path, acronym, and suffix
|
||||
c = group.charter
|
||||
|
||||
# find the latest, preferably approved, revision
|
||||
for h in group.charter.history_set.exclude(rev="").order_by("time"):
|
||||
h_appr = "-" not in h.rev
|
||||
c_appr = "-" not in c.rev
|
||||
if (h.rev > c.rev and not (c_appr and not h_appr)) or (h_appr and not c_appr):
|
||||
c = h
|
||||
|
||||
filename = os.path.join(c.get_file_path(), "%s-%s.txt" % (c.canonical_name(), c.rev))
|
||||
try:
|
||||
# Try getting charter from new charter tool
|
||||
c = group.charter
|
||||
|
||||
# find the latest, preferably approved, revision
|
||||
for h in group.charter.history_set.exclude(rev="").order_by("time"):
|
||||
h_appr = "-" not in h.rev
|
||||
c_appr = "-" not in c.rev
|
||||
if (h.rev > c.rev and not (c_appr and not h_appr)) or (h_appr and not c_appr):
|
||||
c = h
|
||||
|
||||
filename = os.path.join(c.get_file_path(), "%s-%s.txt" % (c.canonical_name(), c.rev))
|
||||
with open(filename) as f:
|
||||
return f.read()
|
||||
except IOError:
|
||||
try:
|
||||
filename = os.path.join(settings.IETFWG_DESCRIPTIONS_PATH, group.acronym) + ".desc.txt"
|
||||
desc_file = open(filename)
|
||||
desc = desc_file.read()
|
||||
except BaseException:
|
||||
desc = 'Error Loading Work Group Description'
|
||||
return desc
|
||||
return 'Error Loading Group Charter'
|
||||
|
||||
def get_area_ads_emails(area):
|
||||
if area.acronym == 'none':
|
||||
|
@ -98,3 +90,18 @@ def save_milestone_in_history(milestone):
|
|||
copy_many_to_many_for_history(h, milestone)
|
||||
|
||||
return h
|
||||
|
||||
def can_manage_group_type(user, group_type):
|
||||
if group_type == "rg":
|
||||
return has_role(user, ('IRTF Chair', 'Secretariat'))
|
||||
elif group_type == "wg":
|
||||
return has_role(user, ('Area Director', 'Secretariat'))
|
||||
|
||||
return has_role(user, 'Secretariat')
|
||||
|
||||
def milestone_reviewer_for_group_type(group_type):
|
||||
if group_type == "rg":
|
||||
return "IRTF Chair"
|
||||
else:
|
||||
return "Area Director"
|
||||
|
||||
|
|
|
@ -199,7 +199,6 @@ INSTALLED_APPS = (
|
|||
'ietf.meeting',
|
||||
'ietf.utils',
|
||||
'ietf.redirects',
|
||||
'ietf.wginfo',
|
||||
'ietf.submit',
|
||||
'ietf.sync',
|
||||
'ietf.community',
|
||||
|
@ -268,7 +267,6 @@ STATUS_CHANGE_PATH = '/a/www/ietf-ftp/status-changes'
|
|||
STATUS_CHANGE_TXT_URL = 'http://www.ietf.org/sc/'
|
||||
AGENDA_PATH = '/a/www/www6s/proceedings/'
|
||||
IPR_DOCUMENT_PATH = '/a/www/ietf-ftp/ietf/IPR/'
|
||||
IETFWG_DESCRIPTIONS_PATH = '/a/www/www6s/wg-descriptions/'
|
||||
IESG_TASK_FILE = '/a/www/www6/iesg/internal/task.txt'
|
||||
IESG_ROLL_CALL_FILE = '/a/www/www6/iesg/internal/rollcall.txt'
|
||||
IESG_MINUTES_FILE = '/a/www/www6/iesg/internal/minutes.txt'
|
||||
|
|
|
@ -254,7 +254,7 @@ class EditSubmissionForm(forms.ModelForm):
|
|||
return document_date
|
||||
|
||||
class PreapprovalForm(forms.Form):
|
||||
name = forms.CharField(max_length=255, required=True, label="Pre-approved name", initial="draft-ietf-")
|
||||
name = forms.CharField(max_length=255, required=True, label="Pre-approved name", initial="draft-")
|
||||
|
||||
def clean_name(self):
|
||||
n = self.cleaned_data['name'].strip().lower()
|
||||
|
@ -268,7 +268,7 @@ class PreapprovalForm(forms.Form):
|
|||
if components[-1] == "00":
|
||||
raise forms.ValidationError("Name appears to end with a revision number -00 - do not include the revision.")
|
||||
if len(components) < 4:
|
||||
raise forms.ValidationError("Name has less than four dash-delimited components - can't form a valid WG draft name.")
|
||||
raise forms.ValidationError("Name has less than four dash-delimited components - can't form a valid group draft name.")
|
||||
if not components[-1]:
|
||||
raise forms.ValidationError("Name ends with a dash.")
|
||||
acronym = components[2]
|
||||
|
|
|
@ -331,7 +331,7 @@ def preapprovals_for_user(user):
|
|||
if has_role(user, "Secretariat"):
|
||||
return res
|
||||
|
||||
acronyms = [g.acronym for g in Group.objects.filter(role__person__user=user, type="wg")]
|
||||
acronyms = [g.acronym for g in Group.objects.filter(role__person__user=user, type__in=("wg", "rg"))]
|
||||
|
||||
res = res.filter(name__regex="draft-[^-]+-(%s)-.*" % "|".join(acronyms))
|
||||
|
||||
|
|
|
@ -401,9 +401,9 @@ def approvals(request):
|
|||
context_instance=RequestContext(request))
|
||||
|
||||
|
||||
@role_required("Secretariat", "WG Chair")
|
||||
@role_required("Secretariat", "WG Chair", "RG Chair")
|
||||
def add_preapproval(request):
|
||||
groups = Group.objects.filter(type="wg").exclude(state="conclude").order_by("acronym").distinct()
|
||||
groups = Group.objects.filter(type__in=("wg", "rg")).exclude(state="conclude").order_by("acronym").distinct()
|
||||
|
||||
if not has_role(request.user, "Secretariat"):
|
||||
groups = groups.filter(role__person__user=request.user,role__name__in=['ad','chair','delegate','secr'])
|
||||
|
@ -427,7 +427,7 @@ def add_preapproval(request):
|
|||
'form': form },
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
@role_required("Secretariat", "WG Chair")
|
||||
@role_required("Secretariat", "WG Chair", "RG Chair")
|
||||
def cancel_preapproval(request, preapproval_id):
|
||||
preapproval = get_object_or_404(Preapproval, pk=preapproval_id)
|
||||
|
||||
|
|
|
@ -72,10 +72,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
</ul>
|
||||
</div></div></li>
|
||||
|
||||
<li style="padding-top:0;"><a href="/wg/">Active WGs</a></li>
|
||||
<li><a href="{% url "ietf.wginfo.views.chartering_wgs" %}">Chartering WGs</a></li>
|
||||
<li><a href="{% url "ietf.wginfo.views.bofs" %}">BoFs</a></li>
|
||||
<li><a href="http://tools.ietf.org/wg/concluded">Concluded WGs</a></li>
|
||||
<li style="padding-top:0;"><a href="{% url "ietf.group.info.active_groups" group_type="wg" %}">Active WGs</a></li>
|
||||
<li style="padding-top:0;"><a href="{% url "ietf.group.info.active_groups" group_type="rg" %}">Active RGs</a></li>
|
||||
<li><a href="{% url "ietf.group.info.chartering_groups" %}">Chartering</a></li>
|
||||
<li><a href="{% url "ietf.group.info.bofs" group_type="wg" %}">BoFs</a></li>
|
||||
<li><a href="{% url "ietf.group.info.concluded_groups" %}">Concluded</a></li>
|
||||
<li><a href="http://www.ietf.org/list/nonwg.html">Non-WG Lists</a></li>
|
||||
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ Charter submission for {{ group.acronym }} {{ group.type.name }}
|
|||
{% block content %}
|
||||
<h1>Charter submission for {{ group.acronym }} {{ group.type.name }}</h1>
|
||||
|
||||
<p>The text will be submitted as <strong>charter-ietf-{{ group.acronym }}-{{ next_rev }}</strong></p>
|
||||
<p>The text will be submitted as <strong>{{ name }}-{{ next_rev }}</strong></p>
|
||||
<form class="edit-info" action="" enctype="multipart/form-data" method="post">{% csrf_token %}
|
||||
<table>
|
||||
{% for field in form.visible_fields %}
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
{% if snapshot %}Snapshot of{% endif %}
|
||||
{% if doc.get_state_slug != "approved" %}Proposed{% endif %}
|
||||
Charter for "{{ group.name }}"
|
||||
(<a {% if group.type.slug == "wg" %}href="{% url "ietf.wginfo.views.group_charter" acronym=group.acronym %}"{% endif %}>{{ group.acronym }}</a>) {{ group.type.name }}
|
||||
(<a {% if group.type_id == "wg" or group.type_id == "rg" %}href="{% url "ietf.group.info.group_charter" group_type=group.type_id acronym=group.acronym %}"{% endif %}>{{ group.acronym }}</a>) {{ group.type.name }}
|
||||
</div>
|
||||
|
||||
<table id="metatable" width="100%">
|
||||
|
@ -38,7 +38,7 @@
|
|||
<td>
|
||||
<div>
|
||||
<a title="{{ doc.get_state.desc }}"
|
||||
{% if not snapshot and user|has_role:"Area Director,Secretariat" %}
|
||||
{% if not snapshot and can_manage %}
|
||||
class="editlink" href="{% url "charter_change_state" name=doc.name %}"
|
||||
{% endif %}>
|
||||
{{ doc.get_state.name }}
|
||||
|
@ -69,7 +69,7 @@
|
|||
|
||||
<tr>
|
||||
<td>Responsible AD:</td>
|
||||
<td><a {% if request.user|has_role:"Area Director,Secretariat" %}class="editlink" href="{% url "charter_edit_ad" name=doc.name %}"{% endif %}>{{ doc.ad|default:"none" }}</a> </td>
|
||||
<td><a {% if user|has_role:"Area Director,Secretariat" %}class="editlink" href="{% url "charter_edit_ad" name=doc.name %}"{% endif %}>{{ doc.ad|default:"none" }}</a> </td>
|
||||
</tr>
|
||||
|
||||
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
|
||||
|
@ -94,13 +94,13 @@
|
|||
</div>
|
||||
|
||||
<div class="actions">
|
||||
{% if not snapshot and user|has_role:"Area Director,Secretariat" %}
|
||||
{% if not snapshot and can_manage %}
|
||||
{% if chartering %}
|
||||
{% url "charter_startstop_process" name=doc.name option='abandon' as abandon_url %}{% if abandon_url %}<a class="button" href="{{ abandon_url }}">Abandon Effort</a>{% endif %}
|
||||
|
||||
{% if request.user|has_role:"Secretariat" %}
|
||||
{% url "charter_approve" name=doc.name as approve_url %}{% if approve_url %}<a class="button" href="{{ approve_url }}">Approve Charter</a>{% endif %}
|
||||
{% endif %}
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
{% url "charter_approve" name=doc.name as approve_url %}{% if approve_url %}<a class="button" href="{{ approve_url }}">Approve Charter</a>{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
{% if group.state_id == "proposed" or group.state_id == "bof" %}
|
||||
|
@ -118,7 +118,7 @@
|
|||
|
||||
<h3>Charter {{ doc.canonical_name }}-{{ doc.rev }}
|
||||
|
||||
{% if not snapshot and user|has_role:"Area Director,Secretariat" and chartering and group.state_id != "conclude" %}
|
||||
{% if not snapshot and can_manage and chartering and group.state_id != "conclude" %}
|
||||
<a class="edit" href="{% url "charter_submit" name=doc.name %}">Change charter text</a>
|
||||
{% endif %}
|
||||
</h3>
|
||||
|
@ -131,13 +131,13 @@
|
|||
|
||||
{% if not snapshot and chartering %}
|
||||
<h3>Proposed Milestones
|
||||
{% if user|has_role:"Area Director,Secretariat" %}
|
||||
<a class="edit" href="{% url "wg_edit_charter_milestones" acronym=doc.group.acronym %}">Edit charter milestones</a>
|
||||
{% if can_manage %}
|
||||
<a class="edit" href="{% url "group_edit_charter_milestones" group_type=doc.group.type_id acronym=doc.group.acronym %}">Edit charter milestones</a>
|
||||
{% endif %}
|
||||
</h3>
|
||||
|
||||
{% if milestones %}
|
||||
{% include "wginfo/milestones.html" %}
|
||||
{% include "group/milestones.html" %}
|
||||
{% else %}
|
||||
<p>No milestones for charter found.</p>
|
||||
{% endif %}
|
||||
|
|
|
@ -36,7 +36,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
<div class="search-results">
|
||||
|
||||
{% if not docs %}
|
||||
<h2>No documents match your query.</h2>
|
||||
{% if not skip_no_matches_warning %}
|
||||
<h2>No documents match your query.</h2>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
|
||||
{% if meta.max %}
|
||||
|
|
4
ietf/templates/group/1wg-charters-by-acronym.txt
Normal file
4
ietf/templates/group/1wg-charters-by-acronym.txt
Normal 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 %}
|
|
@ -1,6 +1,6 @@
|
|||
{% autoescape off %}{% load ietf_filters %}{% for area in areas %}{% for group in area.groups %}{{ group.acronym }}
|
||||
{% endfor %}{% endfor %}
|
||||
|
||||
{% for area in areas %}{% for group in area.groups %}{% include "wginfo/group_entry_with_charter.txt" %}{% endfor %}{% endfor %}{% endautoescape %}
|
||||
{% for area in areas %}{% for group in area.groups %}{% include "group/group_entry_with_charter.txt" %}{% endfor %}{% endfor %}{% endautoescape %}
|
||||
|
||||
|
|
@ -7,4 +7,4 @@ The following Area Abbreviations are used in this document
|
|||
{{ area.acronym|upper }} - {{ area.name }}{% endfor %}
|
||||
{% for group in groups %}
|
||||
{{ group.name }} ({{ group.acronym }}) -- {{ group.parent.acronym|upper }}
|
||||
{% include "wginfo/group_entry.txt" %}{% endfor %}{% endautoescape %}
|
||||
{% include "group/group_entry.txt" %}{% endfor %}{% endautoescape %}
|
|
@ -7,5 +7,5 @@
|
|||
{{ ad.person.plain_name }} <{{ ad.email.address }}>{% endfor %}
|
||||
|
||||
{% for group in area.groups %}{{ group.name }} ({{ group.acronym }})
|
||||
{% include "wginfo/group_entry.txt" %}
|
||||
{% include "group/group_entry.txt" %}
|
||||
{% endfor %}{% endfor %}{% endautoescape %}
|
33
ietf/templates/group/active_rgs.html
Normal file
33
ietf/templates/group/active_rgs.html
Normal 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 %}
|
|
@ -80,7 +80,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
<table class="ietf-wg-table">
|
||||
{% for group in area.groups %}
|
||||
<tr>
|
||||
<td width="10%;"><a href="/wg/{{ group.acronym }}/">{{ group.acronym }}</a></td>
|
||||
<td width="10%;"><a href="{% url "ietf.group.info.group_documents" group_type=group.type_id acronym=group.acronym %}">{{ group.acronym }}</a></td>
|
||||
<td width="1%">{% for ad in area.ads %}{% if ad.person_id == group.ad_id %}<span title="AD for {{ group.acronym }}: {{ ad.person }}" class="square bgcolor{{forloop.counter}}"></span>{% endif %}{% endfor %}</td>
|
||||
<td width="50%">{{ group.name }}</td>
|
||||
<td width="39%">{% for chair in group.chairs %}<a href="mailto:{{ chair.email.address }}">{{ chair.person.plain_name }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}</td>
|
|
@ -10,7 +10,7 @@
|
|||
<p>Groups in the BoF state</p>
|
||||
|
||||
{% if user|has_role:"Area Director,Secretariat" %}
|
||||
<p><a href="{% url "bof_create" %}">Create a new BoF</a></p>
|
||||
<p><a href="{% url "bof_create" group_type="wg" %}">Create a new BoF</a></p>
|
||||
{% endif %}
|
||||
|
||||
{% if not groups %}
|
||||
|
@ -25,7 +25,7 @@
|
|||
{% for g in groups %}
|
||||
<tr class="{{ forloop.counter|divisibleby:2|yesno:"oddrow,evenrow" }}">
|
||||
<td class="acronym">
|
||||
<a href="{% url "group_charter" acronym=g.acronym %}">{{ g.acronym }}</a>
|
||||
<a href="{% url "group_charter" group_type=g.type_id acronym=g.acronym %}">{{ g.acronym }}</a>
|
||||
</td>
|
||||
<td class="title">
|
||||
<a {%comment%}href="{% url "doc_view" name=g.charter.name %}"{%endcomment%}>{{ g.name }}</a>
|
65
ietf/templates/group/chartering_groups.html
Normal file
65
ietf/templates/group/chartering_groups.html
Normal 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 %}
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Request closing of {{ wg.acronym }} {{ wg.type.name }}{% endblock %}
|
||||
{% block title %}Request closing of {{ group.acronym }} {{ group.type.name }}{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
#id_instructions {
|
||||
|
@ -14,7 +14,7 @@ form.conclude .actions {
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Request closing of {{ wg.acronym }} {{ wg.type.name }}</h1>
|
||||
<h1>Request closing of {{ group.acronym }} {{ group.type.name }}</h1>
|
||||
|
||||
<p>
|
||||
Please provide instructions regarding the disposition of each
|
||||
|
@ -29,7 +29,7 @@ form.conclude .actions {
|
|||
{{ form.as_table }}
|
||||
<tr>
|
||||
<td colspan="2" class="actions">
|
||||
<a class="button" href="{% url "group_charter" acronym=wg.acronym %}">Cancel</a>
|
||||
<a class="button" href="{% url "group_charter" group_type=group.type_id acronym=group.acronym %}">Cancel</a>
|
||||
<input class="button" type="submit" value="Send request"/>
|
||||
</td>
|
||||
</tr>
|
42
ietf/templates/group/concluded_groups.html
Normal file
42
ietf/templates/group/concluded_groups.html
Normal 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 %}
|
|
@ -34,12 +34,13 @@
|
|||
<h1>Customize Workflow for {{ group.acronym }} {{ group.type.name }}</h1>
|
||||
|
||||
<p>Below you can customize the draft states and tags used in the
|
||||
<a href="{% url "group_charter" group.acronym %}">{{ group.acronym }} {{ group.type.name }}</a>. Note that some states are
|
||||
<a href="{% url "group_charter" group_type=group.type_id acronym=group.acronym %}">{{ group.acronym }} {{ group.type.name }}</a>. Note that some states are
|
||||
mandatory for group operation and cannot be deactivated.</p>
|
||||
|
||||
{% if group.type_id == "wg" %}
|
||||
<p>You can see the default Working Group I-D State Diagram
|
||||
in <a href="http://tools.ietf.org/html/rfc6174#section-4.1">Section 4.1 of RFC6174</a>.</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
<h3>States</h3>
|
||||
|
|
@ -15,11 +15,11 @@ digraph draftdeps {
|
|||
fontcolor=black,
|
||||
label="Colors in\nthis row"];
|
||||
key_wgdoc [color="#0AFE47",
|
||||
label="Product of\nthis WG",
|
||||
label="Product of\nthis group",
|
||||
style=filled,
|
||||
wg=this];
|
||||
key_otherwgdoc [color="#9999FF",
|
||||
label="Product of\nother WG",
|
||||
label="Product of\nother group",
|
||||
style=filled,
|
||||
wg=blort];
|
||||
key_individual [color="#FF800D",
|
|
@ -1,10 +1,10 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{% if wg %}
|
||||
Edit WG {{ wg.acronym }}
|
||||
{% if group %}
|
||||
Edit {{ group.type.name }} {{ group.acronym }}
|
||||
{% else %}
|
||||
Start chartering new WG
|
||||
Start chartering new group
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -28,12 +28,12 @@ form.edit #id_urls { height: 4em; }
|
|||
{% load ietf_filters %}
|
||||
<h1>
|
||||
{% if action == "edit" %}
|
||||
Edit WG {{ wg.acronym }}
|
||||
Edit {{ group.type.name }} {{ group.acronym }}
|
||||
{% else %}
|
||||
{% if action == "charter" %}
|
||||
Start chartering new WG
|
||||
Start chartering new group
|
||||
{% else %}
|
||||
Create new WG or BoF
|
||||
Create new group or BoF
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</h1>
|
||||
|
@ -43,13 +43,14 @@ chairs and delegates, need a Datatracker account to actually do
|
|||
so. New accounts can be <a href="{% url "create_account" %}">created here</a>.</p>
|
||||
|
||||
<form class="edit" action="" method="post">{% csrf_token %}
|
||||
{% for field in form.hidden_fields %}{{ field }}{% endfor %}
|
||||
<table>
|
||||
{% for field in form.visible_fields %}
|
||||
<tr>
|
||||
<th>{{ field.label_tag }} {% if field.field.required %}*{% endif %}</th>
|
||||
<td>{{ field }}
|
||||
{% if field.name == "ad" and user|has_role:"Area Director" %}
|
||||
<label><input type="checkbox" name="ad" value="{{ login.pk }}" /> Assign to me</label>
|
||||
{% if field.name == "ad" and request.user|has_role:"Area Director" %}
|
||||
<label><input type="checkbox" name="ad" value="{{ request.user.person.pk }}" /> Assign to me</label>
|
||||
{% endif %}
|
||||
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
|
||||
{{ field.errors }}
|
||||
|
@ -68,7 +69,7 @@ so. New accounts can be <a href="{% url "create_account" %}">created here</a>.</
|
|||
<td></td>
|
||||
<td class="actions">
|
||||
{% if action == "edit" %}
|
||||
<a class="button" href="{% url "group_charter" acronym=wg.acronym %}">Cancel</a>
|
||||
<a class="button" href="{% url "group_charter" group_type=group.type_id acronym=group.acronym %}">Cancel</a>
|
||||
<input class="button" type="submit" value="Save"/>
|
||||
{% else %}
|
||||
{% if action == "charter" %}
|
|
@ -42,7 +42,7 @@ tr.milestone.add { font-style: italic; }
|
|||
<noscript>This page depends on Javascript being enabled to work properly.</noscript>
|
||||
|
||||
<p>Links:
|
||||
<a href="{% url "group_charter" acronym=group.acronym %}">{{ group.acronym }} {{ group.type.name }}</a>
|
||||
<a href="{% url "group_charter" group_type=group.type_id acronym=group.acronym %}">{{ group.acronym }} {{ group.type.name }}</a>
|
||||
- <a href="{% url "doc_view" name=group.charter.canonical_name %}">{{ group.charter.canonical_name }}</a>
|
||||
</p>
|
||||
|
||||
|
@ -51,14 +51,13 @@ tr.milestone.add { font-style: italic; }
|
|||
|
||||
{% if needs_review %}
|
||||
Note that as {{ group.type.name }} Chair you cannot edit descriptions of existing
|
||||
milestones and milestones you add are subject to review by the Area
|
||||
Director.
|
||||
milestones and milestones you add are subject to review by the {{ reviewer }}.
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{% if can_reset %}
|
||||
<p>
|
||||
You can <a href="{% url "wg_reset_charter_milestones" acronym=group.acronym %}">reset
|
||||
You can <a href="{% url "group_reset_charter_milestones" group_type=group.type_id acronym=group.acronym %}">reset
|
||||
this list</a> to the milestones currently in use for the {{ group.acronym }} {{ group.type.name }}.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
@ -84,14 +83,14 @@ this list</a> to the milestones currently in use for the {{ group.acronym }} {{
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="edit-milestone{% if form.changed %} changed{% endif %}"><td colspan="2">{% include "wginfo/milestone_form.html" %}</td></tr>
|
||||
<tr class="edit-milestone{% if form.changed %} changed{% endif %}"><td colspan="2">{% include "group/milestone_form.html" %}</td></tr>
|
||||
{% endfor %}
|
||||
<tr class="milestone add"><td></td><td>Add {% if milestone_set == "chartering" %}charter{% endif%} milestone {% if needs_review %}for AD review{% endif %}</td></tr>
|
||||
<tr class="edit-milestone template"><td colspan="2">{% include "wginfo/milestone_form.html" with form=empty_form %}</td></tr>
|
||||
<tr class="milestone add"><td></td><td>Add {% if milestone_set == "chartering" %}charter{% endif%} milestone {% if needs_review %}for {{ reviewer }} review{% endif %}</td></tr>
|
||||
<tr class="edit-milestone template"><td colspan="2">{% include "group/milestone_form.html" with form=empty_form %}</td></tr>
|
||||
</table>
|
||||
|
||||
<div class="actions">
|
||||
<a class="button" href="{% if milestone_set == "charter" %}{% url "doc_view" name=group.charter.canonical_name %}{% else %}{% url "group_charter" acronym=group.acronym %}{% endif %}">Cancel</a>
|
||||
<a class="button" href="{% if milestone_set == "charter" %}{% url "doc_view" name=group.charter.canonical_name %}{% else %}{% url "group_charter" group_type=group.type_id acronym=group.acronym %}{% endif %}">Cancel</a>
|
||||
<input class="button" type="submit" data-labelsave="Save" data-labelreview="Review changes" value="Save" style="display:none"/>
|
||||
<input type="hidden" name="action" value="save">
|
||||
</div>
|
|
@ -66,15 +66,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
<div class="ietf-navset">
|
||||
<div>
|
||||
<a {% if selected == "documents" %}class="selected"{% else %}href="{% url "ietf.wginfo.views.group_documents" acronym=group.acronym %}"{% endif %}>Documents</a> |
|
||||
<a {% if selected == "charter" %}class="selected"{% else %}href="{% url "ietf.wginfo.views.group_charter" acronym=group.acronym %}"{% endif %}>Charter</a> |
|
||||
<a {% if selected == "history" %}class="selected"{% else %}href="{% url "ietf.wginfo.views.history" acronym=group.acronym %}"{% endif %}>History</a>
|
||||
| <a href="{% url 'ietf.wginfo.views.dependencies_pdf' acronym=group.acronym %}">Dependency Graph</a>
|
||||
<a {% if selected == "documents" %}class="selected"{% else %}href="{% url "ietf.group.info.group_documents" group_type=group.type_id acronym=group.acronym %}"{% endif %}>Documents</a> |
|
||||
<a {% if selected == "charter" %}class="selected"{% else %}href="{% url "ietf.group.info.group_charter" group_type=group.type_id acronym=group.acronym %}"{% endif %}>Charter</a> |
|
||||
<a {% if selected == "history" %}class="selected"{% else %}href="{% url "ietf.group.info.history" group_type=group.type_id acronym=group.acronym %}"{% endif %}>History</a>
|
||||
| <a href="{% url 'ietf.group.info.dependencies_pdf' group_type=group.type_id acronym=group.acronym %}">Dependency Graph</a>
|
||||
{% if group.list_archive|startswith:"http:" or group.list_archive|startswith:"https:" or group.list_archive|startswith:"ftp:" %}
|
||||
| <a href="{{ group.list_archive }}">List Archive »</a>
|
||||
{% endif %}
|
||||
{% if group.has_tools_page %}
|
||||
| <a href="http://tools.ietf.org/wg/{{ group.acronym }}/">Tools WG Page »</a>
|
||||
| <a href="http://tools.ietf.org/{{ group.type_id }}/{{ group.acronym }}/">Tools {{ group.type.name }} Page »</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "wginfo/group_base.html" %}
|
||||
{% extends "group/group_base.html" %}
|
||||
|
||||
{% load ietf_filters %}
|
||||
{% block group_subtitle %}Charter{% endblock %}
|
||||
|
@ -12,7 +12,7 @@ h2 a.button { margin-left: 0.5em; font-size: 13px; }
|
|||
<div class="ietf-box ietf-group-details">
|
||||
|
||||
{% if group.state_id == "conclude" %}
|
||||
<span class="ietf-concluded-warning">Note: The data for concluded WGs
|
||||
<span class="ietf-concluded-warning">Note: The data for concluded {{ group.type.name }}s
|
||||
is occasionally incorrect.</span>
|
||||
{% endif %}
|
||||
|
||||
|
@ -26,7 +26,7 @@ is occasionally incorrect.</span>
|
|||
|
||||
<tr><td>Acronym:</td><td>{{ group.acronym }}</td></tr>
|
||||
|
||||
{% if group.parent %}
|
||||
{% if group.parent and group.parent.type_id == "area" %}
|
||||
<tr><td>{{ group.parent.type.name }}:</td><td>{{ group.parent.name }} ({{ group.parent.acronym }})</td></tr>
|
||||
{% endif %}
|
||||
|
||||
|
@ -46,8 +46,8 @@ is occasionally incorrect.</span>
|
|||
<a href="{% url "doc_view" name=group.charter.name %}">{{ group.charter.name }}-{{ group.charter.rev }}</a> ({{ group.charter.get_state.name }})
|
||||
{% else %}
|
||||
none
|
||||
{% if user|has_role:"Area Director,Secretariat" %}
|
||||
- <a href="{% url "ietf.wginfo.edit.submit_initial_charter" acronym=group.acronym %}">Submit Charter</a>
|
||||
{% if can_manage %}
|
||||
- <a href="{% url "ietf.group.edit.submit_initial_charter" group_type=group.type_id acronym=group.acronym %}">Submit Charter</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
@ -64,6 +64,7 @@ is occasionally incorrect.</span>
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
{% if group.parent.type_id == "area" %}
|
||||
<tr><td>Area Director:</td>
|
||||
<td>
|
||||
{% if group.areadirector %}
|
||||
|
@ -71,6 +72,7 @@ is occasionally incorrect.</span>
|
|||
{% else %}?{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
{% if group.techadvisors %}
|
||||
<tr>
|
||||
|
@ -142,7 +144,7 @@ is occasionally incorrect.</span>
|
|||
|
||||
{% with group.groupurl_set.all as urls %}
|
||||
{% if urls %}
|
||||
<p>In addition to the charter maintained by the IETF Secretariat, there is additional information about this working group on the Web at:
|
||||
<p>In addition to the charter, there is additional information about this group on the Web at:
|
||||
{% for url in urls %}
|
||||
<a href="{{ url.url }}">{{ url.name }}</a>{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
|
@ -150,17 +152,17 @@ is occasionally incorrect.</span>
|
|||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<h2>Charter for {% if group.state_id == "proposed" %}Proposed{% endif %} Working Group</h2>
|
||||
<h2>Charter for {% if group.state_id == "proposed" %}Proposed{% endif %} {{ long_group_type }}</h2>
|
||||
|
||||
<p>{{ group.charter_text|escape|format_charter|safe }}</p>
|
||||
|
||||
<h2>{% if group.state_id == "proposed" %}Proposed{% endif %} Milestones</h2>
|
||||
|
||||
{% include "wginfo/milestones.html" with milestones=group.milestones %}
|
||||
{% include "group/milestones.html" with milestones=group.milestones %}
|
||||
|
||||
{% if milestones_in_review %}
|
||||
<p>+ {{ milestones_in_review|length }} new milestone{{ milestones_in_review|pluralize }}
|
||||
currently in Area Director review.</p>
|
||||
currently in {{ milestone_reviewer }} review.</p>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "wginfo/group_base.html" %}
|
||||
{% extends "group/group_base.html" %}
|
||||
|
||||
{% block group_subtitle %}Documents{% endblock %}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
|||
|
||||
{% include "doc/search/search_results.html" %}
|
||||
|
||||
{% include "doc/search/search_results.html" with docs=docs_related meta=meta_related %}
|
||||
{% include "doc/search/search_results.html" with docs=docs_related meta=meta_related skip_no_matches_warning=True %}
|
||||
|
||||
</div>
|
||||
{% endblock group_content %}
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "wginfo/group_base.html" %}
|
||||
{% extends "group/group_base.html" %}
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% block group_subtitle %}History{% endblock %}
|
|
@ -1,4 +1,4 @@
|
|||
{# assumes group, form, needs_review are in the context #}
|
||||
{# assumes group, form, needs_review, reviewer are in the context #}
|
||||
<input type="hidden" name="prefix" value="{{ form.prefix|default:"" }}"/>
|
||||
{{ form.id }}
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
|||
</tr>
|
||||
<tr class="docs">
|
||||
<td>Drafts:</td>
|
||||
<td><input name="{{ form.docs.html_name }}" class="tokenized-field" data-ajax-url="{% url "wg_ajax_search_docs" group.acronym %}" data-pre="{{ form.docs_prepopulate }}"/>
|
||||
<td><input name="{{ form.docs.html_name }}" class="tokenized-field" data-ajax-url="{% url "group_ajax_search_docs" group_type=group.type_id acronym=group.acronym %}" data-pre="{{ form.docs_prepopulate }}"/>
|
||||
{{ form.docs.errors }}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -35,7 +35,7 @@
|
|||
<td>Review:</td>
|
||||
<td class="accept">
|
||||
This milestone is not active yet, awaiting
|
||||
AD acceptance{% if needs_review %}.{% else %}: {{ form.accept }}{% endif %}
|
||||
{{ reviewer }} acceptance{% if needs_review %}.{% else %}: {{ form.accept }}{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
|
@ -1,4 +1,4 @@
|
|||
{% autoescape off %}{% filter wordwrap:73 %}{{ milestones|length }} new milestone{{ milestones|pluralize }} in "{{ group.name }}" {% if milestones|length > 1 %}need{% else %}needs{%endif %} an AD review:
|
||||
{% autoescape off %}{% filter wordwrap:73 %}{{ milestones|length }} new milestone{{ milestones|pluralize }} in "{{ group.name }}" {% if milestones|length > 1 %}need{% else %}needs{%endif %} review by the {{ reviewer }}:
|
||||
|
||||
{% for m in milestones %}"{{ m.desc }}"{% if m.days_ready != None %}
|
||||
Waiting for {{ m.days_ready }} day{{ m.days_ready|pluralize }}.{% endif %}
|
|
@ -29,7 +29,7 @@
|
|||
{% endfor %}
|
||||
|
||||
<div class="actions">
|
||||
<a href="{% url "wg_edit_charter_milestones" acronym=group.acronym %}">Back</a>
|
||||
<a href="{% url "group_edit_charter_milestones" group_type=group.type_id acronym=group.acronym %}">Back</a>
|
||||
<input type="submit" value="Reset charter milestones"/>
|
||||
</div>
|
||||
</form>
|
|
@ -15,9 +15,9 @@ div.milestones-for-group { margin: 0.5em 0; }
|
|||
|
||||
{% for g in ad.groups_needing_review %}
|
||||
|
||||
<div class="milestones-for-group">{{ g.name }} ({{ g.acronym }}) <a href="{% url "wg_edit_milestones" acronym=g.acronym %}">has new milestones</a>:</div>
|
||||
<div class="milestones-for-group">{{ g.name }} ({{ g.acronym }}) <a href="{% url "group_edit_milestones" group_type=g.type_id acronym=g.acronym %}">has new milestones</a>:</div>
|
||||
|
||||
{% include "wginfo/milestones.html" with milestones=g.milestones_needing_review %}
|
||||
{% include "group/milestones.html" with milestones=g.milestones_needing_review %}
|
||||
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
|
|
@ -18,8 +18,8 @@ form .actions a { display: inline-block; margin-right: 1em; }
|
|||
|
||||
<h2>Add Pre-Approval</h2>
|
||||
|
||||
<p>You can register a pre-approved draft name. Then the WG Chair
|
||||
approval step of WG -00 submissions is suspended for that draft name
|
||||
<p>You can register a pre-approved draft name. Then the chair
|
||||
approval step of group -00 submissions is suspended for that draft name
|
||||
so a future submission is posted to the data tracker immediately.</p>
|
||||
|
||||
<p>When the revision 00 draft is submitted, the pre-approval will not
|
||||
|
@ -33,12 +33,12 @@ later cancel the pre-approval to get rid of it.</p>
|
|||
like <code>.txt</code> in the name.</p>
|
||||
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
<p>Only WG submissions are subject to approval and are thus pre-approvable.</p>
|
||||
<p>Only group submissions are subject to approval and are thus pre-approvable.</p>
|
||||
{% else %}
|
||||
<p>As WG Chair{% if groups|length > 1 %} of {{ groups|length }} groups{% endif %} you can pre-approve draft names on the form:
|
||||
<p>As chair{% if groups|length > 1 %} of {{ groups|length }} groups{% endif %} you can pre-approve draft names on the form (click to pre-fill):
|
||||
<table>
|
||||
{% for g in groups %}
|
||||
<tr><td class="name-template">draft-ietf-{{ g.acronym }}-<i>something</i></td></tr>
|
||||
<tr><td class="name-template">draft-{% if g.type_id == "rg"%}irtf{% else %}ietf{% endif %}-{{ g.acronym }}-<i>something</i></td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</p>
|
||||
|
@ -56,8 +56,8 @@ like <code>.txt</code> in the name.</p>
|
|||
|
||||
<tr>
|
||||
<td class="actions" colspan="2">
|
||||
<a href="{% url "submit_approvals" %}#preapprovals">Back</a>
|
||||
<input type="submit" value="Add pre-approval" />
|
||||
<a class="button" href="{% url "submit_approvals" %}#preapprovals">Cancel</a>
|
||||
<input class="button" type="submit" value="Add pre-approval" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -68,7 +68,8 @@ like <code>.txt</code> in the name.</p>
|
|||
{% block scripts %}
|
||||
jQuery(function () {
|
||||
jQuery(".name-template").click(function (){
|
||||
jQuery("form.add-preapproval input[name=name]").val(jQuery(this).text().replace("something", ""));
|
||||
// clear text first to make sure the cursor ends up at the end
|
||||
jQuery("form.add-preapproval input[name=name]").text("").focus().val(jQuery(this).text().replace("something", ""));
|
||||
});
|
||||
});
|
||||
{% endblock %}
|
||||
|
|
|
@ -37,7 +37,7 @@ table.preapprovals tr:hover td a.cancel { visibility: visible; }
|
|||
|
||||
<h2 id="preapprovals">Pre-approved drafts not yet submitted</h2>
|
||||
|
||||
{% if user|has_role:"Secretariat,WG Chair" %}
|
||||
{% if user|has_role:"Secretariat,WG Chair,RG Chair" %}
|
||||
<p>You can <a href="{% url "submit_add_preapproval" %}">add a pre-approval</a>.</p>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -15,9 +15,9 @@ Pre-approval of <b>{{ preapproval.name }}</b> by {{ preapproval.by }}
|
|||
on {{ preapproval.time }}.
|
||||
|
||||
<form class="actions" action="" method="post">{% csrf_token %}
|
||||
<a href="{% url "submit_approvals" %}#preapprovals">Back</a>
|
||||
<a class="button" href="{% url "submit_approvals" %}#preapprovals">Don't cancel</a>
|
||||
<input type="hidden" name="action" value="cancel" />
|
||||
<input type="submit" value="Cancel pre-approval" />
|
||||
<input class="button" type="submit" value="Cancel pre-approval" />
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -53,8 +53,8 @@ urlpatterns = patterns('',
|
|||
(r'^sitemap.xml$', 'django.contrib.sitemaps.views.index', { 'sitemaps': sitemaps}),
|
||||
(r'^submit/', include('ietf.submit.urls')),
|
||||
(r'^sync/', include('ietf.sync.urls')),
|
||||
(r'^wg/', include('ietf.wginfo.urls')),
|
||||
(r'^stream/', include('ietf.group.stream_urls')),
|
||||
(r'^(?P<group_type>(wg|rg))/', include('ietf.group.urls_info')),
|
||||
(r'^stream/', include('ietf.group.urls_stream')),
|
||||
(r'^nomcom/', include('ietf.nomcom.urls')),
|
||||
(r'^templates/', include('ietf.dbtemplate.urls')),
|
||||
|
||||
|
|
1
ietf/wginfo/.gitignore
vendored
1
ietf/wginfo/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
/*.pyc
|
|
@ -1,2 +0,0 @@
|
|||
# Copyright The IETF Trust 2008, All Rights Reserved
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# Copyright The IETF Trust 2008, All Rights Reserved
|
||||
|
|
@ -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),
|
||||
)
|
Loading…
Reference in a new issue