Merged in charter branch fixes from olau@iola.dk, from r4354 through r4381.

- Legacy-Id: 4382
This commit is contained in:
Henrik Levkowetz 2012-05-04 11:47:30 +00:00
commit 839df60480
18 changed files with 147 additions and 71 deletions

View file

@ -136,7 +136,7 @@ def augment_with_start_time(docs):
def get_chartering_type(doc):
chartering = ""
if doc.get_state_slug() not in ("notrev", "approved"):
if doc.group.state_id == "proposed":
if doc.group.state_id in ("proposed", "bof"):
chartering = "initial"
elif doc.group.state_id == "active":
chartering = "rechartering"

View file

@ -32,6 +32,7 @@
from django.conf.urls.defaults import patterns, url, include
from ietf.idrfc import views_doc, views_search, views_edit, views_ballot, views
from ietf.doc.models import State
urlpatterns = patterns('',
(r'^/?$', views_search.search_main),
@ -69,3 +70,9 @@ urlpatterns = patterns('',
(r'^(?P<name>[A-Za-z0-9.-]+)/charter/', include('ietf.wgcharter.urls')),
)
urlpatterns += patterns('django.views.generic.simple',
url(r'^help/state/charter/$', 'direct_to_template', { 'template': 'wgcharter/states.html', 'extra_context': { 'states': State.objects.filter(type="charter") } }, name='help_charter_states'),
)

View file

@ -475,9 +475,11 @@ def send_ballot_commentREDESIGN(request, name, ballot_id):
subj = []
d = ""
blocking_name = "DISCUSS"
if pos.pos.blocking and pos.discuss:
d = pos.discuss
subj.append(pos.pos.name.upper())
blocking_name = pos.pos.name.upper()
subj.append(blocking_name)
c = ""
if pos.comment:
c = pos.comment
@ -488,10 +490,13 @@ def send_ballot_commentREDESIGN(request, name, ballot_id):
if subj:
subject += ": (with %s)" % " and ".join(subj)
doc.filename = doc.name # compatibility attributes
doc.revision_display = doc.rev
body = render_to_string("idrfc/ballot_comment_mail.txt",
dict(discuss=d, comment=c, ad=ad.plain_name(), doc=doc, pos=pos.pos))
dict(discuss=d,
comment=c,
ad=ad.plain_name(),
doc=doc,
pos=pos.pos,
blocking_name=blocking_name,))
frm = ad.role_email("ad").formatted_email()
to = "The IESG <iesg@ietf.org>"

View file

@ -1,19 +1,21 @@
{% autoescape off %}{{ ad }} has entered the following ballot position for
{{ doc.filename }}-{{ doc.revision_display }}: {{ pos.name }}
{{ doc.name }}-{{ doc.rev }}: {{ pos.name }}
When responding, please keep the subject line intact and reply to all
email addresses included in the To and CC lines. (Feel free to cut this
introductory paragraph, however.)
{% if doc.type_id == "draft" %}
Please refer to http://www.ietf.org/iesg/statement/discuss-criteria.html
for more information about IESG DISCUSS and COMMENT positions.
{% endif %}
{% if not discuss and not comment %}
There is no DISCUSS or COMMENT text associated with this position.
There are no remarks associated with this position.
{% endif %}
{% if discuss %}----------------------------------------------------------------------
DISCUSS:
{{ blocking_name }}:
----------------------------------------------------------------------
{{ discuss|safe|wordwrap:73 }}

View file

@ -34,7 +34,7 @@
<td>{{ group.state.name }}</td>
</tr>
<tr>
<td><a href="/wgcharter/help/state/">Charter State</a>:</td>
<td><a href="/doc/help/state/charter/">Charter State</a>:</td>
<td>
<div>
<a title="{{ doc.get_state.desc }}"{% if not snapshot and user|has_role:"Area Director,Secretariat" %} href="{% url charter_change_state name=doc.name %}"{% endif %}>{{ doc.get_state.name }}</a>
@ -52,7 +52,11 @@
{% endif %}
{% else %}
- <a href="{% url charter_startstop_process name=doc.name option='recharter' %}">Recharter</a>
{% if group.state_id == "proposed" or group.state_id == "bof" %}
- <a href="{% url charter_startstop_process name=doc.name option='initcharter' %}">Charter</a>
{% else %}
- <a href="{% url charter_startstop_process name=doc.name option='recharter' %}">Recharter</a>
{% endif %}
{% endif %}
{% endif %}
@ -101,7 +105,7 @@
<h3>Charter {{ doc.canonical_name }}-{{ doc.rev }}
{% if user|has_role:"Area Director,Secretariat" and chartering and group.state_id != "conclude" %}
{% if not snapshot and user|has_role:"Area Director,Secretariat" and chartering and group.state_id != "conclude" %}
<a class="edit" href="{% url charter_submit name=doc.name %}">Change charter text</a>
{% endif %}
</h3>

View file

@ -11,7 +11,7 @@
{% block content %}
{{ top|safe }}
{% if diff_revisions %}
{% if diff_revisions and diff_revisions|length > 1 %}
<div class="ietf-box diffTool">
<h2>Diffs</h2>

View file

@ -13,7 +13,7 @@
<div class="writeup">
<h2>{{ title }}</h2>
{% if can_edit %}<a href="{{ url }}" class="edit">Edit {{ title }}</a>{% endif %}
{% if can_edit %}<a href="{{ url }}" class="edit">{% if text %}Edit{% else %}Generate{% endif %} {{ title }}</a>{% endif %}
<pre {% if can_edit %}class="editable"{% endif %}>
{{ text }}

View file

@ -2,7 +2,7 @@
To: IETF-Announce <ietf-announce@ietf.org>
Subject: WG Action: {{ action_type }} {{ wg.name }} ({{ wg.acronym }})
{% filter wordwrap:73 %}{% ifequal action_type "Formed" %}A new IETF working group has been formed in the {{ wg.parent.name }}.{% endifequal %}{% ifequal action_type "Rechartered" %}The {{ wg.name }} ({{ wg.acronym }}) working group in the {{ wg.parent.name }} of the IETF has been rechartered.{% endifequal %} For additional information please contact the Area Directors or the WG Chair.
{% filter wordwrap:73 %}{% ifequal action_type "Formed" %}A new IETF working group has been formed in the {{ wg.parent.name }}.{% endifequal %}{% ifequal action_type "Rechartered" %}The {{ wg.name }} ({{ wg.acronym }}) working group in the {{ wg.parent.name }} of the IETF has been rechartered.{% endifequal %} For additional information please contact the Area Directors or the WG Chair{{ chairs|pluralize}}.
{% include "wgcharter/wg_info.txt" %}

View file

@ -22,7 +22,7 @@ form #id_ballot_writeup {
<div class="actions">
<a href="{% url doc_writeup name=charter.name %}">Back</a>
<input type="submit" name="save_ballot_writeup" value="Save Ballot Writeup" />
<input style="margin-left: 8px" type="submit" name="issue_ballot" value="Save and {% if reissue %}Re-{% endif %}Issue Ballot" />
<input style="margin-left: 8px" type="submit" name="send_ballot" value="Save and {% if reissue %}Re-{% endif %}Send Ballot to the IESG" />
</div>
</form>

View file

@ -48,7 +48,7 @@ form.change-state .actions {
{{ field.errors }}
</td>
</tr>
{% if field.name == "charter_state" and not option == "initcharter" %}
{% if field.name == "charter_state" and chartering_type == "rechartering" %}
<tr class="ballot-wo-extern" style="display:none">
<td></td>
<td><label><input name="ballot_wo_extern" type="checkbox" /> Ask whether external review can be skipped in ballot</label></td>
@ -64,7 +64,7 @@ form.change-state .actions {
<input type="submit" value="Submit"/>
{% else %}
<a href="{% url doc_view name=doc.name %}">Back</a>
<input type="submit" value="Save"/>
<input type="submit" value="Save and (possibly) notify Secretariat"/>
{% endif %}
</td>
</tr>

View file

@ -2,23 +2,23 @@
------------------------------------------------
Current Status: {{ wg.state.name }} Working Group
Chairs:
{% if chairs %}Chairs:
{% for r in chairs %} {{ r.person.plain_name }} <{{r.email.address}}>
{% endfor %}
Secretaries:
{% endif %}{% if secr %}Secretaries:
{% for r in secr %} {{ r.person.plain_name }} <{{r.email.address}}>
{% endfor %}
Technical advisors:
{% endif %}{% if techadv %}Technical advisors:
{% for r in techadv %} {{ r.person.plain_name }} <{{r.email.address}}>
{% endfor %}
Assigned Area Director:
{% if wg.ad %} {{ wg.ad.plain_name }} <{{ ad_email }}>{% endif %}
{% endif %}{% if wg.ad %}Assigned Area Director:
{{ wg.ad.plain_name }} <{{ ad_email }}>
Mailing list:
{% endif %}{% if wg.list_email %}Mailing list
Address: {{ wg.list_email }}
To Subscribe: {{ wg.list_subscribe }}
Archive: {{ wg.list_archive }}
Charter:
{% endif %}
Charter of Working Group:
{{ charter_text }}

View file

@ -52,7 +52,7 @@ Create WG
<table>
{% for field in form.visible_fields %}
<tr>
<th>{{ field.label_tag }}:</th>
<th>{{ field.label_tag }}: {% if field.field.required %}*{% endif %}</th>
<td>{{ field }}
{% ifequal field.name "ad" %}
{% if user|has_role:"Area Director" %}

43
ietf/utils/textupload.py Normal file
View file

@ -0,0 +1,43 @@
import re
import django.forms
def get_cleaned_text_file_content(uploaded_file):
"""Read uploaded file, try to fix up encoding to UTF-8 and
transform line endings into Unix style, then return the content as
a UTF-8 string. Errors are reported as
django.forms.ValidationError exceptions."""
if not uploaded_file:
return u""
if uploaded_file.size and uploaded_file.size > 10 * 1000 * 1000:
raise django.forms.ValidationError("Text file too large (size %s)." % uploaded_file.size)
content = "".join(uploaded_file.chunks())
# try to fixup encoding
import magic
m = magic.open(magic.MAGIC_MIME)
m.load()
filetype = m.buffer(content) # should look like "text/plain; charset=us-ascii"
if not filetype.startswith("text"):
raise django.forms.ValidationError("Uploaded file does not appear to be a text file.")
match = re.search("charset=([\w-]+)", filetype)
if not match:
raise django.forms.ValidationError("File has unknown encoding.")
encoding = match.group(1)
if "ascii" not in encoding:
try:
content = content.decode(encoding)
except Exception as e:
raise django.forms.ValidationError("Error decoding file (%s). Try submitting with UTF-8 encoding or remove non-ASCII characters." % str(e))
# turn line-endings into Unix style
content = content.replace("\r\n", "\n").replace("\r", "\n")
return content.encode("utf-8")

View file

@ -48,7 +48,12 @@ def generate_ballot_writeup(request, doc):
return e
def default_action_text(wg, charter, user, action):
def default_action_text(wg, charter, user):
if next_approved_revision(wg.charter.rev) == "01":
action = "Formed"
else:
action = "Rechartered"
e = WriteupDocEvent(doc=charter, by=user)
e.by = user
e.type = "changed_action_announcement"

View file

@ -67,18 +67,19 @@ class EditCharterTestCase(django.test.TestCase):
charter = Document.objects.get(name="charter-ietf-%s" % group.acronym)
self.assertEquals(charter.get_state_slug(), slug)
self.assertTrue(charter.docevent_set.count() > events_before)
events_now = charter.docevent_set.count()
self.assertTrue(events_now > events_before)
def find_event(t):
return [e for e in charter.docevent_set.all()[:events_now - events_before] if e.type == t]
self.assertTrue("State changed" in find_event("changed_document")[0].desc)
if slug in ("intrev", "iesgrev"):
self.assertEquals(charter.docevent_set.all()[0].type, "created_ballot")
self.assertTrue("State changed" in charter.docevent_set.all()[1].desc)
else:
self.assertTrue("State changed" in charter.docevent_set.all()[0].desc)
if slug == "extrev":
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("State changed" in outbox[-1]['Subject'])
else:
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("State changed" in outbox[-1]['Subject'])
self.assertTrue(find_event("created_ballot"))
self.assertEquals(len(outbox), mailbox_before + 1)
self.assertTrue("State changed" in outbox[-1]['Subject'])
def test_edit_telechat_date(self):
make_test_data()
@ -130,9 +131,20 @@ class EditCharterTestCase(django.test.TestCase):
q = PyQuery(r.content)
self.assertEquals(len(q('form input[name=txt]')), 1)
# faulty post
test_file = StringIO("\x10\x11\x12") # post binary file
test_file.name = "unnamed"
r = self.client.post(url, dict(txt=test_file))
self.assertEquals(r.status_code, 200)
self.assertTrue("does not appear to be a text file" in r.content)
# post
prev_rev = charter.rev
test_file = StringIO("hello world")
latin_1_snippet = '\xe5' * 10
utf_8_snippet = '\xc3\xa5' * 10
test_file = StringIO("Windows line\r\nMac line\rUnix line\n" + latin_1_snippet)
test_file.name = "unnamed"
r = self.client.post(url, dict(txt=test_file))
@ -142,6 +154,10 @@ class EditCharterTestCase(django.test.TestCase):
self.assertEquals(charter.rev, next_revision(prev_rev))
self.assertTrue("new_revision" in charter.latest_event().type)
with open(os.path.join(self.charter_dir, charter.canonical_name() + "-" + charter.rev + ".txt")) as f:
self.assertEquals(f.read(),
"Windows line\nMac line\nUnix line\n" + utf_8_snippet)
class CharterApproveBallotTestCase(django.test.TestCase):
fixtures = ['names']

View file

@ -1,12 +1,8 @@
# Copyright The IETF Trust 2011, All Rights Reserved
from django.conf.urls.defaults import patterns, url
from ietf.doc.models import State
urlpatterns = patterns('django.views.generic.simple',
url(r'^help/state/$', 'direct_to_template', { 'template': 'wgcharter/states.html', 'extra_context': { 'states': State.objects.filter(type="charter") } }, name='help_charter_states'),
)
urlpatterns += patterns('',
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'),

View file

@ -10,13 +10,15 @@ 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.safestring import mark_safe
from django.conf import settings
from ietf.utils.mail import send_mail_text, send_mail_preformatted
from ietf.utils.textupload import get_cleaned_text_file_content
from ietf.ietfauth.decorators import has_role, role_required
from ietf.iesg.models import TelechatDate
from ietf.doc.models import *
from ietf.doc.utils import create_ballot_if_not_open, close_open_ballots
from ietf.doc.utils import *
from ietf.name.models import *
from ietf.person.models import *
from ietf.group.models import *
@ -28,7 +30,7 @@ from ietf.wgcharter.utils import *
class ChangeStateForm(forms.Form):
charter_state = forms.ModelChoiceField(State.objects.filter(type="charter", slug__in=["infrev", "intrev", "extrev", "iesgrev"]), 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="Optional message to the Secretariat", required=False)
message = forms.CharField(widget=forms.Textarea, help_text="Leave blank to change state without notifying the Secretariat", required=False, label=mark_safe("Message to<br> Secretariat"))
comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the charter history", required=False)
def __init__(self, *args, **kwargs):
self.hide = kwargs.pop('hide', None)
@ -115,6 +117,8 @@ def change_state(request, name, option=None):
create_ballot_if_not_open(charter, login, "r-wo-ext")
else:
create_ballot_if_not_open(charter, login, "r-extrev")
default_review_text(wg, charter, login)
default_action_text(wg, charter, login)
elif charter_state.slug == "iesgrev":
create_ballot_if_not_open(charter, login, "approve")
@ -175,6 +179,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),
messages=simplejson.dumps(messages),
states_for_ballot_wo_extern=simplejson.dumps(list(states_for_ballot_wo_extern)),
),
@ -225,13 +230,14 @@ class UploadForm(forms.Form):
def clean_content(self):
return self.cleaned_data["content"].replace("\r", "")
def clean_txt(self):
return get_cleaned_text_file_content(self.cleaned_data["txt"])
def save(self, wg, rev):
fd = self.cleaned_data['txt']
filename = os.path.join(settings.CHARTER_PATH, '%s-%s.txt' % (wg.charter.canonical_name(), rev))
with open(filename, 'wb+') as destination:
if fd:
for chunk in fd.chunks():
destination.write(chunk)
with open(filename, 'wb') as destination:
if self.cleaned_data['txt']:
destination.write(self.cleaned_data['txt'])
else:
destination.write(self.cleaned_data['content'])
@ -242,7 +248,8 @@ def submit(request, name):
login = request.user.get_profile()
not_uploaded_yet = charter.rev.endswith("-00") and not os.path.exists(os.path.join(settings.CHARTER_PATH, '%s-%s.txt' % (charter.canonical_name(), charter.rev)))
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)
if not_uploaded_yet:
# this case is special - we recently chartered or rechartered and have no file yet
@ -320,10 +327,7 @@ def announcement_text(request, name, ann):
existing = charter.latest_event(WriteupDocEvent, type="changed_review_announcement")
if not existing:
if ann == "action":
if next_approved_revision(wg.charter.rev) == "01":
existing = default_action_text(wg, charter, login, "Formed")
else:
existing = default_action_text(wg, charter, login, "Rechartered")
existing = default_action_text(wg, charter, login)
elif ann == "review":
existing = default_review_text(wg, charter, login)
@ -347,10 +351,7 @@ def announcement_text(request, name, ann):
if "regenerate_text" in request.POST:
if ann == "action":
if next_approved_revision(wg.charter.rev) == "01":
e = default_action_text(wg, charter, login, "Formed")
else:
e = default_action_text(wg, charter, login, "Rechartered")
e = default_action_text(wg, charter, login)
elif ann == "review":
e = default_review_text(wg, charter, login)
# make sure form has the updated text
@ -402,7 +403,7 @@ def ballot_writeupnotes(request, name):
form = BallotWriteupForm(initial=dict(ballot_writeup=existing.text))
if request.method == 'POST' and ("save_ballot_writeup" in request.POST or "issue_ballot" in request.POST):
if request.method == 'POST' and ("save_ballot_writeup" in request.POST or "send_ballot" in request.POST):
form = BallotWriteupForm(request.POST)
if form.is_valid():
t = form.cleaned_data["ballot_writeup"]
@ -414,7 +415,7 @@ def ballot_writeupnotes(request, name):
e.text = t
e.save()
if "issue_ballot" in request.POST and approval:
if "send_ballot" in request.POST and approval:
if has_role(request.user, "Area Director") and not charter.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=login, ballot=ballot):
# sending the ballot counts as a yes
pos = BallotPositionDocEvent(doc=charter, by=login)
@ -430,7 +431,7 @@ def ballot_writeupnotes(request, name):
e = DocEvent(doc=charter, by=login)
e.by = login
e.type = "sent_ballot_announcement"
e.desc = "Ballot has been issued"
e.desc = "Ballot has been sent"
e.save()
return render_to_response('wgcharter/ballot_issued.html',
@ -458,10 +459,7 @@ def approve(request, name):
e = charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
if not e:
if next_approved_revision(wg.charter.rev) == "01":
announcement = default_action_text(wg, charter, login, "Formed").text
else:
announcement = default_action_text(wg, charter, login, "Rechartered").text
announcement = default_action_text(wg, charter, login).text
else:
announcement = e.text

View file

@ -47,11 +47,11 @@ class WGForm(forms.Form):
if not re.match(r'^[-\w]+$', acronym):
raise forms.ValidationError("Acronym is invalid, may only contain letters, numbers and dashes.")
if self.cur_acronym and acronym != self.cur_acronym:
if acronym != self.cur_acronym:
if Group.objects.filter(acronym__iexact=acronym):
raise forms.ValidationError("Acronym used in an existing WG. Please pick another.")
raise forms.ValidationError("Acronym used in an existing group. Please pick another.")
if GroupHistory.objects.filter(acronym__iexact=acronym):
raise forms.ValidationError("Acronym used in a previous WG. Please pick another.")
raise forms.ValidationError("Acronym used in a previous group. Please pick another.")
return acronym
def clean_urls(self):
@ -118,7 +118,7 @@ def edit(request, acronym=None, action="edit"):
rev="00-00",
)
charter.save()
charter.set_state(State.objects.get(type="charter", slug="infrev"))
charter.set_state(State.objects.get(type="charter", slug="notrev"))
# Create an alias as well
DocAlias.objects.create(