From f025993f504741cc6d69db3d7d9bcac2b45a7aaf Mon Sep 17 00:00:00 2001 From: Martin Qvist Date: Thu, 15 Sep 2011 12:28:08 +0000 Subject: [PATCH] Bug fixes, changes based on feedback from RjS. Abandon, recharter and initial charter views. Forward snapshots for abandoned efforts. - Legacy-Id: 3421 --- ietf/idrfc/views_edit.py | 236 +----------------- ietf/templates/wgrecord/change_state.html | 59 ++++- ietf/templates/wgrecord/edit_info.html | 4 +- ietf/templates/wgrecord/record_tab_base.html | 16 +- .../wgrecord/record_tab_charter.html | 2 +- ietf/templates/wgrecord/search_form.html | 20 +- ietf/templates/wgrecord/status_columns.html | 4 +- ietf/wginfo/urls.py | 6 +- ietf/wgrecord/mails.py | 1 + ietf/wgrecord/urls.py | 1 + ietf/wgrecord/views_ballot.py | 2 +- ietf/wgrecord/views_edit.py | 86 ++++--- ietf/wgrecord/views_rec.py | 15 +- ietf/wgrecord/views_search.py | 36 +-- redesign/doc/models.py | 5 + static/js/wg-change-state.js | 14 -- 16 files changed, 191 insertions(+), 316 deletions(-) diff --git a/ietf/idrfc/views_edit.py b/ietf/idrfc/views_edit.py index 4eacacf6f..ec8e8822e 100644 --- a/ietf/idrfc/views_edit.py +++ b/ietf/idrfc/views_edit.py @@ -154,75 +154,6 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES: def dehtmlify_textarea_text(s): return s.replace("
", "\n").replace("", "").replace("", "").replace(" ", " ") -class EditInfoForm(forms.Form): - intended_status = forms.ModelChoiceField(IDIntendedStatus.objects.all(), empty_label=None, required=True) - status_date = forms.DateField(required=False, help_text="Format is YYYY-MM-DD") - area_acronym = forms.ModelChoiceField(Area.active_areas(), required=True, empty_label='None Selected') - via_rfc_editor = forms.BooleanField(required=False, label="Via IRTF or RFC Editor") - job_owner = forms.ModelChoiceField(IESGLogin.objects.filter(user_level__in=(IESGLogin.AD_LEVEL, IESGLogin.INACTIVE_AD_LEVEL)).order_by('user_level', 'last_name'), label="Responsible AD", empty_label=None, required=True) - create_in_state = forms.ModelChoiceField(IDState.objects.filter(document_state_id__in=(IDState.PUBLICATION_REQUESTED, IDState.AD_WATCHING)), empty_label=None, required=True) - state_change_notice_to = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas", required=False) - note = forms.CharField(widget=forms.Textarea, label="IESG note", required=False) - telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False) - returning_item = forms.BooleanField(required=False) - - def __init__(self, *args, **kwargs): - old_ads = kwargs.pop('old_ads') - - super(self.__class__, self).__init__(*args, **kwargs) - - job_owners = IESGLogin.objects.in_bulk([t[0] for t in self.fields['job_owner'].choices]) - choices = [("","None Selected"), ] - if old_ads: - # separate active ADs from inactive - separated = False - for t in self.fields['job_owner'].choices: - if job_owners[t[0]].user_level != IESGLogin.AD_LEVEL and not separated: - choices.append(("", "----------------")) - separated = True - choices.append(t) - self.fields['job_owner'].choices = choices - else: - # remove old ones - for t in self.fields['job_owner'].choices: - if job_owners[t[0]].user_level==IESGLogin.AD_LEVEL: - choices.append(t) - self.fields['job_owner'].choices = choices - - # telechat choices - dates = TelechatDates.objects.all()[0].dates() - init = kwargs['initial']['telechat_date'] - if init and init not in dates: - dates.insert(0, init) - - choices = [("", "(not on agenda)")] - for d in dates: - choices.append((d, d.strftime("%Y-%m-%d"))) - - self.fields['telechat_date'].choices = choices - -# if kwargs['initial']['area_acronym'] == Acronym.INDIVIDUAL_SUBMITTER: -# # default to "gen" -# kwargs['initial']['area_acronym'] = 1008 - - # returning item is rendered non-standard - self.standard_fields = [x for x in self.visible_fields() if x.name not in ('returning_item',)] - - def clean_status_date(self): - d = self.cleaned_data['status_date'] - if d: - if d < date.today(): - raise forms.ValidationError("Date must not be in the past.") - if d >= date.today() + timedelta(days=365 * 2): - raise forms.ValidationError("Date must be within two years.") - - return d - - def clean_note(self): - # note is stored munged in the database - return self.cleaned_data['note'].replace('\n', '
').replace('\r', '').replace(' ', '  ') - - def get_initial_state_change_notice(doc): # set change state notice to something sensible receivers = [] @@ -245,144 +176,11 @@ def get_initial_state_change_notice(doc): def get_new_ballot_id(): return IDInternal.objects.aggregate(Max('ballot'))['ballot__max'] + 1 -@group_required('Area_Director','Secretariat') -def edit_info(request, name): - """Edit various Internet Draft attributes, notifying parties as - necessary and logging changes as document comments.""" - doc = get_object_or_404(InternetDraft, filename=name) - if doc.status.status == "Expired": - raise Http404() - - login = IESGLogin.objects.get(login_name=request.user.username) - - new_document = False - if not doc.idinternal: - new_document = True - doc.idinternal = IDInternal(draft=doc, - rfc_flag=type(doc) == Rfc, - cur_state_id=IDState.PUBLICATION_REQUESTED, - prev_state_id=IDState.PUBLICATION_REQUESTED, - state_change_notice_to=get_initial_state_change_notice(doc), - primary_flag=1, - area_acronym_id=Acronym.INDIVIDUAL_SUBMITTER, - # would be better to use NULL to - # signify an empty ballot - ballot_id=get_new_ballot_id(), - via_rfc_editor = False, - ) - - if doc.idinternal.agenda: - initial_telechat_date = doc.idinternal.telechat_date - else: - initial_telechat_date = None - - if request.method == 'POST': - form = EditInfoForm(request.POST, - old_ads=False, - initial=dict(telechat_date=initial_telechat_date, - area_acronym=doc.idinternal.area_acronym_id)) - if form.is_valid(): - changes = [] - r = form.cleaned_data - entry = "%s has been changed to %s from %s" - if new_document: - doc.idinternal.cur_state_id=r['create_in_state'].document_state_id - doc.idinternal.prev_state_id=r['create_in_state'].document_state_id - # Django barfs in the diff below because these fields - # can't be NULL - doc.idinternal.job_owner = r['job_owner'] - if 'area_acronym' in r: - doc.idinternal.area_acronym = r['area_acronym'] - - replaces = doc.replaces_set.all() - if replaces and replaces[0].idinternal: - c = "Earlier history may be found in the Comment Log for %s" % (replaces[0], replaces[0].idinternal.get_absolute_url()) - add_document_comment(request, doc, c) - - orig_job_owner = doc.idinternal.job_owner - - # update the attributes, keeping track of what we're doing - - # coalesce some of the changes into one comment, mail them below - def diff(obj, attr, name): - v = getattr(obj, attr) - if r[attr] != v: - changes.append(entry % (name, r[attr], v)) - setattr(obj, attr, r[attr]) - - diff(doc, 'intended_status', "Intended Status") - diff(doc.idinternal, 'status_date', "Status date") - if 'area_acronym' in r and r['area_acronym']: - diff(doc.idinternal, 'area_acronym', 'Area acronym') - diff(doc.idinternal, 'job_owner', 'Responsible AD') - diff(doc.idinternal, 'state_change_notice_to', "State Change Notice email list") - - if changes and not new_document: - add_document_comment(request, doc, "
".join(changes)) - - # handle note (for some reason the old Perl code didn't - # include that in the changes) - if r['note'] != doc.idinternal.note: - if not r['note']: - if doc.idinternal.note: - add_document_comment(request, doc, "Note field has been cleared") - else: - if doc.idinternal.note: - add_document_comment(request, doc, "[Note]: changed to '%s'" % r['note']) - else: - add_document_comment(request, doc, "[Note]: '%s' added" % r['note']) - - doc.idinternal.note = r['note'] - - update_telechat(request, doc.idinternal, - r['telechat_date'], r['returning_item']) - - if in_group(request.user, 'Secretariat'): - doc.idinternal.via_rfc_editor = bool(r['via_rfc_editor']) - - doc.idinternal.email_display = str(doc.idinternal.job_owner) - doc.idinternal.token_name = str(doc.idinternal.job_owner) - doc.idinternal.token_email = doc.idinternal.job_owner.person.email()[1] - doc.idinternal.mark_by = login - doc.idinternal.event_date = date.today() - - if changes and not new_document: - email_owner(request, doc, orig_job_owner, login, "\n".join(changes)) - if new_document: - add_document_comment(request, doc, "Draft added in state %s" % doc.idinternal.cur_state.state) - - doc.idinternal.save() - doc.save() - return HttpResponseRedirect(doc.idinternal.get_absolute_url()) - else: - init = dict(intended_status=doc.intended_status_id, - status_date=doc.idinternal.status_date, - area_acronym=doc.idinternal.area_acronym_id, - job_owner=doc.idinternal.job_owner_id, - state_change_notice_to=doc.idinternal.state_change_notice_to, - note=dehtmlify_textarea_text(doc.idinternal.note), - telechat_date=initial_telechat_date, - returning_item=doc.idinternal.returning_item, - ) - - form = EditInfoForm(old_ads=False, initial=init) - - if not in_group(request.user, 'Secretariat'): - form.standard_fields = [x for x in form.standard_fields if x.name != "via_rfc_editor"] - - - return render_to_response('idrfc/edit_info.html', - dict(doc=doc, - form=form, - user=request.user, - login=login), - context_instance=RequestContext(request)) - class EditInfoFormREDESIGN(forms.Form): intended_std_level = forms.ModelChoiceField(IntendedStdLevelName.objects.all(), empty_label=None, required=True) status_date = forms.DateField(required=False, help_text="Format is YYYY-MM-DD") via_rfc_editor = forms.BooleanField(required=False, label="Via IRTF or RFC Editor") - ad = forms.ModelChoiceField(Person.objects.filter(email__role__name__in=("ad", "ex-ad")).order_by('email__role__name', 'name'), label="Responsible AD", empty_label=None, required=True) + ad = forms.ModelChoiceField(Person.objects.filter(email__role__name="ad", email__role__group__state="active").order_by('name'), label="Responsible AD", empty_label=None, required=True) create_in_state = forms.ModelChoiceField(IesgDocStateName.objects.filter(slug__in=("pub-req", "watching")), empty_label=None, required=True) notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas", required=False) note = forms.CharField(widget=forms.Textarea, label="IESG note", required=False) @@ -390,22 +188,13 @@ class EditInfoFormREDESIGN(forms.Form): returning_item = forms.BooleanField(required=False) def __init__(self, *args, **kwargs): - old_ads = kwargs.pop('old_ads') - super(self.__class__, self).__init__(*args, **kwargs) - # fix up ad field + # if previous AD is now ex-AD, append that person to the list + ad_pk = self.initial.get('ad') choices = self.fields['ad'].choices - ex_ads = dict((e.pk, e) for e in Person.objects.filter(email__role__name="ex-ad").distinct()) - if old_ads: - # separate active ADs from inactive - for i, t in enumerate(choices): - if t[0] in ex_ads: - choices.insert(i, ("", "----------------")) - break - else: - # remove old ones - self.fields['ad'].choices = [t for t in choices if t[0] not in ex_ads] + if ad_pk and ad_pk not in [pk for pk, name in choices]: + self.fields['ad'].choices = list(choices) + [("", "-------"), (ad_pk, Person.objects.get(pk=ad_pk).name)] # telechat choices dates = TelechatDates.objects.all()[0].dates() @@ -473,8 +262,8 @@ def edit_infoREDESIGN(request, name): if request.method == 'POST': form = EditInfoForm(request.POST, - old_ads=False, - initial=dict(telechat_date=initial_telechat_date)) + initial=dict(ad=doc.ad_id, + telechat_date=initial_telechat_date)) if form.is_valid(): save_document_in_history(doc) @@ -539,6 +328,7 @@ def edit_infoREDESIGN(request, name): for c in changes: e = DocEvent(doc=doc, by=login) + e.desc = c e.type = "changed_document" e.save() @@ -550,8 +340,8 @@ def edit_infoREDESIGN(request, name): if r["status_date"] != status_date: e = StatusDateDocEvent(doc=doc, by=login) e.type ="changed_status_date" - d = desc("Status date", r["status_date"], status_date) - changes.append(d) + e.desc = desc("Status date", r["status_date"], status_date) + changes.append(e.desc) e.date = r["status_date"] e.save() @@ -572,16 +362,16 @@ def edit_infoREDESIGN(request, name): else: e = doc.latest_event(StatusDateDocEvent) status = e.date if e else None - init = dict(intended_std_level=doc.intended_std_level, + init = dict(intended_std_level=doc.intended_std_level_id, status_date=status, - ad=doc.ad, + ad=doc.ad_id, notify=doc.notify, note=dehtmlify_textarea_text(doc.note), telechat_date=initial_telechat_date, returning_item=initial_returning_item, ) - form = EditInfoForm(old_ads=False, initial=init) + form = EditInfoForm(initial=init) if not has_role(request.user, 'Secretariat'): # filter out Via RFC Editor diff --git a/ietf/templates/wgrecord/change_state.html b/ietf/templates/wgrecord/change_state.html index 5992b32d0..04ac30c66 100644 --- a/ietf/templates/wgrecord/change_state.html +++ b/ietf/templates/wgrecord/change_state.html @@ -1,6 +1,20 @@ {% extends "base.html" %} -{% block title %}Change state of WG {{ wg.acronym }}{% endblock %} +{% block title %} +{% ifequal option "initcharter" %} +Initiate chartering of WG {{ wg.acronym }} +{% else %} +{% ifequal option "recharter" %} +Recharter WG {{ wg.acronym }} +{% else %} +{% ifequal option "abandon" %} +Abandon effort on WG {{ wg.acronym }} +{% else %} +Change state of WG {{ wg.acronym }} +{% endifequal %} +{% endifequal %} +{% endifequal %} +{% endblock %} {% block morecss %} form.change-state select { @@ -19,22 +33,49 @@ form.change-state .actions { {% endblock %} {% block content %} -

Change state of {{ wg.acronym }}

+

{% ifequal option "initcharter" %} +Initiate chartering of WG {{ wg.acronym }} +{% else %} +{% ifequal option "recharter" %} +Recharter WG {{ wg.acronym }} +{% else %} +{% ifequal option "abandon" %} +Abandon effort on WG {{ wg.acronym }} +{% else %} +Change state of WG {{ wg.acronym }} +{% endifequal %} +{% endifequal %} +{% endifequal %} +

-

For help on the states, see the state table.

+{% ifnotequal option "initcharter" %}{% ifnotequal option "recharter" %}{% ifnotequal option "abandon" %}

For help on the states, see the state table.

{% endifnotequal %}{% endifnotequal %}{% endifnotequal %}
{% for field in form.visible_fields %} - - + + +
{{ field.label_tag }}:{{ field }} + {% ifequal field.name "initial_time" %} + {% ifequal option "recharter" %} + {{ field.label_tag }}:{{ field }} + {% if field.help_text %}
{{ field.help_text }}
{% endif %} + {% else %} + {% ifequal option "initcharter" %} +
{{ field.label_tag }}:{{ field }} + {% if field.help_text %}
{{ field.help_text }}
{% endif %} + {% endifequal %} + {% endifequal %} + {% else %} +
{{ field.label_tag }}:{{ field }} + {% if field.help_text %}
{{ field.help_text }}
{% endif %} + {% endifequal %} {% ifequal field.name "charter_state" %} {% ifequal field.errors "warning" %} -
  • The initial review time hasn't elapsed. Proceed anyway?
+
  • The initial review time hasn't elapsed. Select this checkbox to proceed anyway:
{% endifequal %} {% endifequal %} - {% if field.help_text %}
{{ field.help_text }}
{% endif %} {% ifnotequal field.name "charter_state" %} {{ field.errors }} {% endifnotequal %} @@ -43,8 +84,12 @@ form.change-state .actions { {% endfor %}
+ {% if option %} + + {% else %} Back + {% endif %}
diff --git a/ietf/templates/wgrecord/edit_info.html b/ietf/templates/wgrecord/edit_info.html index d8eb18b91..876d65b45 100644 --- a/ietf/templates/wgrecord/edit_info.html +++ b/ietf/templates/wgrecord/edit_info.html @@ -69,8 +69,10 @@ Create WG record {% if wg %} Back + + {% else %} + {% endif %} - diff --git a/ietf/templates/wgrecord/record_tab_base.html b/ietf/templates/wgrecord/record_tab_base.html index 018c5010c..c3c90eb11 100644 --- a/ietf/templates/wgrecord/record_tab_base.html +++ b/ietf/templates/wgrecord/record_tab_base.html @@ -21,7 +21,19 @@ Copyright The IETF Trust 2011, All Rights Reserved {% if user|in_group:"Area_Director,Secretariat" %} {% if not snapshot %}
+{% ifnotequal wg.charter.charter_state_id "notrev" %} +{% ifnotequal wg.charter.charter_state_id "approved" %} +Abandon effort Change state +{% ifnotequal wg.state_id "conclude" %} +Edit charter +{% endifnotequal %} +{% else %} +Recharter +{% endifnotequal %} +{% else %} +Recharter +{% endifnotequal %} {% ifequal wg.state_id "active" %}{% ifequal wg.charter.charter_state_id "approved" %} Conclude WG @@ -31,9 +43,7 @@ Copyright The IETF Trust 2011, All Rights Reserved Edit WG {% endifnotequal %} -{% ifnotequal wg.state_id "conclude" %} -Edit charter -{% endifnotequal %} +
{% endif %}{# if not snapshot #} {% endif %}{# if user in group #} diff --git a/ietf/templates/wgrecord/record_tab_charter.html b/ietf/templates/wgrecord/record_tab_charter.html index 14c9f9be1..71acf0bed 100644 --- a/ietf/templates/wgrecord/record_tab_charter.html +++ b/ietf/templates/wgrecord/record_tab_charter.html @@ -7,7 +7,7 @@ Copyright The IETF Trust 2011, All Rights Reserved {% load ietf_filters %} {% block record_revision %} -Snapshots: {% if not snapshot %}{% else %}{% endif %}current{% if not snapshot %}{% else %}{% endif %} {% for d in versions reversed %}{% ifnotequal d.rev wg.charter.rev %}{% ifequal snapshot d.rev %}{% else %}{% endifequal %}{{ d.rev }}{% ifequal snapshot d.rev %}{% else %}{% endifequal %} {% endifnotequal %}{% endfor %} +Snapshots: {% for d in versions reversed %}{% ifequal active_rev d.rev %}{% endifequal %}{% ifequal rev d.rev %}{% else %}{% ifequal active_rev d.rev %}{% else %}{% endifequal %}{% endifequal %}{{ d.rev }}{% ifequal rev d.rev %}{% else %}{% endifequal %}{% ifequal active_rev d.rev %}{% endifequal %} {% endfor %} {% endblock %} {% block record_metatable %} diff --git a/ietf/templates/wgrecord/search_form.html b/ietf/templates/wgrecord/search_form.html index d0be186ab..d9f06c2ba 100644 --- a/ietf/templates/wgrecord/search_form.html +++ b/ietf/templates/wgrecord/search_form.html @@ -5,7 +5,14 @@ Copyright The IETF Trust 2011, All Rights Reserved
- {{ form.name }} + {{ form.nameacronym }} +
+
+ + + + +
{{ form.inprocess }} WGs (in chartering process)
{{ form.active }} WGs (approved charter)
Advanced @@ -13,9 +20,6 @@ Copyright The IETF Trust 2011, All Rights Reserved
Additional search criteria: -
- {{ form.acronym }} -
{{ form.state }} :: {{ form.charter_state }}
@@ -49,8 +53,9 @@ function toggleSubmit() { var button = document.getElementById("id_search_submit"); var by = findCheckedSearchBy(); var value = findSearchByValue(by); - var text = document.getElementById("id_name"); - if ((value == "") && (text.value == "")) { + var active = document.getElementById("id_active"); + var text = document.getElementById("id_nameacronym"); + if ((value == "") && (text.value == "" && active.checked)) { button.disabled = true; } else { button.disabled = false; @@ -90,7 +95,6 @@ function findCheckedSearchBy() { } function findSearchByValue(by) { - if (by == 'acronym') { return document.getElementById("id_acronym").value; } if (by == 'state') { // state might be wg state... state_value = document.getElementById("id_state").value; @@ -108,13 +112,11 @@ function findSearchByValue(by) { function changeBy() { var by=findCheckedSearchBy(); var f = document.search_form; - f.acronym.disabled=true; f.state.disabled=true; f.charter_state.disabled=true; f.ad.disabled=true; f.area.disabled=true; f.anyfield.disabled=true; f.eacronym.disabled=true; - if (by=='acronym') { f.acronym.disabled=false;} if (by=='state') { f.state.disabled=false; f.charter_state.disabled=false;} if (by=='ad') { f.ad.disabled=false; } if (by=='area') { f.area.disabled=false;} diff --git a/ietf/templates/wgrecord/status_columns.html b/ietf/templates/wgrecord/status_columns.html index 84e8d3095..ee2a9b9b3 100644 --- a/ietf/templates/wgrecord/status_columns.html +++ b/ietf/templates/wgrecord/status_columns.html @@ -4,11 +4,11 @@ Copyright The IETF Trust 2011, All Rights Reserved {% load ietf_filters ietf_streams %}{% load wg_ballot_icon %} {% if wg.charter %} -{{ wg.charter.charter_state|safe }} +{{ wg.charter.charter_state|safe }} {% ifequal wg.state_id "proposed" %}{% ifnotequal wg.charter.charter_state_id "notrev" %}(Initial Chartering){% endifnotequal %}{% else %}{% ifnotequal wg.charter.charter_state_id "approved" %}(Rechartering){% endifnotequal %}{% endifequal %} {% else %} (data missing) {% endif %} -{% if not hide_telechat_date %}{% if doc.telechat_date %}
IESG Telechat: {{ doc.telechat_date }}{% endif %}{% endif %} +{% if wg.charter.telechat_date %}
IESG Telechat: {{ wg.charter.telechat_date }}{% endif %} {% block extra_status %}{% endblock %} diff --git a/ietf/wginfo/urls.py b/ietf/wginfo/urls.py index 65e022e94..ce0663c26 100644 --- a/ietf/wginfo/urls.py +++ b/ietf/wginfo/urls.py @@ -13,8 +13,8 @@ urlpatterns = patterns('', (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'^(?P[a-z0-9-]+)/documents/txt/$', views.wg_documents_txt), - (r'^(?P[a-z0-9-]+)/$', views.wg_documents_html), - (r'^(?P[a-z0-9-]+)/charter/$', views.wg_charter), + (r'^(?P[a-zA-Z0-9-]+)/documents/txt/$', views.wg_documents_txt), + (r'^(?P[a-zA-Z0-9-]+)/$', views.wg_documents_html), + (r'^(?P[a-zA-Z0-9-]+)/charter/$', views.wg_charter), (r'^(?P[^/]+)/management/', include('ietf.wgchairs.urls')), ) diff --git a/ietf/wgrecord/mails.py b/ietf/wgrecord/mails.py index 03da1318b..18cc1fa28 100644 --- a/ietf/wgrecord/mails.py +++ b/ietf/wgrecord/mails.py @@ -17,6 +17,7 @@ from redesign.person.models import Person # These become part of the subject of the email types = {} types['state'] = "State changed" +types['state-notrev'] = "State changed to Not currently under review" types['state-infrev'] = "State changed to Informal review" types['state-intrev'] = "State changed to Internal review" types['state-extrev'] = "State changed to External review" diff --git a/ietf/wgrecord/urls.py b/ietf/wgrecord/urls.py index 3be2f76f2..c22fbe1d6 100644 --- a/ietf/wgrecord/urls.py +++ b/ietf/wgrecord/urls.py @@ -17,6 +17,7 @@ urlpatterns += patterns('', url(r'^(?P[A-Za-z0-9._+-]+)/((?P[0-9][0-9](-[0-9][0-9])?)/)?((?Pballot|writeup|history)/)?$', views_rec.wg_main, name="wg_view_record"), (r'^(?P[A-Za-z0-9._+-]+)/_ballot.data$', views_rec.wg_ballot), url(r'^(?P[A-Za-z0-9._+-]+)/edit/state/$', views_edit.change_state, name='wg_change_state'), + url(r'^(?P[A-Za-z0-9._+-]+)/edit/(?P