Add confirmation step so that the new charter effort page allows one
to continue from a BoF (with a warning with confirmation being ticked) and continue from a WG (with another warning and confirmation not being ticked). Also allow overriding a historically used acronym (one that's in the GroupHistory) after confirmation. - Legacy-Id: 4402
This commit is contained in:
parent
2dbed6a439
commit
79b4c0843b
|
@ -4,35 +4,20 @@
|
||||||
{% if wg %}
|
{% if wg %}
|
||||||
Edit WG {{ wg.acronym }}
|
Edit WG {{ wg.acronym }}
|
||||||
{% else %}
|
{% else %}
|
||||||
Create WG
|
Start chartering new WG
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block morecss %}
|
{% block morecss %}
|
||||||
form.edit-info #id_name {
|
form.edit #id_name,
|
||||||
width: 396px;
|
form.edit #id_list_email,
|
||||||
}
|
form.edit #id_list_subscribe,
|
||||||
|
form.edit #id_list_archive,
|
||||||
form.edit-info #id_list_email {
|
form.edit #id_urls,
|
||||||
width: 396px;
|
form.edit #id_comments { width: 400px; }
|
||||||
}
|
form.edit input[type=checkbox] { vertical-align: middle; }
|
||||||
|
ul.errorlist { border-width: 0px; padding: 0px; margin: 0px;}
|
||||||
form.edit-info #id_list_subscribe {
|
ul.errorlist li { color: #a00; margin: 0px; padding: 0px; list-style: none; }
|
||||||
width: 396px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form.edit-info #id_list_archive {
|
|
||||||
width: 396px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form.edit-info #id_urls {
|
|
||||||
width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form.edit-info #id_comments {
|
|
||||||
width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block pagehead %}
|
{% block pagehead %}
|
||||||
|
@ -44,11 +29,11 @@ form.edit-info #id_comments {
|
||||||
<h1>{% if wg %}
|
<h1>{% if wg %}
|
||||||
Edit WG {{ wg.acronym }}
|
Edit WG {{ wg.acronym }}
|
||||||
{% else %}
|
{% else %}
|
||||||
Create WG
|
Start chartering new WG
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<form class="edit-info" action="" method="POST">
|
<form class="edit" action="" method="POST">
|
||||||
<table>
|
<table>
|
||||||
{% for field in form.visible_fields %}
|
{% for field in form.visible_fields %}
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -63,6 +48,14 @@ Create WG
|
||||||
{{ field.errors }}
|
{{ field.errors }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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 %}
|
{% endfor %}
|
||||||
<tr>
|
<tr>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
@ -71,7 +64,7 @@ Create WG
|
||||||
<a href="{% url wg_charter acronym=wg.acronym %}">Back</a>
|
<a href="{% url wg_charter acronym=wg.acronym %}">Back</a>
|
||||||
<input type="submit" value="Save"/>
|
<input type="submit" value="Save"/>
|
||||||
{% else %}
|
{% else %}
|
||||||
<input type="submit" value="Create"/>
|
<input type="submit" value="Start chartering"/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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/jquery.tokeninput.js"></script>
|
||||||
<script type="text/javascript" src="/js/lib/json2.js"></script>
|
<script type="text/javascript" src="/js/lib/json2.js"></script>
|
||||||
<script type="text/javascript" src="/js/emails-field.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 %}
|
{% endblock %}
|
||||||
|
|
|
@ -33,7 +33,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)
|
urls = forms.CharField(widget=forms.Textarea, label="Additional URLs", help_text="Format: http://site/path (Optional description). Separate multiple entries with newline.", required=False)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.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)
|
super(self.__class__, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
# if previous AD is now ex-AD, append that person to the list
|
# if previous AD is now ex-AD, append that person to the list
|
||||||
|
@ -42,16 +44,49 @@ class WGForm(forms.Form):
|
||||||
if ad_pk and ad_pk not in [pk for pk, name in choices]:
|
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.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):
|
def clean_acronym(self):
|
||||||
|
self.confirm_msg = ""
|
||||||
|
self.autoenable_confirm = False
|
||||||
|
|
||||||
acronym = self.cleaned_data['acronym'].strip().lower()
|
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.")
|
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):
|
existing = Group.objects.filter(acronym__iexact=acronym)
|
||||||
raise forms.ValidationError("Acronym used in an existing group. Please pick another.")
|
if existing:
|
||||||
if GroupHistory.objects.filter(acronym__iexact=acronym):
|
existing = existing[0]
|
||||||
raise forms.ValidationError("Acronym used in a previous group. Please pick another.")
|
|
||||||
|
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
|
return acronym
|
||||||
|
|
||||||
def clean_urls(self):
|
def clean_urls(self):
|
||||||
|
@ -71,10 +106,7 @@ def edit(request, acronym=None, action="edit"):
|
||||||
"""Edit or create a WG, notifying parties as
|
"""Edit or create a WG, notifying parties as
|
||||||
necessary and logging changes as group events."""
|
necessary and logging changes as group events."""
|
||||||
if action == "edit":
|
if action == "edit":
|
||||||
# Editing. Get group
|
|
||||||
wg = get_object_or_404(Group, acronym=acronym)
|
wg = get_object_or_404(Group, acronym=acronym)
|
||||||
if not wg.charter:
|
|
||||||
raise Http404
|
|
||||||
new_wg = False
|
new_wg = False
|
||||||
elif action == "create":
|
elif action == "create":
|
||||||
wg = None
|
wg = None
|
||||||
|
@ -85,27 +117,34 @@ def edit(request, acronym=None, action="edit"):
|
||||||
login = request.user.get_profile()
|
login = request.user.get_profile()
|
||||||
|
|
||||||
if request.method == 'POST':
|
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():
|
if form.is_valid():
|
||||||
r = form.cleaned_data
|
clean = form.cleaned_data
|
||||||
if new_wg:
|
if new_wg:
|
||||||
# Create WG
|
# get ourselves a proposed WG
|
||||||
wg = Group(name=r["name"],
|
try:
|
||||||
acronym=r["acronym"],
|
wg = Group.objects.get(acronym=clean["acronym"])
|
||||||
type=GroupTypeName.objects.get(slug="wg"),
|
|
||||||
state=GroupStateName.objects.get(slug="proposed"))
|
save_group_in_history(wg)
|
||||||
wg.save()
|
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 = ChangeStateGroupEvent(group=wg, type="changed_state")
|
||||||
e.time = datetime.datetime.now()
|
e.time = wg.time
|
||||||
e.by = login
|
e.by = login
|
||||||
e.state_id = "proposed"
|
e.state_id = "proposed"
|
||||||
e.desc = "Proposed group"
|
e.desc = "Proposed group"
|
||||||
e.save()
|
e.save()
|
||||||
else:
|
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:
|
try:
|
||||||
charter = Document.objects.get(docalias__name="charter-ietf-%s" % wg.acronym)
|
charter = Document.objects.get(docalias__name="charter-ietf-%s" % wg.acronym)
|
||||||
except Document.DoesNotExist:
|
except Document.DoesNotExist:
|
||||||
|
@ -139,9 +178,9 @@ def edit(request, acronym=None, action="edit"):
|
||||||
|
|
||||||
def diff(attr, name):
|
def diff(attr, name):
|
||||||
v = getattr(wg, attr)
|
v = getattr(wg, attr)
|
||||||
if r[attr] != v:
|
if clean[attr] != v:
|
||||||
changes.append(desc(name, r[attr], v))
|
changes.append(desc(name, clean[attr], v))
|
||||||
setattr(wg, attr, r[attr])
|
setattr(wg, attr, clean[attr])
|
||||||
|
|
||||||
prev_acronym = wg.acronym
|
prev_acronym = wg.acronym
|
||||||
|
|
||||||
|
@ -167,7 +206,7 @@ def edit(request, acronym=None, action="edit"):
|
||||||
|
|
||||||
# update roles
|
# update roles
|
||||||
for attr, slug, title in [('chairs', 'chair', "Chairs"), ('secretaries', 'secr', "Secretaries"), ('techadv', 'techadv', "Tech Advisors")]:
|
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")
|
old = Email.objects.filter(role__group=wg, role__name=slug).select_related("person")
|
||||||
if set(new) != set(old):
|
if set(new) != set(old):
|
||||||
changes.append(desc(title,
|
changes.append(desc(title,
|
||||||
|
@ -178,7 +217,7 @@ def edit(request, acronym=None, action="edit"):
|
||||||
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=wg, person=e.person)
|
||||||
|
|
||||||
# update urls
|
# update urls
|
||||||
new_urls = r['urls']
|
new_urls = clean['urls']
|
||||||
old_urls = format_urls(wg.groupurl_set.order_by('url'), ", ")
|
old_urls = format_urls(wg.groupurl_set.order_by('url'), ", ")
|
||||||
if ", ".join(sorted(new_urls)) != old_urls:
|
if ", ".join(sorted(new_urls)) != old_urls:
|
||||||
changes.append(desc('Urls', ", ".join(sorted(new_urls)), old_urls))
|
changes.append(desc('Urls', ", ".join(sorted(new_urls)), old_urls))
|
||||||
|
@ -223,7 +262,7 @@ def edit(request, acronym=None, action="edit"):
|
||||||
else:
|
else:
|
||||||
init = dict(ad=login.id if has_role(request.user, "Area Director") else None,
|
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',
|
return render_to_response('wginfo/edit.html',
|
||||||
dict(wg=wg,
|
dict(wg=wg,
|
||||||
|
|
|
@ -120,6 +120,38 @@ class WgEditTestCase(django.test.TestCase):
|
||||||
self.assertEquals(group.charter.name, "charter-ietf-testwg")
|
self.assertEquals(group.charter.name, "charter-ietf-testwg")
|
||||||
self.assertEquals(group.charter.rev, "00-00")
|
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):
|
def test_edit_info(self):
|
||||||
make_test_data()
|
make_test_data()
|
||||||
|
|
Loading…
Reference in a new issue