diff --git a/changelog b/changelog index b5426c55a..52b7034ba 100644 --- a/changelog +++ b/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 03 Jun 2014 23:12:44 -0200 + + ietfdb (5.4.3) ietf; urgency=medium * Merged in [7772] from rjsparks@nostrum.com: diff --git a/ietf/bin/send-milestone-reminders b/ietf/bin/send-milestone-reminders index bbd514dd0..9ed5d254f 100755 --- a/ietf/bin/send-milestone-reminders +++ b/ietf/bin/send-milestone-reminders @@ -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() diff --git a/ietf/community/display.py b/ietf/community/display.py index b5438d81f..d42769597 100644 --- a/ietf/community/display.py +++ b/ietf/community/display.py @@ -88,7 +88,7 @@ class WGField(DisplayField): if raw: return document.group.acronym else: - return '%s' % (urlreverse('wg_docs', kwargs={'acronym':document.group.acronym}), document.group.acronym) if (document.group and document.group.acronym != 'none') else '' + return '%s' % (urlreverse('group_docs', kwargs=dict(group_type=document.group.type_id, acronym=document.group.acronym)), document.group.acronym) if (document.group and document.group.acronym != 'none') else '' class ADField(DisplayField): diff --git a/ietf/doc/models.py b/ietf/doc/models.py index 2d56c35a0..916c326c4 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -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): diff --git a/ietf/doc/views_charter.py b/ietf/doc/views_charter.py index a3247eee2..b9c5ac47b 100644 --- a/ietf/doc/views_charter.py +++ b/ietf/doc/views_charter.py @@ -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
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: %s-%s.txt" % (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: %s-%s.txt" % (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: %s-%s.txt" % (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) diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index 4fb148628..755a19b5d 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -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 = "%s" % (urlreverse("wg_docs", kwargs=dict(acronym=doc.group.acronym)), submission) + submission = "%s" % (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)) diff --git a/ietf/feed_urls.py b/ietf/feed_urls.py index f58926a7c..eea94dc7b 100644 --- a/ietf/feed_urls.py +++ b/ietf/feed_urls.py @@ -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 diff --git a/ietf/wginfo/edit.py b/ietf/group/edit.py similarity index 58% rename from ietf/wginfo/edit.py rename to ietf/group/edit.py index 7f9138341..b77eafd62 100644 --- a/ietf/wginfo/edit.py +++ b/ietf/group/edit.py @@ -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
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 %(new)s from %(old)s" - if new_wg: + if new_group: entry = "%(attr)s changed to %(new)s" 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[\w\d:#@%/;$()~_?\+-=\\\.&]+)( \((?P.+)\))?', 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)) + }) diff --git a/ietf/wginfo/feeds.py b/ietf/group/feeds.py similarity index 84% rename from ietf/wginfo/feeds.py rename to ietf/group/feeds.py index 8ec8a5fca..e54c6a2bf 100644 --- a/ietf/wginfo/feeds.py +++ b/ietf/group/feeds.py @@ -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 diff --git a/ietf/wginfo/views.py b/ietf/group/info.py similarity index 69% rename from ietf/wginfo/views.py rename to ietf/group/info.py index 678be6d10..4068873f4 100644 --- a/ietf/wginfo/views.py +++ b/ietf/group/info.py @@ -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--wg-etc - if len(parts) >= 3 and parts[1] != "ietf" and parts[2].startswith(group.acronym + "-"): + # canonical form draft--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) diff --git a/ietf/wginfo/mails.py b/ietf/group/mails.py similarity index 80% rename from ietf/wginfo/mails.py rename to ietf/group/mails.py index d4546197e..4a2cbdd26 100644 --- a/ietf/wginfo/mails.py +++ b/ietf/group/mails.py @@ -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 ") 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 ") + + 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): diff --git a/ietf/wginfo/milestones.py b/ietf/group/milestones.py similarity index 84% rename from ietf/wginfo/milestones.py rename to ietf/group/milestones.py index d628a52b4..6b4a0083e 100644 --- a/ietf/wginfo/milestones.py +++ b/ietf/group/milestones.py @@ -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') diff --git a/ietf/wginfo/tests.py b/ietf/group/tests_info.py similarity index 91% rename from ietf/wginfo/tests.py rename to ietf/group/tests_info.py index 018a9068d..4e1b26b3f 100644 --- a/ietf/wginfo/tests.py +++ b/ietf/group/tests_info.py @@ -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") diff --git a/ietf/group/urls.py b/ietf/group/urls.py index 97ea99157..ea049c7e5 100644 --- a/ietf/group/urls.py +++ b/ietf/group/urls.py @@ -4,6 +4,9 @@ from django.conf.urls import patterns urlpatterns = patterns('', (r'^(?P[a-z0-9]+).json$', 'ietf.group.ajax.group_json'), + (r'^chartering/$', 'ietf.group.info.chartering_groups'), + (r'^chartering/create/(?P(wg|rg))/$', 'ietf.group.edit.edit', {'action': "charter"}, "group_create"), + (r'^concluded/$', 'ietf.group.info.concluded_groups'), ) diff --git a/ietf/group/urls_info.py b/ietf/group/urls_info.py new file mode 100644 index 000000000..0039047b5 --- /dev/null +++ b/ietf/group/urls_info.py @@ -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[a-zA-Z0-9-._]+)/documents/txt/$', info.group_documents_txt), + (r'^(?P[a-zA-Z0-9-._]+)/$', info.group_documents, None, "group_docs"), + (r'^(?P[a-zA-Z0-9-._]+)/charter/$', info.group_charter, None, 'group_charter'), + (r'^(?P[a-zA-Z0-9-._]+)/history/$', info.history), + (r'^(?P[a-zA-Z0-9-._]+)/deps/dot/$', info.dependencies_dot), + (r'^(?P[a-zA-Z0-9-._]+)/deps/pdf/$', info.dependencies_pdf), + (r'^(?P[a-zA-Z0-9-._]+)/init-charter/', edit.submit_initial_charter), + (r'^(?P[a-zA-Z0-9-._]+)/edit/$', edit.edit, {'action': "edit"}, "group_edit"), + (r'^(?P[a-zA-Z0-9-._]+)/conclude/$', edit.conclude), + (r'^(?P[a-zA-Z0-9-._]+)/milestones/$', milestones.edit_milestones, {'milestone_set': "current"}, "group_edit_milestones"), + (r'^(?P[a-zA-Z0-9-._]+)/milestones/charter/$', milestones.edit_milestones, {'milestone_set': "charter"}, "group_edit_charter_milestones"), + (r'^(?P[a-zA-Z0-9-._]+)/milestones/charter/reset/$', milestones.reset_charter_milestones, None, "group_reset_charter_milestones"), + (r'^(?P[a-zA-Z0-9-._]+)/ajax/searchdocs/$', milestones.ajax_search_docs, None, "group_ajax_search_docs"), + (r'^(?P[a-zA-Z0-9-._]+)/workflow/$', edit.customize_workflow), +) diff --git a/ietf/group/stream_urls.py b/ietf/group/urls_stream.py similarity index 100% rename from ietf/group/stream_urls.py rename to ietf/group/urls_stream.py diff --git a/ietf/group/utils.py b/ietf/group/utils.py index bd79c7dce..8dda40488 100644 --- a/ietf/group/utils.py +++ b/ietf/group/utils.py @@ -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" + diff --git a/ietf/settings.py b/ietf/settings.py index 7772cf5e5..f835b5415 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -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' diff --git a/ietf/submit/forms.py b/ietf/submit/forms.py index 9f04313a0..975f5797c 100644 --- a/ietf/submit/forms.py +++ b/ietf/submit/forms.py @@ -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] diff --git a/ietf/submit/utils.py b/ietf/submit/utils.py index 207064120..b561a3045 100644 --- a/ietf/submit/utils.py +++ b/ietf/submit/utils.py @@ -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)) diff --git a/ietf/submit/views.py b/ietf/submit/views.py index daa008718..6749086e8 100644 --- a/ietf/submit/views.py +++ b/ietf/submit/views.py @@ -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) diff --git a/ietf/templates/base/left_menu.html b/ietf/templates/base/left_menu.html index f9b1886b4..da92a0acd 100644 --- a/ietf/templates/base/left_menu.html +++ b/ietf/templates/base/left_menu.html @@ -72,10 +72,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -
  • Active WGs
  • -
  • Chartering WGs
  • -
  • BoFs
  • -
  • Concluded WGs
  • +
  • Active WGs
  • +
  • Active RGs
  • +
  • Chartering
  • +
  • BoFs
  • +
  • Concluded
  • Non-WG Lists
  • diff --git a/ietf/templates/doc/charter/submit.html b/ietf/templates/doc/charter/submit.html index b239beb5d..52dedc927 100644 --- a/ietf/templates/doc/charter/submit.html +++ b/ietf/templates/doc/charter/submit.html @@ -14,7 +14,7 @@ Charter submission for {{ group.acronym }} {{ group.type.name }} {% block content %}

    Charter submission for {{ group.acronym }} {{ group.type.name }}

    -

    The text will be submitted as charter-ietf-{{ group.acronym }}-{{ next_rev }}

    +

    The text will be submitted as {{ name }}-{{ next_rev }}

    {% csrf_token %} {% for field in form.visible_fields %} diff --git a/ietf/templates/doc/document_charter.html b/ietf/templates/doc/document_charter.html index 351872718..a39f26226 100644 --- a/ietf/templates/doc/document_charter.html +++ b/ietf/templates/doc/document_charter.html @@ -25,7 +25,7 @@ {% if snapshot %}Snapshot of{% endif %} {% if doc.get_state_slug != "approved" %}Proposed{% endif %} Charter for "{{ group.name }}" - ({{ group.acronym }}) {{ group.type.name }} + ({{ group.acronym }}) {{ group.type.name }}
    @@ -38,7 +38,7 @@ - + @@ -94,13 +94,13 @@
    - {% 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 %}Abandon Effort{% endif %} - {% if request.user|has_role:"Secretariat" %} - {% url "charter_approve" name=doc.name as approve_url %}{% if approve_url %}Approve Charter{% endif %} - {% endif %} + {% if user|has_role:"Secretariat" %} + {% url "charter_approve" name=doc.name as approve_url %}{% if approve_url %}Approve Charter{% endif %} + {% endif %} {% else %} {% if group.state_id == "proposed" or group.state_id == "bof" %} @@ -118,7 +118,7 @@

    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" %} Change charter text {% endif %}

    @@ -131,13 +131,13 @@ {% if not snapshot and chartering %}

    Proposed Milestones -{% if user|has_role:"Area Director,Secretariat" %} -Edit charter milestones +{% if can_manage %} +Edit charter milestones {% endif %}

    {% if milestones %} -{% include "wginfo/milestones.html" %} +{% include "group/milestones.html" %} {% else %}

    No milestones for charter found.

    {% endif %} diff --git a/ietf/templates/doc/search/search_results.html b/ietf/templates/doc/search/search_results.html index 8a5e0fb38..2c9d77756 100644 --- a/ietf/templates/doc/search/search_results.html +++ b/ietf/templates/doc/search/search_results.html @@ -36,7 +36,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    {% if not docs %} -

    No documents match your query.

    + {% if not skip_no_matches_warning %} +

    No documents match your query.

    + {% endif %} {% else %} {% if meta.max %} diff --git a/ietf/templates/group/1wg-charters-by-acronym.txt b/ietf/templates/group/1wg-charters-by-acronym.txt new file mode 100644 index 000000000..c2cf24e15 --- /dev/null +++ b/ietf/templates/group/1wg-charters-by-acronym.txt @@ -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 %} diff --git a/ietf/templates/wginfo/1wg-charters.txt b/ietf/templates/group/1wg-charters.txt similarity index 71% rename from ietf/templates/wginfo/1wg-charters.txt rename to ietf/templates/group/1wg-charters.txt index 767dd1bb0..d79f7ac59 100644 --- a/ietf/templates/wginfo/1wg-charters.txt +++ b/ietf/templates/group/1wg-charters.txt @@ -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 %} diff --git a/ietf/templates/wginfo/1wg-summary-by-acronym.txt b/ietf/templates/group/1wg-summary-by-acronym.txt similarity index 82% rename from ietf/templates/wginfo/1wg-summary-by-acronym.txt rename to ietf/templates/group/1wg-summary-by-acronym.txt index 028d5fd0e..50174815f 100644 --- a/ietf/templates/wginfo/1wg-summary-by-acronym.txt +++ b/ietf/templates/group/1wg-summary-by-acronym.txt @@ -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 %} diff --git a/ietf/templates/wginfo/1wg-summary.txt b/ietf/templates/group/1wg-summary.txt similarity index 90% rename from ietf/templates/wginfo/1wg-summary.txt rename to ietf/templates/group/1wg-summary.txt index f6ae51202..47d1ef1a7 100644 --- a/ietf/templates/wginfo/1wg-summary.txt +++ b/ietf/templates/group/1wg-summary.txt @@ -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 %} diff --git a/ietf/templates/group/active_rgs.html b/ietf/templates/group/active_rgs.html new file mode 100644 index 000000000..58b8ca7b4 --- /dev/null +++ b/ietf/templates/group/active_rgs.html @@ -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 %} +

    Active IRTF Research Groups

    + +

    IRTF Chair:

    + + + +

    Active Research Groups:

    + +
    +
    Responsible AD:{{ doc.ad|default:"none" }} {{ doc.ad|default:"none" }}

    + {% for group in groups %} + + + + + + {% endfor %} +
    {{ group.acronym }}{{ group.name }}{% for chair in group.chairs %}{{ chair.person.plain_name }}{% if not forloop.last %}, {% endif %}{% endfor %}
    + + +{% endblock %} diff --git a/ietf/templates/wginfo/active_wgs.html b/ietf/templates/group/active_wgs.html similarity index 96% rename from ietf/templates/wginfo/active_wgs.html rename to ietf/templates/group/active_wgs.html index 4d88435fa..d631a2405 100644 --- a/ietf/templates/wginfo/active_wgs.html +++ b/ietf/templates/group/active_wgs.html @@ -80,7 +80,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. {% for group in area.groups %} - + diff --git a/ietf/templates/wginfo/bofs.html b/ietf/templates/group/bofs.html similarity index 80% rename from ietf/templates/wginfo/bofs.html rename to ietf/templates/group/bofs.html index 8123cc6bd..04fafa646 100644 --- a/ietf/templates/wginfo/bofs.html +++ b/ietf/templates/group/bofs.html @@ -10,7 +10,7 @@

    Groups in the BoF state

    {% if user|has_role:"Area Director,Secretariat" %} -

    Create a new BoF

    +

    Create a new BoF

    {% endif %} {% if not groups %} @@ -25,7 +25,7 @@ {% for g in groups %} diff --git a/ietf/templates/group/concluded_groups.html b/ietf/templates/group/concluded_groups.html new file mode 100644 index 000000000..e309a388c --- /dev/null +++ b/ietf/templates/group/concluded_groups.html @@ -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 %} +

    Concluded Groups

    + +

    Note that the information on historical groups may be inaccurate.

    + +{% for t in group_types %} +

    {{ t.name }}s

    + + {% if t.slug == "wg" %}

    Some additional concluded WGs may + be present at tools.ietf.org/wg/concluded/

    {% endif %} + + {% if not t.concluded_groups %} +

    No groups found.

    + {% else %} +
    {{ group.acronym }}{{ group.acronym }} {% for ad in area.ads %}{% if ad.person_id == group.ad_id %}{% endif %}{% endfor %} {{ group.name }} {% for chair in group.chairs %}{{ chair.person.plain_name }}{% if not forloop.last %}, {% endif %}{% endfor %}
    - {{ g.acronym }} + {{ g.acronym }} {{ g.name }} diff --git a/ietf/templates/group/chartering_groups.html b/ietf/templates/group/chartering_groups.html new file mode 100644 index 000000000..6218c1073 --- /dev/null +++ b/ietf/templates/group/chartering_groups.html @@ -0,0 +1,65 @@ +{% extends "base.html" %} + +{% block title %}Chartering or Re-Chartering Groups{% endblock %} + +{% block content %} +{% load ietf_filters %} +{% load ballot_icon %} + +

    Chartering or Re-Chartering Groups

    + +

    Groups with a charter in state + {% for s in charter_states %}{% if not forloop.first %}, {% if forloop.last %}or {% endif %}{% endif %}{{ s.name }}{% endfor %}. +

    + + +{% for t in group_types %} +

    {{ t.name }}s

    + + {% if t.can_manage %} +

    Start chartering new {{ t.name }}

    + {% endif %} + + {% if not t.chartering_groups %} +

    No groups found.

    + {% else %} + + + + + + + + {% for g in t.chartering_groups %} + + + + + + + + {% endfor %} +
    GroupCharterDateStatus
    + {{ g.acronym }} + + {{ g.name }} + {{ g.charter.time|date:"Y-m-d" }} + {{ 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 %}
    IESG Telechat: {{ g.charter.telechat_date|date:"Y-m-d" }}{% endif %} +
    + {% ballot_icon g.charter %} +
    + {% endif %} + +{% endfor %} + +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/ietf/templates/wginfo/conclude.html b/ietf/templates/group/conclude.html similarity index 74% rename from ietf/templates/wginfo/conclude.html rename to ietf/templates/group/conclude.html index 0812e64d5..705dd4989 100644 --- a/ietf/templates/wginfo/conclude.html +++ b/ietf/templates/group/conclude.html @@ -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 %} -

    Request closing of {{ wg.acronym }} {{ wg.type.name }}

    +

    Request closing of {{ group.acronym }} {{ group.type.name }}

    Please provide instructions regarding the disposition of each @@ -29,7 +29,7 @@ form.conclude .actions { {{ form.as_table }}

    - Cancel + Cancel
    + {% for g in t.concluded_groups %} + + + + + {% endfor %} +
    + {{ g.acronym }} + {{ g.name }} + ({% if g.start_date %}{{ g.start_date|date:"M. Y" }}{% else %}?{% endif %} + - + {% if g.conclude_date %}{{ g.conclude_date|date:"M. Y" }}{% else %}?{% endif %}) +
    + {% endif %} + +{% endfor %} + +{% endblock %} diff --git a/ietf/templates/wginfo/customize_workflow.html b/ietf/templates/group/customize_workflow.html similarity index 96% rename from ietf/templates/wginfo/customize_workflow.html rename to ietf/templates/group/customize_workflow.html index 069e319a9..533690b09 100644 --- a/ietf/templates/wginfo/customize_workflow.html +++ b/ietf/templates/group/customize_workflow.html @@ -34,12 +34,13 @@

    Customize Workflow for {{ group.acronym }} {{ group.type.name }}

    Below you can customize the draft states and tags used in the -{{ group.acronym }} {{ group.type.name }}. Note that some states are +{{ group.acronym }} {{ group.type.name }}. Note that some states are mandatory for group operation and cannot be deactivated.

    +{% if group.type_id == "wg" %}

    You can see the default Working Group I-D State Diagram in Section 4.1 of RFC6174.

    - +{% endif %}

    States

    diff --git a/ietf/templates/wginfo/dot.txt b/ietf/templates/group/dot.txt similarity index 96% rename from ietf/templates/wginfo/dot.txt rename to ietf/templates/group/dot.txt index b31943961..ed2658c5a 100644 --- a/ietf/templates/wginfo/dot.txt +++ b/ietf/templates/group/dot.txt @@ -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", diff --git a/ietf/templates/wginfo/edit.html b/ietf/templates/group/edit.html similarity index 82% rename from ietf/templates/wginfo/edit.html rename to ietf/templates/group/edit.html index 8e21b090d..2cadc8910 100644 --- a/ietf/templates/wginfo/edit.html +++ b/ietf/templates/group/edit.html @@ -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 %}

    {% 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 %}

    @@ -43,13 +43,14 @@ chairs and delegates, need a Datatracker account to actually do so. New accounts can be created here.

    {% csrf_token %} + {% for field in form.hidden_fields %}{{ field }}{% endfor %} {% for field in form.visible_fields %} - + {% endfor %} - - + +
    {{ field.label_tag }} {% if field.field.required %}*{% endif %} {{ field }} - {% if field.name == "ad" and user|has_role:"Area Director" %} - + {% if field.name == "ad" and request.user|has_role:"Area Director" %} + {% endif %} {% if field.help_text %}
    {{ field.help_text }}
    {% endif %} {{ field.errors }} @@ -68,7 +69,7 @@ so. New accounts can be created here.
    {% if action == "edit" %} - Cancel + Cancel {% else %} {% if action == "charter" %} diff --git a/ietf/templates/wginfo/edit_milestones.html b/ietf/templates/group/edit_milestones.html similarity index 87% rename from ietf/templates/wginfo/edit_milestones.html rename to ietf/templates/group/edit_milestones.html index 676d47b56..045821194 100644 --- a/ietf/templates/wginfo/edit_milestones.html +++ b/ietf/templates/group/edit_milestones.html @@ -42,7 +42,7 @@ tr.milestone.add { font-style: italic; }

    Links: - {{ group.acronym }} {{ group.type.name }} + {{ group.acronym }} {{ group.type.name }} - {{ group.charter.canonical_name }}

    @@ -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 %}

    {% if can_reset %}

    -You can reset +You can reset this list to the milestones currently in use for the {{ group.acronym }} {{ group.type.name }}.

    {% endif %} @@ -84,14 +83,14 @@ this list to the milestones currently in use for the {{ group.acronym }} {{
    {% include "wginfo/milestone_form.html" %}
    {% include "group/milestone_form.html" %}
    Add {% if milestone_set == "chartering" %}charter{% endif%} milestone {% if needs_review %}for AD review{% endif %}
    {% include "wginfo/milestone_form.html" with form=empty_form %}
    Add {% if milestone_set == "chartering" %}charter{% endif%} milestone {% if needs_review %}for {{ reviewer }} review{% endif %}
    {% include "group/milestone_form.html" with form=empty_form %}
    diff --git a/ietf/templates/wginfo/email_secretariat.txt b/ietf/templates/group/email_secretariat.txt similarity index 100% rename from ietf/templates/wginfo/email_secretariat.txt rename to ietf/templates/group/email_secretariat.txt diff --git a/ietf/templates/wginfo/feed_item_description.html b/ietf/templates/group/feed_item_description.html similarity index 100% rename from ietf/templates/wginfo/feed_item_description.html rename to ietf/templates/group/feed_item_description.html diff --git a/ietf/templates/wginfo/group_base.html b/ietf/templates/group/group_base.html similarity index 86% rename from ietf/templates/wginfo/group_base.html rename to ietf/templates/group/group_base.html index d6dfea8bc..d605d803a 100644 --- a/ietf/templates/wginfo/group_base.html +++ b/ietf/templates/group/group_base.html @@ -66,15 +66,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    - Documents | - Charter | - History - | Dependency Graph + Documents | + Charter | + History + | Dependency Graph {% if group.list_archive|startswith:"http:" or group.list_archive|startswith:"https:" or group.list_archive|startswith:"ftp:" %} | List Archive » {% endif %} {% if group.has_tools_page %} - | Tools WG Page » + | Tools {{ group.type.name }} Page » {% endif %}
    diff --git a/ietf/templates/wginfo/group_charter.html b/ietf/templates/group/group_charter.html similarity index 87% rename from ietf/templates/wginfo/group_charter.html rename to ietf/templates/group/group_charter.html index 8aaa27317..1481229e3 100644 --- a/ietf/templates/wginfo/group_charter.html +++ b/ietf/templates/group/group_charter.html @@ -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; }
    {% if group.state_id == "conclude" %} -Note: The data for concluded WGs +Note: The data for concluded {{ group.type.name }}s is occasionally incorrect. {% endif %} @@ -26,7 +26,7 @@ is occasionally incorrect. Acronym:{{ group.acronym }} - {% if group.parent %} + {% if group.parent and group.parent.type_id == "area" %} {{ group.parent.type.name }}:{{ group.parent.name }} ({{ group.parent.acronym }}) {% endif %} @@ -46,8 +46,8 @@ is occasionally incorrect. {{ group.charter.name }}-{{ group.charter.rev }} ({{ group.charter.get_state.name }}) {% else %} none - {% if user|has_role:"Area Director,Secretariat" %} - - Submit Charter + {% if can_manage %} + - Submit Charter {% endif %} {% endif %} @@ -64,6 +64,7 @@ is occasionally incorrect. + {% if group.parent.type_id == "area" %} Area Director: {% if group.areadirector %} @@ -71,6 +72,7 @@ is occasionally incorrect. {% else %}?{% endif %} + {% endif %} {% if group.techadvisors %} @@ -142,7 +144,7 @@ is occasionally incorrect. {% with group.groupurl_set.all as urls %} {% if urls %} -

    In addition to the charter maintained by the IETF Secretariat, there is additional information about this working group on the Web at: +

    In addition to the charter, there is additional information about this group on the Web at: {% for url in urls %} {{ url.name }}{% if not forloop.last %}, {% endif %} {% endfor %} @@ -150,17 +152,17 @@ is occasionally incorrect. {% endif %} {% endwith %} -

    Charter for {% if group.state_id == "proposed" %}Proposed{% endif %} Working Group

    +

    Charter for {% if group.state_id == "proposed" %}Proposed{% endif %} {{ long_group_type }}

    {{ group.charter_text|escape|format_charter|safe }}

    {% if group.state_id == "proposed" %}Proposed{% endif %} Milestones

    -{% include "wginfo/milestones.html" with milestones=group.milestones %} +{% include "group/milestones.html" with milestones=group.milestones %} {% if milestones_in_review %}

    + {{ milestones_in_review|length }} new milestone{{ milestones_in_review|pluralize }} -currently in Area Director review.

    +currently in {{ milestone_reviewer }} review.

    {% endif %} {% endblock %} diff --git a/ietf/templates/wginfo/group_documents.html b/ietf/templates/group/group_documents.html similarity index 81% rename from ietf/templates/wginfo/group_documents.html rename to ietf/templates/group/group_documents.html index dfbc2edb3..0795b9d99 100644 --- a/ietf/templates/wginfo/group_documents.html +++ b/ietf/templates/group/group_documents.html @@ -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 %}
    {% endblock group_content %} diff --git a/ietf/templates/wginfo/group_entry.txt b/ietf/templates/group/group_entry.txt similarity index 100% rename from ietf/templates/wginfo/group_entry.txt rename to ietf/templates/group/group_entry.txt diff --git a/ietf/templates/wginfo/group_entry_with_charter.txt b/ietf/templates/group/group_entry_with_charter.txt similarity index 100% rename from ietf/templates/wginfo/group_entry_with_charter.txt rename to ietf/templates/group/group_entry_with_charter.txt diff --git a/ietf/templates/wginfo/history.html b/ietf/templates/group/history.html similarity index 93% rename from ietf/templates/wginfo/history.html rename to ietf/templates/group/history.html index bc1926609..66d2b0a8d 100644 --- a/ietf/templates/wginfo/history.html +++ b/ietf/templates/group/history.html @@ -1,4 +1,4 @@ -{% extends "wginfo/group_base.html" %} +{% extends "group/group_base.html" %} {% load ietf_filters %} {% block group_subtitle %}History{% endblock %} diff --git a/ietf/templates/wginfo/milestone_form.html b/ietf/templates/group/milestone_form.html similarity index 80% rename from ietf/templates/wginfo/milestone_form.html rename to ietf/templates/group/milestone_form.html index c0b261ebf..bee0e8746 100644 --- a/ietf/templates/wginfo/milestone_form.html +++ b/ietf/templates/group/milestone_form.html @@ -1,4 +1,4 @@ -{# assumes group, form, needs_review are in the context #} +{# assumes group, form, needs_review, reviewer are in the context #} {{ form.id }} @@ -26,7 +26,7 @@ Drafts: - + {{ form.docs.errors }} @@ -35,7 +35,7 @@ Review: 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 %} {% endif %} diff --git a/ietf/templates/wginfo/milestones.html b/ietf/templates/group/milestones.html similarity index 100% rename from ietf/templates/wginfo/milestones.html rename to ietf/templates/group/milestones.html diff --git a/ietf/templates/wginfo/reminder_milestones_due.txt b/ietf/templates/group/reminder_milestones_due.txt similarity index 100% rename from ietf/templates/wginfo/reminder_milestones_due.txt rename to ietf/templates/group/reminder_milestones_due.txt diff --git a/ietf/templates/wginfo/reminder_milestones_need_review.txt b/ietf/templates/group/reminder_milestones_need_review.txt similarity index 80% rename from ietf/templates/wginfo/reminder_milestones_need_review.txt rename to ietf/templates/group/reminder_milestones_need_review.txt index e32dbfac7..4a552783e 100644 --- a/ietf/templates/wginfo/reminder_milestones_need_review.txt +++ b/ietf/templates/group/reminder_milestones_need_review.txt @@ -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 %} diff --git a/ietf/templates/wginfo/reminder_milestones_overdue.txt b/ietf/templates/group/reminder_milestones_overdue.txt similarity index 100% rename from ietf/templates/wginfo/reminder_milestones_overdue.txt rename to ietf/templates/group/reminder_milestones_overdue.txt diff --git a/ietf/templates/wginfo/reset_charter_milestones.html b/ietf/templates/group/reset_charter_milestones.html similarity index 91% rename from ietf/templates/wginfo/reset_charter_milestones.html rename to ietf/templates/group/reset_charter_milestones.html index a905d1047..6ee387399 100644 --- a/ietf/templates/wginfo/reset_charter_milestones.html +++ b/ietf/templates/group/reset_charter_milestones.html @@ -29,7 +29,7 @@ {% endfor %}
    - Back + Back
    diff --git a/ietf/templates/iesg/milestones_needing_review.html b/ietf/templates/iesg/milestones_needing_review.html index 3cca9584b..547d06bf8 100644 --- a/ietf/templates/iesg/milestones_needing_review.html +++ b/ietf/templates/iesg/milestones_needing_review.html @@ -15,9 +15,9 @@ div.milestones-for-group { margin: 0.5em 0; } {% for g in ad.groups_needing_review %} -
    {{ g.name }} ({{ g.acronym }}) has new milestones:
    +
    {{ g.name }} ({{ g.acronym }}) has new milestones:
    -{% include "wginfo/milestones.html" with milestones=g.milestones_needing_review %} +{% include "group/milestones.html" with milestones=g.milestones_needing_review %} {% endfor %} {% endfor %} diff --git a/ietf/templates/submit/add_preapproval.html b/ietf/templates/submit/add_preapproval.html index 99ace1c12..cf17904c4 100644 --- a/ietf/templates/submit/add_preapproval.html +++ b/ietf/templates/submit/add_preapproval.html @@ -18,8 +18,8 @@ form .actions a { display: inline-block; margin-right: 1em; }

    Add Pre-Approval

    -

    You can register a pre-approved draft name. Then the WG Chair -approval step of WG -00 submissions is suspended for that draft name +

    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.

    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.

    like .txt in the name.

    {% if user|has_role:"Secretariat" %} -

    Only WG submissions are subject to approval and are thus pre-approvable.

    +

    Only group submissions are subject to approval and are thus pre-approvable.

    {% else %} -

    As WG Chair{% if groups|length > 1 %} of {{ groups|length }} groups{% endif %} you can pre-approve draft names on the form: +

    As chair{% if groups|length > 1 %} of {{ groups|length }} groups{% endif %} you can pre-approve draft names on the form (click to pre-fill): {% for g in groups %} - + {% endfor %}
    draft-ietf-{{ g.acronym }}-something
    draft-{% if g.type_id == "rg"%}irtf{% else %}ietf{% endif %}-{{ g.acronym }}-something

    @@ -56,8 +56,8 @@ like .txt in the name.

    - Back - + Cancel + @@ -68,7 +68,8 @@ like .txt in the name.

    {% 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 %} diff --git a/ietf/templates/submit/approvals.html b/ietf/templates/submit/approvals.html index 9f3f2bc68..6a270488d 100644 --- a/ietf/templates/submit/approvals.html +++ b/ietf/templates/submit/approvals.html @@ -37,7 +37,7 @@ table.preapprovals tr:hover td a.cancel { visibility: visible; }

    Pre-approved drafts not yet submitted

    -{% if user|has_role:"Secretariat,WG Chair" %} +{% if user|has_role:"Secretariat,WG Chair,RG Chair" %}

    You can add a pre-approval.

    {% endif %} diff --git a/ietf/templates/submit/cancel_preapproval.html b/ietf/templates/submit/cancel_preapproval.html index 8d70e3e66..901f3c390 100644 --- a/ietf/templates/submit/cancel_preapproval.html +++ b/ietf/templates/submit/cancel_preapproval.html @@ -15,9 +15,9 @@ Pre-approval of {{ preapproval.name }} by {{ preapproval.by }} on {{ preapproval.time }}.
    {% csrf_token %} - Back + Don't cancel - +
    {% endblock %} diff --git a/ietf/templates/wginfo/1wg-charters-by-acronym.txt b/ietf/templates/wginfo/1wg-charters-by-acronym.txt deleted file mode 100644 index bad8939cd..000000000 --- a/ietf/templates/wginfo/1wg-charters-by-acronym.txt +++ /dev/null @@ -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 %} diff --git a/ietf/templates/wginfo/chartering_wgs.html b/ietf/templates/wginfo/chartering_wgs.html deleted file mode 100644 index 00daeb90f..000000000 --- a/ietf/templates/wginfo/chartering_wgs.html +++ /dev/null @@ -1,58 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Chartering or Re-Chartering Working Groups{% endblock %} - -{% block content %} -{% load ietf_filters %} -{% load ballot_icon %} - -

    Chartering or Re-Chartering Working Groups

    - -

    Groups with a charter in state -{% for s in charter_states %}{% if not forloop.first %}, {% if forloop.last %}or {% endif %}{% endif %}{{ s.name }}{% endfor %}.

    - -{% if user|has_role:"Area Director,Secretariat" %} -

    Start chartering new WG

    -{% endif %} - -{% if not groups %} -

    No groups found.

    -{% else %} - - - - - - - -{% for g in groups %} - - - - - - - -{% endfor %} -
    GroupCharterDateStatus
    - {{ g.acronym }} - - {{ g.name }} - {{ g.charter.time|date:"Y-m-d" }} - {{ 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 %}
    IESG Telechat: {{ g.charter.telechat_date|date:"Y-m-d" }}{% endif %} -
    - {% ballot_icon g.charter %} -
    -{% endif %} - -{% endblock %} - -{% block js %} - - -{% endblock %} diff --git a/ietf/urls.py b/ietf/urls.py index b462e1e61..586e90abc 100644 --- a/ietf/urls.py +++ b/ietf/urls.py @@ -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(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')), diff --git a/ietf/wginfo/.gitignore b/ietf/wginfo/.gitignore deleted file mode 100644 index a74b07aee..000000000 --- a/ietf/wginfo/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/*.pyc diff --git a/ietf/wginfo/__init__.py b/ietf/wginfo/__init__.py deleted file mode 100644 index 5ed40b97d..000000000 --- a/ietf/wginfo/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Copyright The IETF Trust 2008, All Rights Reserved - diff --git a/ietf/wginfo/models.py b/ietf/wginfo/models.py deleted file mode 100644 index 5ed40b97d..000000000 --- a/ietf/wginfo/models.py +++ /dev/null @@ -1,2 +0,0 @@ -# Copyright The IETF Trust 2008, All Rights Reserved - diff --git a/ietf/wginfo/urls.py b/ietf/wginfo/urls.py deleted file mode 100644 index fb749c460..000000000 --- a/ietf/wginfo/urls.py +++ /dev/null @@ -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[a-zA-Z0-9-]+)/documents/txt/$', views.group_documents_txt), - (r'^(?P[a-zA-Z0-9-]+)/$', views.group_documents, None, "wg_docs"), - (r'^(?P[a-zA-Z0-9-]+)/charter/$', views.group_charter, None, 'group_charter'), - (r'^(?P[a-zA-Z0-9-]+)/init-charter/', edit.submit_initial_charter, None, "wg_init_charter"), - (r'^(?P[a-zA-Z0-9-]+)/history/$', views.history), - (r'^(?P[a-zA-Z0-9-]+)/deps/dot/$', views.dependencies_dot), - (r'^(?P[a-zA-Z0-9-]+)/deps/pdf/$', views.dependencies_pdf), - (r'^(?P[a-zA-Z0-9-]+)/edit/$', edit.edit, {'action': "edit"}, "group_edit"), - (r'^(?P[a-zA-Z0-9-]+)/conclude/$', edit.conclude, None, "wg_conclude"), - (r'^(?P[a-zA-Z0-9-]+)/milestones/$', milestones.edit_milestones, {'milestone_set': "current"}, "wg_edit_milestones"), - (r'^(?P[a-zA-Z0-9-]+)/milestones/charter/$', milestones.edit_milestones, {'milestone_set': "charter"}, "wg_edit_charter_milestones"), - (r'^(?P[a-zA-Z0-9-]+)/milestones/charter/reset/$', milestones.reset_charter_milestones, None, "wg_reset_charter_milestones"), - (r'^(?P[a-zA-Z0-9-]+)/ajax/searchdocs/$', milestones.ajax_search_docs, None, "wg_ajax_search_docs"), - (r'^(?P[a-zA-Z0-9-]+)/workflow/$', edit.customize_workflow), -)