Merged in fixes from Ole, from branch/charter:r4382-4407, and some changes from Henrik (removing the use of pos.blocked in views_ballot.py, and setting a ballot id in idrfc/testsREDESIGN.py) to make idrfc.EditPositionTestCase succeed.

- Legacy-Id: 4444
This commit is contained in:
Henrik Levkowetz 2012-06-10 16:22:16 +00:00
commit afe3ee71b8
18 changed files with 329 additions and 108 deletions

View file

@ -86,6 +86,7 @@ GROUP_EVENT_CHOICES = [
("changed_state", "Changed state"),
("added_comment", "Added comment"),
("info_changed", "Changed metadata"),
("requested_close", "Requested closing group"),
]
class GroupEvent(models.Model):

View file

@ -33,6 +33,7 @@
from ietf.idtracker.models import InternetDraft, IDInternal, BallotInfo, IESGDiscuss, IESGLogin, DocumentComment, Acronym, IDState
from ietf.idrfc.models import RfcEditorQueue
from ietf.ipr.models import IprRfc, IprDraft, IprDetail
from ietf.doc.models import BallotDocEvent
import re
from datetime import date

View file

@ -40,6 +40,7 @@ from django.core.urlresolvers import reverse as urlreverse
from django.conf import settings
from pyquery import PyQuery
import debug
from ietf.doc.models import *
from ietf.name.models import *
@ -505,7 +506,8 @@ class EditPositionTestCase(django.test.TestCase):
def test_cannot_edit_position_as_pre_ad(self):
draft = make_test_data()
url = urlreverse('doc_edit_position', kwargs=dict(name=draft.name))
url = urlreverse('doc_edit_position', kwargs=dict(name=draft.name,
ballot_id=draft.latest_event(BallotDocEvent, type="created_ballot").pk))
# transform to pre-ad
ad_role = Role.objects.filter(name="ad")[0]

View file

@ -13,6 +13,8 @@ from django.utils.html import strip_tags
from django.utils import simplejson
from django.conf import settings
import debug
from ietf.utils.mail import send_mail_text, send_mail_preformatted
from ietf.ietfauth.decorators import group_required, role_required
from ietf.idtracker.templatetags.ietf_filters import in_group
@ -274,8 +276,10 @@ def edit_positionREDESIGN(request, name, ballot_id):
pos.comment = clean["comment"].rstrip()
pos.comment_time = old_pos.comment_time if old_pos else None
pos.discuss = clean["discuss"].rstrip()
if not pos.pos.blocking:
if not pos.pos_id == "discuss":
pos.discuss = ""
# if not pos.pos.blocking:
# pos.discuss = ""
pos.discuss_time = old_pos.discuss_time if old_pos else None
changes = []
@ -350,7 +354,6 @@ def edit_positionREDESIGN(request, name, ballot_id):
form = EditPositionForm(initial=initial, ballot_type=ballot.ballot_type)
blocking_positions = dict((p.pk, p.name) for p in form.fields["position"].queryset.all() if p.blocking)
print blocking_positions, form.fields["position"].queryset.all()
ballot_deferred = None
if doc.get_state_slug("%s-iesg" % doc.type_id) == "defer":
@ -363,7 +366,7 @@ def edit_positionREDESIGN(request, name, ballot_id):
return_to_url=return_to_url,
old_pos=old_pos,
ballot_deferred=ballot_deferred,
show_discuss_text=old_pos and old_pos.pos.blocking,
show_discuss_text=old_pos and old_pos.pos_id=="discuss",
blocking_positions=simplejson.dumps(blocking_positions),
),
context_instance=RequestContext(request))
@ -476,7 +479,7 @@ def send_ballot_commentREDESIGN(request, name, ballot_id):
subj = []
d = ""
blocking_name = "DISCUSS"
if pos.pos.blocking and pos.discuss:
if pos.pos_id == "discuss" and pos.discuss:
d = pos.discuss
blocking_name = pos.pos.name.upper()
subj.append(blocking_name)

View file

@ -68,11 +68,14 @@ class SearchForm(forms.Form):
def clean(self):
q = self.cleaned_data
# Reset query['by'] if needed
for k in ('author','group','area','ad'):
if (q['by'] == k) and not q[k]:
if 'by' not in q:
q['by'] = None
else:
for k in ('author','group','area','ad'):
if (q['by'] == k) and not q[k]:
q['by'] = None
if (q['by'] == 'state') and not (q['state'] or q['subState']):
q['by'] = None
if (q['by'] == 'state') and not (q['state'] or q['subState']):
q['by'] = None
# Reset other fields
for k in ('author','group','area','ad'):
if q['by'] != k:
@ -296,11 +299,14 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
def clean(self):
q = self.cleaned_data
# Reset query['by'] if needed
for k in ('author','group','area','ad'):
if (q['by'] == k) and (k not in q or not q[k]):
if 'by' not in q:
q['by'] = None
else:
for k in ('author','group','area','ad'):
if (q['by'] == k) and (k not in q or not q[k]):
q['by'] = None
if (q['by'] == 'state') and (not 'state' in q or not 'subState' in q or not (q['state'] or q['subState'])):
q['by'] = None
if (q['by'] == 'state') and (not 'state' in q or not 'subState' in q or not (q['state'] or q['subState'])):
q['by'] = None
# Reset other fields
for k in ('author','group','area','ad'):
if q['by'] != k:

View file

@ -16,7 +16,7 @@ from ietf.utils.mail import outbox
from ietf.person.models import Person, Email
from ietf.group.models import Group, Role
from ietf.doc.models import Document, BallotPositionDocEvent
from ietf.doc.models import Document, BallotDocEvent, BallotPositionDocEvent
from ietf.submit.models import IdSubmissionDetail
class SubmitTestCase(django.test.TestCase):
@ -163,6 +163,7 @@ class SubmitTestCase(django.test.TestCase):
# make a discuss to see if the AD gets an email
ballot_position = BallotPositionDocEvent()
ballot_position.ballot = draft.latest_event(BallotDocEvent, type="created_ballot")
ballot_position.pos_id = "discuss"
ballot_position.type = "changed_ballot_position"
ballot_position.doc = draft
@ -234,7 +235,7 @@ class SubmitTestCase(django.test.TestCase):
self.assertTrue(name in unicode(outbox[-2]))
self.assertTrue("mars" in unicode(outbox[-2]))
self.assertTrue(draft.ad.role_email("ad").address in unicode(outbox[-2]))
self.assertTrue(ballot_position.ad.email_address() in unicode(outbox[-2]))
self.assertTrue(ballot_position.ad.role_email("ad").address in unicode(outbox[-2]))
self.assertTrue("New Version Notification" in outbox[-1]["Subject"])
self.assertTrue(name in unicode(outbox[-1]))
self.assertTrue("mars" in unicode(outbox[-1]))

View file

@ -53,9 +53,9 @@
{% else %}
{% if group.state_id == "proposed" or group.state_id == "bof" %}
- <a href="{% url charter_startstop_process name=doc.name option='initcharter' %}">Charter</a>
- <a href="{% url charter_submit name=doc.name option='initcharter' %}">Start chartering</a>
{% else %}
- <a href="{% url charter_startstop_process name=doc.name option='recharter' %}">Recharter</a>
- <a href="{% url charter_submit name=doc.name option='recharter' %}">Recharter</a>
{% endif %}
{% endif %}
@ -88,16 +88,24 @@
</tr>
{% endif %}
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
<tr>
<td>Send notices to:</td>
<td>{{ doc.notify|default:"none" }}
{% if user|has_role:"Area Director,Secretariat" %}
- <a href="{% url charter_edit_notify name=doc.name %}">Change</a>
{% endif %}
</td>
</tr>
<tr><td>Last updated:</td><td> {{ doc.time|date:"Y-m-d" }}</td></tr>
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
</table>
<div class="actions">
<a href="/feed/wgchanges/{{ group.acronym }}/">Atom feed</a>
<a href="/feed/group-changes/{{ group.acronym }}/">Atom feed</a>
</div>
</div>

View file

@ -20,7 +20,9 @@ form.change-state .actions {
{% block content %}
<h1>{{ title }}</h1>
{% if "Change state" in title %}<p class="helptext">For help on the states, see the <a href="{% url help_charter_states %}">state table</a>.</p>{% endif %}
{% if not option %}
<p class="helptext">For help on the states, see the <a href="{% url help_charter_states %}">state table</a>.</p>
{% endif %}
<form class="change-state" action="" method="post">
<table>
@ -60,9 +62,10 @@ form.change-state .actions {
{% endif %}
<tr>
<td colspan="2" class="actions">
{% if option %}
<input type="submit" value="Submit"/>
{% else %}
{% if option == "initcharter" or option == "recharter" %}
<input type="submit" value="Initiate chartering"/>
{% endif %}
{% if not option or option == "abandon" %}
<a href="{% url doc_view name=doc.name %}">Back</a>
<input type="submit" value="Save and (possibly) notify Secretariat"/>
{% endif %}

View file

@ -0,0 +1,32 @@
{% extends "base.html" %}
{% block title %}Set notification list for {{ doc.name }}{% endblock %}
{% block morecss %}
form.edit-notify td input#id_notify { width: 40em; }
form.edit-notify td.actions { padding-top: 1em; }
{% endblock %}
{% block content %}
{% load ietf_filters %}
<h1>Set notification list for {{ doc.name }}</h1>
<form class="edit-notify" action="" method="POST">
<table>
<tr>
<th>{{ form.notify.label_tag }}:</th>
<td>{{ form.notify }}
<div class="help">{{ form.notify.help_text }}</div>
{{ form.notify.errors }}
</td>
</tr>
<tr>
<td></td>
<td class="actions">
<a href="{% url doc_view name=doc.name %}">Back</a>
<input type="submit" value="Save"/>
</td>
</tr>
</table>
</form>
{% endblock %}

View file

@ -4,35 +4,20 @@
{% if wg %}
Edit WG {{ wg.acronym }}
{% else %}
Create WG
Start chartering new WG
{% endif %}
{% endblock %}
{% block morecss %}
form.edit-info #id_name {
width: 396px;
}
form.edit-info #id_list_email {
width: 396px;
}
form.edit-info #id_list_subscribe {
width: 396px;
}
form.edit-info #id_list_archive {
width: 396px;
}
form.edit-info #id_urls {
width: 400px;
}
form.edit-info #id_comments {
width: 400px;
}
form.edit #id_name,
form.edit #id_list_email,
form.edit #id_list_subscribe,
form.edit #id_list_archive,
form.edit #id_urls,
form.edit #id_comments { width: 400px; }
form.edit input[type=checkbox] { vertical-align: middle; }
ul.errorlist { border-width: 0px; padding: 0px; margin: 0px;}
ul.errorlist li { color: #a00; margin: 0px; padding: 0px; list-style: none; }
{% endblock %}
{% block pagehead %}
@ -44,11 +29,11 @@ form.edit-info #id_comments {
<h1>{% if wg %}
Edit WG {{ wg.acronym }}
{% else %}
Create WG
Start chartering new WG
{% endif %}
</h1>
<form class="edit-info" action="" method="POST">
<form class="edit" action="" method="POST">
<table>
{% for field in form.visible_fields %}
<tr>
@ -63,6 +48,14 @@ Create WG
{{ field.errors }}
</td>
</tr>
{% if field.name == "acronym" and form.confirm_msg %}
<tr>
<td></td>
<td><input name="confirmed" type="checkbox"{% if form.autoenable_confirm %} checked="checked"{% endif %}/>
{{ form.confirm_msg }}
</td>
</tr>
{% endif %}
{% endfor %}
<tr>
<td></td>
@ -71,7 +64,7 @@ Create WG
<a href="{% url wg_charter acronym=wg.acronym %}">Back</a>
<input type="submit" value="Save"/>
{% else %}
<input type="submit" value="Create"/>
<input type="submit" value="Start chartering"/>
{% endif %}
</td>
</tr>
@ -83,4 +76,15 @@ Create WG
<script type="text/javascript" src="/js/lib/jquery.tokeninput.js"></script>
<script type="text/javascript" src="/js/lib/json2.js"></script>
<script type="text/javascript" src="/js/emails-field.js"></script>
<script>
jQuery(function () {
if (jQuery('input[name="confirmed"]').length > 0) {
jQuery('input[name="acronym"]').change(function() {
// make sure we don't accidentally confirm another acronym
jQuery('input[name="confirmed"]').closest("tr").remove();
jQuery('input[name="acronym"]').siblings(".errorlist").remove();
});
}
});
</script>
{% endblock %}

View file

@ -55,7 +55,14 @@ is occasionally incorrect.</span>
<tr><td>Area:</td><td>{{ wg.parent.name }} ({{ wg.parent.acronym }})</td></tr>
{% endif %}
<tr><td>State:</td><td>{{ wg.state.name }}</td></tr>
<tr>
<td>State:</td>
<td>{{ wg.state.name }}
{% if requested_close %}
(but in the process of being closed)
{% endif %}
</td>
</tr>
<tr>
<td>Charter:</td>

View file

@ -37,6 +37,20 @@ def email_secretariat(request, wg, type, text):
)
)
def email_state_changed(request, doc, text):
to = [e.strip() for e in doc.notify.replace(';', ',').split(',')]
if not to:
return
text = strip_tags(text)
text += "\n\n"
text += "URL: %s" % (settings.IDTRACKER_BASE_URL + doc.get_absolute_url())
send_mail_text(request, to, None,
"State changed: %s-%s" % (doc.canonical_name(), doc.rev),
text)
def generate_ballot_writeup(request, doc):
e = WriteupDocEvent()
e.type = "changed_ballot_writeup_text"

View file

@ -116,6 +116,22 @@ class EditCharterTestCase(django.test.TestCase):
charter = Document.objects.get(name=charter.name)
self.assertTrue(not charter.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date)
def test_edit_notify(self):
make_test_data()
charter = Group.objects.get(acronym="mars").charter
url = urlreverse('charter_edit_notify', kwargs=dict(name=charter.name))
login_testing_unauthorized(self, "secretary", url)
# post
self.assertTrue(not charter.notify)
r = self.client.post(url, dict(notify="someone@example.com, someoneelse@example.com"))
self.assertEquals(r.status_code, 302)
charter = Document.objects.get(name=charter.name)
self.assertEquals(charter.notify, "someone@example.com, someoneelse@example.com")
def test_submit_charter(self):
make_test_data()

View file

@ -6,9 +6,10 @@ urlpatterns = patterns('',
url(r'^state/$', "ietf.wgcharter.views.change_state", name='charter_change_state'),
url(r'^(?P<option>initcharter|recharter|abandon)/$', "ietf.wgcharter.views.change_state", name='charter_startstop_process'),
url(r'^telechat/$', "ietf.wgcharter.views.telechat_date", name='charter_telechat_date'),
url(r'^notify/$', "ietf.wgcharter.views.edit_notify", name='charter_edit_notify'),
url(r'^(?P<ann>action|review)/$', "ietf.wgcharter.views.announcement_text"),
url(r'^ballotwriteupnotes/$', "ietf.wgcharter.views.ballot_writeupnotes"),
url(r'^approve/$', "ietf.wgcharter.views.approve", name='charter_approve'),
url(r'^submit/$', "ietf.wgcharter.views.submit", name='charter_submit'),
url(r'^submit/(?P<option>initcharter|recharter)/$', "ietf.wgcharter.views.submit", name='charter_submit'),
)

View file

@ -9,7 +9,7 @@ from django.template import RequestContext
from django import forms
from django.forms.util import ErrorList
from django.utils import simplejson
from django.utils.html import strip_tags
from django.utils.html import strip_tags, escape
from django.utils.safestring import mark_safe
from django.conf import settings
@ -47,8 +47,10 @@ def change_state(request, name, option=None):
charter = get_object_or_404(Document, type="charter", name=name)
wg = charter.group
chartering_type = get_chartering_type(charter)
initial_review = charter.latest_event(InitialReviewDocEvent, type="initial_review")
if charter.get_state_slug() != "infrev" or (initial_review and initial_review.expires < datetime.datetime.now()):
if charter.get_state_slug() != "infrev" or (initial_review and initial_review.expires < datetime.datetime.now()) or chartering_type == "rechartering":
initial_review = None
login = request.user.get_profile()
@ -112,6 +114,8 @@ def change_state(request, name, option=None):
if message:
email_secretariat(request, wg, "state-%s" % charter_state.slug, message)
email_state_changed(request, charter, "State changed to %s." % charter_state)
if charter_state.slug == "intrev":
if request.POST.get("ballot_wo_extern"):
create_ballot_if_not_open(charter, login, "r-wo-ext")
@ -128,19 +132,17 @@ def change_state(request, name, option=None):
e.desc = "Initial review time expires %s" % e.expires.strftime("%Y-%m-%d")
e.save()
if option in ("initcharter", "recharter"):
return redirect('charter_submit', name=charter.name)
return redirect('doc_view', name=charter.name)
else:
if option == "recharter":
hide = ['charter_state']
init = dict(initial_time=1, message='%s has initiated a recharter effort on the WG:\n "%s" (%s)' % (login.plain_name(), wg.name, wg.acronym))
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 WG:\n "%s" (%s)' % (login.plain_name(), wg.name, wg.acronym))
init = dict(initial_time=1, message='%s has initiated chartering of the proposed WG:\n "%s" (%s).' % (login.plain_name(), wg.name, wg.acronym))
elif option == "abandon":
hide = ['initial_time', 'charter_state']
init = dict(message='%s has abandoned the chartering effort on the WG: "%s" (%s)' % (login.plain_name(), wg.name, wg.acronym))
init = dict(message='%s has abandoned the chartering effort on the WG:\n "%s" (%s).' % (login.plain_name(), wg.name, wg.acronym))
else:
hide = ['initial_time']
s = charter.get_state()
@ -165,8 +167,8 @@ def change_state(request, name, option=None):
messages = {
state_pk("infrev"): 'The WG "%s" (%s) has been set to Informal IESG review by %s.' % (wg.name, wg.acronym, login.plain_name()),
state_pk("intrev"): 'The WG "%s" (%s) has been set to Internal review by %s. Please place it on the next IESG telechat and inform the IAB.' % (wg.name, wg.acronym, login.plain_name()),
state_pk("extrev"): 'The WG "%s" (%s) has been set to External review by %s. Please send out the external review announcement to the appropriate lists.\n\nSend the announcement to other SDOs: Yes\nAdditional recipients of the announcement: ' % (wg.name, wg.acronym, login.plain_name()),
state_pk("intrev"): 'The WG "%s" (%s) has been set to Internal review by %s.\nPlease place it on the next IESG telechat and inform the IAB.' % (wg.name, wg.acronym, login.plain_name()),
state_pk("extrev"): 'The WG "%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: ' % (wg.name, wg.acronym, login.plain_name()),
}
states_for_ballot_wo_extern = State.objects.filter(type="charter", slug="intrev").values_list("pk", flat=True)
@ -179,7 +181,7 @@ def change_state(request, name, option=None):
prev_charter_state=prev_charter_state,
title=title,
initial_review=initial_review,
chartering_type=get_chartering_type(charter),
chartering_type=chartering_type,
messages=simplejson.dumps(messages),
states_for_ballot_wo_extern=simplejson.dumps(list(states_for_ballot_wo_extern)),
),
@ -223,6 +225,49 @@ def telechat_date(request, name):
login=login),
context_instance=RequestContext(request))
class NotifyForm(forms.Form):
notify = forms.CharField(max_length=255, help_text="List of email addresses to receive state notifications, separated by comma", label="Notification list", required=False)
def clean_notify(self):
return self.cleaned_data["notify"].strip()
@role_required("Area Director", "Secretariat")
def edit_notify(request, name):
doc = get_object_or_404(Document, type="charter", name=name)
login = request.user.get_profile()
init = {'notify': doc.notify}
if request.method == "POST":
form = NotifyForm(request.POST, initial=init)
if form.is_valid():
n = form.cleaned_data["notify"]
if n != doc.notify:
save_document_in_history(doc)
e = DocEvent(doc=doc, by=login)
e.desc = "Notification list changed to %s" % (escape(n) or "none")
if doc.notify:
e.desc += " from %s" % escape(doc.notify)
e.type = "changed_document"
e.save()
doc.notify = n
doc.time = e.time
doc.save()
return redirect("doc_view", name=doc.name)
else:
form = NotifyForm(initial=init)
return render_to_response('wgcharter/edit_notify.html',
dict(doc=doc,
form=form,
user=request.user,
login=login),
context_instance=RequestContext(request))
class UploadForm(forms.Form):
content = forms.CharField(widget=forms.Textarea, label="Charter text", help_text="Edit the charter text", required=False)
txt = forms.FileField(label=".txt format", help_text="Or upload a .txt file", required=False)
@ -242,7 +287,7 @@ class UploadForm(forms.Form):
destination.write(self.cleaned_data['content'])
@role_required('Area Director','Secretariat')
def submit(request, name):
def submit(request, name, option=None):
charter = get_object_or_404(Document, type="charter", name=name)
wg = charter.group
@ -281,7 +326,10 @@ def submit(request, name):
charter.time = datetime.datetime.now()
charter.save()
return HttpResponseRedirect(reverse('doc_view', kwargs={'name': charter.name}))
if option:
return redirect('charter_startstop_process', name=charter.name, option=option)
else:
return redirect("doc_view", name=charter.name)
else:
init = { "content": ""}
c = charter

View file

@ -34,7 +34,9 @@ class WGForm(forms.Form):
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.cur_acronym = kwargs.pop('cur_acronym')
self.wg = kwargs.pop('wg', None)
self.confirmed = kwargs.pop('confirmed', False)
super(self.__class__, self).__init__(*args, **kwargs)
# if previous AD is now ex-AD, append that person to the list
@ -43,16 +45,49 @@ class WGForm(forms.Form):
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).plain_name())]
self.confirm_msg = ""
self.autoenable_confirm = False
def clean_acronym(self):
self.confirm_msg = ""
self.autoenable_confirm = False
acronym = self.cleaned_data['acronym'].strip().lower()
if not re.match(r'^[-\w]+$', acronym):
# be careful with acronyms, requiring confirmation to take existing or override historic
if self.wg and acronym == self.wg.acronym:
return acronym # no change, no check
if not re.match(r'^[-a-z0-9]+$', acronym):
raise forms.ValidationError("Acronym is invalid, may only contain letters, numbers and dashes.")
if acronym != self.cur_acronym:
if Group.objects.filter(acronym__iexact=acronym):
raise forms.ValidationError(mark_safe("The acronym belongs to an existing group. Please pick another,<br/> or go to <a href='%s'>recharter %s</a>" % (reverse("doc_view", None, kwargs={"name":"charter-ietf-%s"%acronym, }), acronym)))
if GroupHistory.objects.filter(acronym__iexact=acronym):
raise forms.ValidationError("The acronym has been used by a previous group. Please pick another.")
existing = Group.objects.filter(acronym__iexact=acronym)
if existing:
existing = existing[0]
if not self.wg and existing and existing.type_id == "wg":
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.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.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"))
if existing:
raise forms.ValidationError("Acronym used for an existing group (%s)." % existing.name)
old = GroupHistory.objects.filter(acronym__iexact=acronym, type="wg")
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.")
return acronym
def clean_urls(self):
@ -72,10 +107,7 @@ def edit(request, acronym=None, action="edit"):
"""Edit or create a WG, notifying parties as
necessary and logging changes as group events."""
if action == "edit":
# Editing. Get group
wg = get_object_or_404(Group, acronym=acronym)
if not wg.charter:
raise Http404
new_wg = False
elif action == "create":
wg = None
@ -86,27 +118,34 @@ def edit(request, acronym=None, action="edit"):
login = request.user.get_profile()
if request.method == 'POST':
form = WGForm(request.POST, cur_acronym=wg.acronym if wg else None)
form = WGForm(request.POST, wg=wg, confirmed=request.POST.get("confirmed", False))
if form.is_valid():
r = form.cleaned_data
clean = form.cleaned_data
if new_wg:
# Create WG
wg = Group(name=r["name"],
acronym=r["acronym"],
type=GroupTypeName.objects.get(slug="wg"),
state=GroupStateName.objects.get(slug="proposed"))
wg.save()
# get ourselves a proposed WG
try:
wg = Group.objects.get(acronym=clean["acronym"])
save_group_in_history(wg)
wg.state = GroupStateName.objects.get(slug="proposed")
wg.time = datetime.datetime.now()
wg.save()
except Group.DoesNotExist:
wg = Group.objects.create(name=clean["name"],
acronym=clean["acronym"],
type=GroupTypeName.objects.get(slug="wg"),
state=GroupStateName.objects.get(slug="proposed"))
e = ChangeStateGroupEvent(group=wg, type="changed_state")
e.time = datetime.datetime.now()
e.time = wg.time
e.by = login
e.state_id = "proposed"
e.desc = "Proposed group"
e.save()
else:
gh = save_group_in_history(wg)
save_group_in_history(wg)
if not wg.charter:
if not wg.charter: # make sure we have a charter
try:
charter = Document.objects.get(docalias__name="charter-ietf-%s" % wg.acronym)
except Document.DoesNotExist:
@ -140,9 +179,9 @@ def edit(request, acronym=None, action="edit"):
def diff(attr, name):
v = getattr(wg, attr)
if r[attr] != v:
changes.append(desc(name, r[attr], v))
setattr(wg, attr, r[attr])
if clean[attr] != v:
changes.append(desc(name, clean[attr], v))
setattr(wg, attr, clean[attr])
prev_acronym = wg.acronym
@ -168,7 +207,7 @@ def edit(request, acronym=None, action="edit"):
# update roles
for attr, slug, title in [('chairs', 'chair', "Chairs"), ('secretaries', 'secr', "Secretaries"), ('techadv', 'techadv', "Tech Advisors")]:
new = r[attr]
new = clean[attr]
old = Email.objects.filter(role__group=wg, role__name=slug).select_related("person")
if set(new) != set(old):
changes.append(desc(title,
@ -179,7 +218,7 @@ def edit(request, acronym=None, action="edit"):
Role.objects.get_or_create(name_id=slug, email=e, group=wg, person=e.person)
# update urls
new_urls = r['urls']
new_urls = clean['urls']
old_urls = format_urls(wg.groupurl_set.order_by('url'), ", ")
if ", ".join(sorted(new_urls)) != old_urls:
changes.append(desc('Urls', ", ".join(sorted(new_urls)), old_urls))
@ -203,7 +242,7 @@ def edit(request, acronym=None, action="edit"):
wg.save()
if new_wg:
return redirect('charter_startstop_process', name=wg.charter.name, option="initcharter")
return redirect('charter_submit', name=wg.charter.name, option="initcharter")
return redirect('wg_charter', acronym=wg.acronym)
else: # form.is_valid()
@ -224,7 +263,7 @@ def edit(request, acronym=None, action="edit"):
else:
init = dict(ad=login.id if has_role(request.user, "Area Director") else None,
)
form = WGForm(initial=init, cur_acronym=wg.acronym if wg else None)
form = WGForm(initial=init, wg=wg)
return render_to_response('wginfo/edit.html',
dict(wg=wg,
@ -236,19 +275,12 @@ def edit(request, acronym=None, action="edit"):
class ConcludeForm(forms.Form):
instructions = forms.CharField(widget=forms.Textarea, required=True)
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."""
try:
wg = Group.objects.get(acronym=acronym)
except Group.DoesNotExist:
wglist = GroupHistory.objects.filter(acronym=acronym)
if wglist:
return redirect('wg_conclude', acronym=wglist[0].group.acronym)
else:
raise Http404
wg = get_object_or_404(Group, acronym=acronym)
login = request.user.get_profile()
@ -259,6 +291,11 @@ def conclude(request, acronym):
email_secretariat(request, wg, "conclude", instructions)
e = GroupEvent(group=wg, by=login)
e.type = "requested_close"
e.desc = "Requested closing group"
e.save()
return redirect('wg_charter', acronym=wg.acronym)
else:
form = ConcludeForm()

View file

@ -120,6 +120,38 @@ class WgEditTestCase(django.test.TestCase):
self.assertEquals(group.charter.name, "charter-ietf-testwg")
self.assertEquals(group.charter.rev, "00-00")
def test_create_based_on_existing(self):
make_test_data()
url = urlreverse('wg_create')
login_testing_unauthorized(self, "secretary", url)
group = Group.objects.get(acronym="mars")
# try hijacking area - faulty
r = self.client.post(url, dict(name="Test", acronym=group.parent.acronym))
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertEquals(len(q('form input[name="confirmed"]')), 0) # can't confirm us out of this
# try elevating BoF to WG
group.state_id = "bof"
group.save()
r = self.client.post(url, dict(name="Test", acronym=group.acronym))
self.assertEquals(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertEquals(len(q('form input[name="confirmed"]')), 1)
self.assertEquals(Group.objects.get(acronym=group.acronym).state_id, "bof")
# confirm elevation
r = self.client.post(url, dict(name="Test", acronym=group.acronym, confirmed="1"))
self.assertEquals(r.status_code, 302)
self.assertEquals(Group.objects.get(acronym=group.acronym).state_id, "proposed")
self.assertEquals(Group.objects.get(acronym=group.acronym).name, "Test")
def test_edit_info(self):
make_test_data()

View file

@ -194,15 +194,20 @@ def wg_charter(request, acronym):
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
fill_in_charter_info(wg)
actions = []
e = wg.latest_event(type__in=("changed_state", "requested_close",))
requested_close = wg.state_id != "conclude" and e and e.type == "requested_close"
if wg.state_id != "conclude":
actions.append(("Edit WG", urlreverse("wg_edit", kwargs=dict(acronym=wg.acronym))))
if wg.state_id == "active" and (not wg.charter or wg.charter.get_state_slug() == "approved"):
actions.append(("Conclude WG", urlreverse("wg_conclude", kwargs=dict(acronym=wg.acronym))))
if wg.state_id in ("active", "dormant"):
actions.append(("Request closing WG", urlreverse("wg_conclude", kwargs=dict(acronym=wg.acronym))))
context = get_wg_menu_context(wg, "charter")
context.update(dict(
actions=actions))
actions=actions,
requested_close=requested_close,
))
return render_to_response('wginfo/wg_charterREDESIGN.html',
context,