Move WG edit and conclude views from wgcharter to wginfo since they
aren't dealing with charters directly, clean up and add some polish and fix a bunch of refactoring bugs. Start on fixing tests. - Legacy-Id: 4169
This commit is contained in:
parent
32659ed957
commit
2fea753f51
|
@ -1,25 +1,32 @@
|
|||
{# Copyright The IETF Trust 2011, All Rights Reserved #}
|
||||
{% load ietf_filters %}
|
||||
{{ obj.info.text|safe }}<br/>
|
||||
<br/>
|
||||
<p>{{ obj.info.text|safe|linebreaksbr }}</p>
|
||||
|
||||
{% with obj.group as wg %}
|
||||
WG name: {{ wg.name }}<br/>
|
||||
WG acronym: {{ wg.acronym }}<br/>
|
||||
IETF area: {{ wg.parent|default:"-" }}<br/>
|
||||
<p>
|
||||
WG name: {{ wg.name }}<br/>
|
||||
WG acronym: {{ wg.acronym }}<br/>
|
||||
IETF area: {{ wg.parent|default:"-" }}<br/>
|
||||
</p>
|
||||
|
||||
WG chairs: {% for n in obj.chairs %}{{ n }}{% if not forloop.last %}, {% endif %}{% endfor %}<br/>
|
||||
WG secretaries: {% for n in obj.secr %}{{ n }}{% if not forloop.last %}, {% endif %}{% endfor %}<br/>
|
||||
WG technical advisors: {% for n in obj.techadv %}{{ n }}{% if not forloop.last %}, {% endif %}{% endfor %}<br/>
|
||||
Assigned AD: {{ wg.ad }}<br/>
|
||||
<p>
|
||||
WG chairs: {% for n in obj.chairs %}{{ n }}{% if not forloop.last %}, {% endif %}{% endfor %}<br/>
|
||||
WG secretaries: {% for n in obj.secr %}{{ n }}{% if not forloop.last %}, {% endif %}{% endfor %}<br/>
|
||||
WG technical advisors: {% for n in obj.techadv %}{{ n }}{% if not forloop.last %}, {% endif %}{% endfor %}<br/>
|
||||
Assigned AD: {{ wg.ad }}<br/>
|
||||
</p>
|
||||
|
||||
Mailing list: {{ wg.list_email }}<br/>
|
||||
Mailing list subscribe {{ wg.list_subscribe }}<br/>
|
||||
Mailing list archive: {{ wg.list_archive }}<br/>
|
||||
Other web sites: {% for a in wg.groupurl_set.all %}{{ a.url }} {% if a.name %}({{ a.name }}){% endif %}{% if not forloop.last %}, {% endif %}{% endfor %}<br/>
|
||||
<p>
|
||||
Mailing list: {{ wg.list_email }}<br/>
|
||||
Mailing list subscribe {{ wg.list_subscribe }}<br/>
|
||||
Mailing list archive: {{ wg.list_archive }}<br/>
|
||||
Other web sites: {% for a in wg.groupurl_set.all %}{{ a.url }} {% if a.name %}({{ a.name }}){% endif %}{% if not forloop.last %}, {% endif %}{% endfor %}<br/>
|
||||
</p>
|
||||
|
||||
WG State: {{ wg.state|safe }}<br/>
|
||||
Charter State: {{ wg.charter.charter_state|safe }}<br/>
|
||||
<br/>
|
||||
<p>
|
||||
WG State: {{ wg.state|safe }}<br/>
|
||||
Charter State: {% if wg.charter %}{{ wg.charter.get_state|safe }}{% endif %}<br/>
|
||||
</p>
|
||||
{% if obj.rev %}
|
||||
{{ obj.charter|safe }}
|
||||
{% else %}
|
||||
|
|
|
@ -46,9 +46,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
{% if chartering %}
|
||||
|
||||
{% if doc.group.comments %}
|
||||
{% if chartering and doc.group.comments %}
|
||||
<tr>
|
||||
{% if chartering == "initial" %}<td>Reason for chartering:</td>{% endif %}
|
||||
{% if chartering == "rechartering" %}<td>Reason for rechartering:</td>{% endif %}
|
||||
|
@ -72,28 +70,28 @@
|
|||
|
||||
<tr><td>Last updated:</td><td> {{ doc.time|date:"Y-m-d" }}</td></tr>
|
||||
|
||||
{% endif %}
|
||||
|
||||
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
|
||||
</table>
|
||||
|
||||
{% if not snapshot and user|has_role:"Area Director,Secretariat" %}
|
||||
<div class="actions">
|
||||
{% if not snapshot and user|has_role:"Area Director,Secretariat" %}
|
||||
|
||||
{% if chartering %}
|
||||
{% if chartering %}
|
||||
|
||||
{% if doc.group.state_id != "conclude" %}
|
||||
<a href="{% url wg_submit name=doc.group.acronym %}">Edit charter</a>
|
||||
{% endif %}
|
||||
{% if doc.group.state_id != "conclude" %}
|
||||
<a href="{% url wg_submit name=doc.group.acronym %}">Edit charter text</a>
|
||||
{% endif %}
|
||||
|
||||
<a href="{% url wg_startstop_process name=doc.group.acronym option='abandon' %}">Abandon effort</a>
|
||||
<a href="{% url wg_startstop_process name=doc.group.acronym option='abandon' %}">Abandon effort</a>
|
||||
|
||||
{% else %}
|
||||
<a href="{% url wg_startstop_process name=doc.group.acronym option='recharter' %}">Recharter</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<a href="{% url wg_startstop_process name=doc.group.acronym option='recharter' %}">Recharter</a>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div><a href="/feed/wgcomments/{{ doc.group.acronym }}/">Atom feed</a></div>
|
||||
<a href="/feed/wgcomments/{{ doc.group.acronym }}/">Atom feed</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>Other versions: <a href="{{ txt_url }}">plain text</a></p>
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
Copyright The IETF Trust 2011, All Rights Reserved
|
||||
{% endcomment %}
|
||||
|
||||
{% block title %}Add comment on {{ wg.acronym }}{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
form.add-comment #id_comment {
|
||||
width: 600px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
form.add-comment .actions {
|
||||
padding-top: 20px;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Add comment on {{ wg.acronym }}</h1>
|
||||
|
||||
<p>The comment will be added to the history trail.</p>
|
||||
|
||||
<form class="add-comment" action="" method="POST">
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td class="actions">
|
||||
<a href="{% url wg_view name=wg.acronym %}">Back</a>
|
||||
<input type="submit" value="Add comment"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,5 +1,5 @@
|
|||
{% autoescape off %}
|
||||
{{ text }}
|
||||
|
||||
WG Record URL: {{ url }}
|
||||
WG URL: {{ url }}
|
||||
{% endautoescape %}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
{% block title %}
|
||||
{% if wg %}
|
||||
Edit info on {{ wg.acronym }}
|
||||
Edit WG {{ wg.acronym }}
|
||||
{% else %}
|
||||
Create WG
|
||||
{% endif %}
|
||||
|
@ -42,7 +42,7 @@ form.edit-info #id_comments {
|
|||
{% block content %}
|
||||
{% load ietf_filters %}
|
||||
<h1>{% if wg %}
|
||||
Edit info on {{ wg.acronym }}
|
||||
Edit WG {{ wg.acronym }}
|
||||
{% else %}
|
||||
Create WG
|
||||
{% endif %}
|
||||
|
@ -55,7 +55,7 @@ Create WG
|
|||
<th>{{ field.label_tag }}:</th>
|
||||
<td>{{ field }}
|
||||
{% ifequal field.name "ad" %}
|
||||
{% if user|in_group:"Area_Director" %}
|
||||
{% if user|has_role:"Area Director" %}
|
||||
<label><input type="checkbox" name="ad" value="{{ login.pk }}" /> Assign to me</label>
|
||||
{% endif %}
|
||||
{% endifequal %}
|
||||
|
@ -68,7 +68,7 @@ Create WG
|
|||
<td></td>
|
||||
<td class="actions">
|
||||
{% if wg %}
|
||||
<a href="{% url wg_view name=wg.acronym %}">Back</a>
|
||||
<a href="{% url wg_charter acronym=wg.acronym %}">Back</a>
|
||||
<input type="submit" value="Save"/>
|
||||
{% else %}
|
||||
<input type="submit" value="Create"/>
|
||||
|
@ -79,24 +79,8 @@ Create WG
|
|||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
$(document).ready(function () {
|
||||
var chairs = eval($("#id_chairs").val()),
|
||||
secretaries = eval($("#id_secretaries").val()),
|
||||
techadv = eval($("#id_techadv").val());
|
||||
$("#id_chairs").tokenInput("/wgcharter/searchPerson/", { hintText: "",
|
||||
preventDuplicates: true,
|
||||
prePopulate: chairs });
|
||||
$("#id_secretaries").tokenInput("/wgcharter/searchPerson/", { hintText: "",
|
||||
preventDuplicates: true,
|
||||
prePopulate: secretaries });
|
||||
$("#id_techadv").tokenInput("/wgcharter/searchPerson/", { hintText: "",
|
||||
preventDuplicates: true,
|
||||
prePopulate: techadv });
|
||||
$("#id_name").focus();
|
||||
});
|
||||
{% endblock %}
|
||||
|
||||
{% block content_end %}
|
||||
<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>
|
||||
{% endblock %}
|
|
@ -4,7 +4,7 @@ from django.conf import settings
|
|||
from django.contrib.syndication.feeds import Feed, FeedDoesNotExist
|
||||
from django.utils.feedgenerator import Atom1Feed
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
from ietf.utils.history import find_history_active_at
|
||||
|
||||
from ietf.group.models import Group
|
||||
|
@ -19,16 +19,16 @@ class GroupComments(Feed):
|
|||
description_template = "feeds/wg_charter_description.html"
|
||||
def get_object(self, bits):
|
||||
if len(bits) != 1:
|
||||
raise ObjectDoesNotExist
|
||||
raise Group.DoesNotExist
|
||||
return Group.objects.get(acronym=bits[0])
|
||||
|
||||
def title(self, obj):
|
||||
return "WG Record changes for %s" % obj.acronym
|
||||
return "WG changes for %s" % obj.acronym
|
||||
|
||||
def link(self, obj):
|
||||
if obj is None:
|
||||
if not obj:
|
||||
raise FeedDoesNotExist
|
||||
return reverse('wg_view', kwargs={'name': obj.acronym})
|
||||
return urlreverse('wg_charter', kwargs={'acronym': obj.acronym})
|
||||
|
||||
def description(self, obj):
|
||||
return self.title(obj)
|
||||
|
@ -59,7 +59,7 @@ class GroupComments(Feed):
|
|||
return history
|
||||
|
||||
def item_link(self, obj):
|
||||
return reverse('wg_view', kwargs={'name': obj['group'].acronym})
|
||||
return urlreverse('wg_charter', kwargs={'acronym': obj['group'].acronym})
|
||||
|
||||
def item_pubdate(self, obj):
|
||||
return obj['date']
|
||||
|
|
|
@ -14,26 +14,26 @@ from ietf.ipr.search import iprs_from_docs
|
|||
from ietf.doc.models import WriteupDocEvent, DocAlias, GroupBallotPositionDocEvent
|
||||
from ietf.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"
|
||||
types['state-iesgrev'] = "State changed to IESG review"
|
||||
types['state-approved'] = "Charter approved"
|
||||
types['conclude'] = "Request closing of WG"
|
||||
|
||||
def email_secretariat(request, wg, type, text):
|
||||
to = ["iesg-secretary@ietf.org"]
|
||||
|
||||
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"
|
||||
types['state-iesgrev'] = "State changed to IESG review"
|
||||
types['state-approved'] = "Charter approved"
|
||||
types['conclude'] = "Request closing of WG"
|
||||
|
||||
subject = u"Regarding WG %s: %s" % (wg.acronym, types[type])
|
||||
|
||||
text = strip_tags(text)
|
||||
send_mail(request, to, None,
|
||||
"Regarding WG %s: %s" % (wg.acronym, types[type]),
|
||||
send_mail(request, to, None, subject,
|
||||
"wgcharter/email_secretariat.txt",
|
||||
dict(text=text,
|
||||
url=settings.IDTRACKER_BASE_URL + urlreverse('wg_view', kwargs=dict(name=wg.acronym))))
|
||||
url=settings.IDTRACKER_BASE_URL + urlreverse('wg_charter', kwargs=dict(acronym=wg.acronym))))
|
||||
|
||||
def generate_ballot_writeup(request, doc):
|
||||
e = WriteupDocEvent()
|
||||
|
|
|
@ -5,6 +5,7 @@ from StringIO import StringIO
|
|||
|
||||
import django.test
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
from ietf.utils.mail import outbox
|
||||
from ietf.utils.test_data import make_test_data
|
||||
from ietf.utils.test_utils import login_testing_unauthorized
|
||||
|
@ -120,34 +121,6 @@ class WgStateTestCase(django.test.TestCase):
|
|||
else:
|
||||
self.assertTrue("State changed" in outbox[-1]['Subject'])
|
||||
|
||||
def test_conclude(self):
|
||||
make_test_data()
|
||||
|
||||
# And make a charter for group
|
||||
group = Group.objects.get(acronym="mars")
|
||||
|
||||
# -- Test conclude WG --
|
||||
url = urlreverse('wg_conclude', kwargs=dict(name=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEquals(len(q('form textarea[name=instructions]')), 1)
|
||||
|
||||
# faulty post
|
||||
r = self.client.post(url, dict(instructions="")) # No instructions
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
|
||||
# conclusion request
|
||||
r = self.client.post(url, dict(instructions="Test instructions"))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
# The WG remains active until the state is set to conclude via change_state
|
||||
group = Group.objects.get(acronym=group.acronym)
|
||||
self.assertEquals(group.state_id, "active")
|
||||
|
||||
class WgInfoTestCase(django.test.TestCase):
|
||||
fixtures = ['names']
|
||||
|
@ -160,106 +133,6 @@ class WgInfoTestCase(django.test.TestCase):
|
|||
def tearDown(self):
|
||||
shutil.rmtree(self.charter_dir)
|
||||
|
||||
def test_create(self):
|
||||
make_test_data()
|
||||
|
||||
# -- Test WG creation --
|
||||
url = urlreverse('wg_create')
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
num_wgs = len(Group.objects.filter(type="wg"))
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEquals(len(q('form input[name=acronym]')), 1)
|
||||
|
||||
# faulty post
|
||||
r = self.client.post(url, dict(acronym="foobarbaz")) # No name
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertEquals(len(Group.objects.filter(type="wg")), num_wgs)
|
||||
|
||||
# creation
|
||||
r = self.client.post(url, dict(acronym="testwg", name="Testing WG"))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
self.assertEquals(len(Group.objects.filter(type="wg")), num_wgs + 1)
|
||||
group = Group.objects.get(acronym="testwg")
|
||||
self.assertEquals(group.name, "Testing WG")
|
||||
# check that a charter was created with the correct name
|
||||
self.assertEquals(group.charter.name, "charter-ietf-testwg")
|
||||
# and that it has no revision
|
||||
self.assertEquals(group.charter.rev, "")
|
||||
|
||||
|
||||
def test_edit_info(self):
|
||||
make_test_data()
|
||||
|
||||
# And make a charter for group
|
||||
group = Group.objects.get(acronym="mars")
|
||||
|
||||
url = urlreverse('wg_edit_info', kwargs=dict(name=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEquals(len(q('form select[name=parent]')), 1)
|
||||
self.assertEquals(len(q('form input[name=acronym]')), 1)
|
||||
|
||||
# faulty post
|
||||
Group.objects.create(name="Collision Test Group", acronym="collide")
|
||||
r = self.client.post(url, dict(acronym="collide"))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
|
||||
# Create old acronym
|
||||
group.acronym = "oldmars"
|
||||
group.save()
|
||||
save_group_in_history(group)
|
||||
group.acronym = "mars"
|
||||
group.save()
|
||||
|
||||
# post with warning
|
||||
r = self.client.post(url, dict(acronym="oldmars"))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
|
||||
# edit info
|
||||
area = group.parent
|
||||
ad = Person.objects.get(name="Aread Irector")
|
||||
r = self.client.post(url,
|
||||
dict(name="Mars Not Special Interest Group",
|
||||
acronym="mnsig",
|
||||
parent=area.pk,
|
||||
ad=ad.pk,
|
||||
chairs="aread@ietf.org, ad1@ietf.org",
|
||||
secretaries="aread@ietf.org, ad1@ietf.org, ad2@ietf.org",
|
||||
techadv="aread@ietf.org",
|
||||
list_email="mars@mail",
|
||||
list_subscribe="subscribe.mars",
|
||||
list_archive="archive.mars",
|
||||
urls="http://mars.mars (MARS site)"
|
||||
))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
|
||||
group = Group.objects.get(acronym="mnsig")
|
||||
self.assertEquals(group.name, "Mars Not Special Interest Group")
|
||||
self.assertEquals(group.parent, area)
|
||||
self.assertEquals(group.ad, ad)
|
||||
for k in ("chair", "secr", "techadv"):
|
||||
self.assertTrue(group.role_set.filter(name=k, email__address="aread@ietf.org"))
|
||||
self.assertEquals(group.list_email, "mars@mail")
|
||||
self.assertEquals(group.list_subscribe, "subscribe.mars")
|
||||
self.assertEquals(group.list_archive, "archive.mars")
|
||||
self.assertEquals(group.groupurl_set.all()[0].url, "http://mars.mars")
|
||||
self.assertEquals(group.groupurl_set.all()[0].name, "MARS site")
|
||||
|
||||
def test_edit_telechat_date(self):
|
||||
make_test_data()
|
||||
|
||||
|
@ -324,31 +197,6 @@ class WgInfoTestCase(django.test.TestCase):
|
|||
self.assertEquals(charter.rev, next_revision(prev_rev))
|
||||
self.assertTrue("new_revision" in charter.latest_event().type)
|
||||
|
||||
class WgAddCommentTestCase(django.test.TestCase):
|
||||
fixtures = ['names']
|
||||
|
||||
def test_add_comment(self):
|
||||
make_test_data()
|
||||
|
||||
group = Group.objects.get(acronym="mars")
|
||||
url = urlreverse('wg_add_comment', kwargs=dict(name=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEquals(len(q('form textarea[name=comment]')), 1)
|
||||
|
||||
# request resurrect
|
||||
comments_before = group.groupevent_set.filter(type="added_comment").count()
|
||||
|
||||
r = self.client.post(url, dict(comment="This is a test."))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
|
||||
self.assertEquals(group.groupevent_set.filter(type="added_comment").count(), comments_before + 1)
|
||||
self.assertTrue("This is a test." in group.groupevent_set.filter(type="added_comment").order_by('-time')[0].desc)
|
||||
|
||||
class WgEditPositionTestCase(django.test.TestCase):
|
||||
fixtures = ['names', 'ballot']
|
||||
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
# Copyright The IETF Trust 2011, All Rights Reserved
|
||||
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
from ietf.wgcharter import views, views_search, views_edit, views_ballot, views_submit
|
||||
from ietf.wgcharter import views, views_edit, views_ballot, views_submit
|
||||
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('',
|
||||
url(r'^create/$', views_edit.edit_info, name="wg_create"),
|
||||
(r'^searchPerson/$', views.search_person), # FIXME: fix URL
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/((?P<rev>[0-9][0-9](-[0-9][0-9])?)/)?((?P<tab>ballot|writeup|history)/)?$', views.wg_main, name="wg_view"),
|
||||
(r'^(?P<name>[A-Za-z0-9._+-]+)/_ballot.data$', views.wg_ballot),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/state/$', views_edit.change_state, name='wg_change_state'),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/(?P<option>initcharter|recharter|abandon)/$', views_edit.change_state, name='wg_startstop_process'),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/telechat/$', views_edit.telechat_date, name='charter_telechat_date'),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/info/$', views_edit.edit_info, name='wg_edit_info'),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/conclude/$', views_edit.conclude, name='wg_conclude'),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/(?P<ann>action|review)/$', views_ballot.announcement_text, name='wg_announcement_text'),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/position/$', views_ballot.edit_position, name='wg_edit_position'),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/sendballotcomment/$', views_ballot.send_ballot_comment, name='wg_send_ballot_comment'),
|
||||
|
|
|
@ -29,47 +29,6 @@ def set_or_create_charter(wg):
|
|||
wg.save()
|
||||
return charter
|
||||
|
||||
def save_charter_in_history(charter):
|
||||
'''This is a modified save_document_in_history that save the name
|
||||
as charter-ietf-wgacronym with wgacronym being the current Group
|
||||
acronym. The charter Document may have an old name which is no longer
|
||||
in use'''
|
||||
def get_model_fields_as_dict(obj):
|
||||
return dict((field.name, getattr(obj, field.name))
|
||||
for field in obj._meta.fields
|
||||
if field is not obj._meta.pk)
|
||||
|
||||
# copy fields
|
||||
fields = get_model_fields_as_dict(charter)
|
||||
fields["doc"] = charter
|
||||
fields["name"] = 'charter-ietf-%s' % charter.chartered_group.acronym
|
||||
|
||||
chist = DocHistory(**fields)
|
||||
chist.save()
|
||||
|
||||
# copy many to many
|
||||
for field in charter._meta.many_to_many:
|
||||
if field.rel.through and field.rel.through._meta.auto_created:
|
||||
setattr(chist, field.name, getattr(charter, field.name).all())
|
||||
|
||||
# copy remaining tricky many to many
|
||||
def transfer_fields(obj, HistModel):
|
||||
mfields = get_model_fields_as_dict(item)
|
||||
# map charter -> chist
|
||||
for k, v in mfields.iteritems():
|
||||
if v == charter:
|
||||
mfields[k] = chist
|
||||
HistModel.objects.create(**mfields)
|
||||
|
||||
for item in RelatedDocument.objects.filter(source=charter):
|
||||
transfer_fields(item, RelatedDocHistory)
|
||||
|
||||
for item in DocumentAuthor.objects.filter(document=charter):
|
||||
transfer_fields(item, DocHistoryAuthor)
|
||||
|
||||
return chist
|
||||
|
||||
|
||||
def add_wg_comment(request, wg, text, ballot=None):
|
||||
if request:
|
||||
login = request.user.get_profile()
|
||||
|
@ -103,13 +62,6 @@ def log_group_state_changed(request, wg, by, note=''):
|
|||
e.save()
|
||||
return e
|
||||
|
||||
def log_info_changed(request, wg, by, change):
|
||||
e = GroupEvent(group=wg, by=by)
|
||||
e.type = "info_changed"
|
||||
e.desc = change
|
||||
e.save()
|
||||
return e
|
||||
|
||||
def get_charter_for_revision(charter, r):
|
||||
if r == None:
|
||||
return None
|
||||
|
|
|
@ -243,14 +243,3 @@ def wg_ballot(request, name):
|
|||
info['pos_no_record'] = no_record
|
||||
return render_to_response('wgcharter/wg_ballot.html', {'info':info, 'wg':wg, 'doc': doc}, context_instance=RequestContext(request))
|
||||
|
||||
|
||||
def json_emails(l):
|
||||
result = []
|
||||
for p in l:
|
||||
result.append({"id": p.address + "", "name": p.person.plain_name() + " <" + p.address + ">"})
|
||||
return simplejson.dumps(result)
|
||||
|
||||
def search_person(request):
|
||||
if request.method == 'GET':
|
||||
emails = Email.objects.filter(person__name__istartswith=request.GET.get('q','')).order_by('person__name')
|
||||
return HttpResponse(json_emails(emails), mimetype='application/json')
|
||||
|
|
|
@ -14,82 +14,78 @@ from django.conf import settings
|
|||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
from ietf.utils.mail import send_mail_text, send_mail_preformatted
|
||||
from ietf.ietfauth.decorators import group_required
|
||||
from ietf.idtracker.templatetags.ietf_filters import in_group
|
||||
from ietf.ietfauth.decorators import has_role
|
||||
from mails import email_secretariat, generate_ballot_writeup, generate_issue_ballot_mail
|
||||
|
||||
from utils import *
|
||||
from ietf.ietfauth.decorators import has_role, role_required
|
||||
from ietf.wgcharter.mails import email_secretariat, generate_ballot_writeup, generate_issue_ballot_mail
|
||||
from ietf.wgcharter.utils import *
|
||||
from ietf.group.models import Group, GroupHistory, GroupEvent
|
||||
from ietf.group.utils import save_group_in_history
|
||||
from ietf.name.models import GroupBallotPositionName, GroupStateName
|
||||
from ietf.doc.models import *
|
||||
|
||||
def default_action_text(wg, charter, user, action):
|
||||
e = WriteupDocEvent(doc=charter, by=user)
|
||||
e.by = user
|
||||
e.type = "changed_action_announcement"
|
||||
e.desc = "WG action text was changed"
|
||||
e = WriteupDocEvent(doc=charter, by=user)
|
||||
e.by = user
|
||||
e.type = "changed_action_announcement"
|
||||
e.desc = "WG action text was changed"
|
||||
|
||||
info = {}
|
||||
info['chairs'] = [{ 'name': x.person.plain_name(), 'email': x.email.address} for x in wg.role_set.filter(name="Chair")]
|
||||
info['secr'] = [{ 'name': x.person.plain_name(), 'email': x.email.address} for x in wg.role_set.filter(name="Secr")]
|
||||
info['techadv'] = [{ 'name': x.person.plain_name(), 'email': x.email.address} for x in wg.role_set.filter(name="Techadv")]
|
||||
info['ad'] = {'name': wg.ad.plain_name(), 'email': wg.ad.role_email("ad").address } if wg.ad else None,
|
||||
info['list'] = wg.list_email if wg.list_email else None,
|
||||
info['list_subscribe'] = str(wg.list_subscribe) if wg.list_subscribe else None,
|
||||
info['list_archive'] = str(wg.list_archive) if wg.list_archive else None,
|
||||
info = {}
|
||||
info['chairs'] = [{ 'name': x.person.plain_name(), 'email': x.email.address} for x in wg.role_set.filter(name="Chair")]
|
||||
info['secr'] = [{ 'name': x.person.plain_name(), 'email': x.email.address} for x in wg.role_set.filter(name="Secr")]
|
||||
info['techadv'] = [{ 'name': x.person.plain_name(), 'email': x.email.address} for x in wg.role_set.filter(name="Techadv")]
|
||||
info['ad'] = {'name': wg.ad.plain_name(), 'email': wg.ad.role_email("ad").address } if wg.ad else None,
|
||||
info['list'] = wg.list_email if wg.list_email else None,
|
||||
info['list_subscribe'] = str(wg.list_subscribe) if wg.list_subscribe else None,
|
||||
info['list_archive'] = str(wg.list_archive) if wg.list_archive else None,
|
||||
|
||||
filename = os.path.join(settings.CHARTER_PATH, 'charter-ietf-%s-%s.txt' % (wg.acronym, wg.charter.rev))
|
||||
try:
|
||||
charter_text = open(filename, 'r')
|
||||
info['charter_txt'] = charter_text.read()
|
||||
except IOError:
|
||||
info['charter_txt'] = "Error: couldn't read charter text"
|
||||
filename = os.path.join(settings.CHARTER_PATH, 'charter-ietf-%s-%s.txt' % (wg.acronym, wg.charter.rev))
|
||||
try:
|
||||
charter_text = open(filename, 'r')
|
||||
info['charter_txt'] = charter_text.read()
|
||||
except IOError:
|
||||
info['charter_txt'] = "Error: couldn't read charter text"
|
||||
|
||||
e.text = render_to_string("wgcharter/action_text.txt",
|
||||
dict(wg=wg,
|
||||
charter_url=settings.IDTRACKER_BASE_URL + charter.get_absolute_url(),
|
||||
action_type=action,
|
||||
info=info,
|
||||
)
|
||||
)
|
||||
e.text = render_to_string("wgcharter/action_text.txt",
|
||||
dict(wg=wg,
|
||||
charter_url=settings.IDTRACKER_BASE_URL + charter.get_absolute_url(),
|
||||
action_type=action,
|
||||
info=info,
|
||||
))
|
||||
|
||||
e.save()
|
||||
return e
|
||||
e.save()
|
||||
return e
|
||||
|
||||
def default_review_text(wg, charter, user):
|
||||
e = WriteupDocEvent(doc=charter, by=user)
|
||||
e.by = user
|
||||
e.type = "changed_review_announcement"
|
||||
e.desc = "WG review text was changed"
|
||||
info = {}
|
||||
info['chairs'] = [{ 'name': x.person.plain_name(), 'email': x.email.address} for x in wg.role_set.filter(name="Chair")]
|
||||
info['secr'] = [{ 'name': x.person.plain_name(), 'email': x.email.address} for x in wg.role_set.filter(name="Secr")]
|
||||
info['techadv'] = [{ 'name': x.person.plain_name(), 'email': x.email.address} for x in wg.role_set.filter(name="Techadv")]
|
||||
info['ad'] = {'name': wg.ad.plain_name(), 'email': wg.ad.role_email("ad").address } if wg.ad else None,
|
||||
info['list'] = wg.list_email if wg.list_email else None,
|
||||
info['list_subscribe'] = wg.list_subscribe if wg.list_subscribe else None,
|
||||
info['list_archive'] = wg.list_archive if wg.list_archive else None,
|
||||
e = WriteupDocEvent(doc=charter, by=user)
|
||||
e.by = user
|
||||
e.type = "changed_review_announcement"
|
||||
e.desc = "WG review text was changed"
|
||||
info = {}
|
||||
info['chairs'] = [{ 'name': x.person.plain_name(), 'email': x.email.address} for x in wg.role_set.filter(name="Chair")]
|
||||
info['secr'] = [{ 'name': x.person.plain_name(), 'email': x.email.address} for x in wg.role_set.filter(name="Secr")]
|
||||
info['techadv'] = [{ 'name': x.person.plain_name(), 'email': x.email.address} for x in wg.role_set.filter(name="Techadv")]
|
||||
info['ad'] = {'name': wg.ad.plain_name(), 'email': wg.ad.role_email("ad").address } if wg.ad else None,
|
||||
info['list'] = wg.list_email if wg.list_email else None,
|
||||
info['list_subscribe'] = wg.list_subscribe if wg.list_subscribe else None,
|
||||
info['list_archive'] = wg.list_archive if wg.list_archive else None,
|
||||
|
||||
info['bydate'] = (date.today() + timedelta(weeks=1)).isoformat()
|
||||
info['bydate'] = (date.today() + timedelta(weeks=1)).isoformat()
|
||||
|
||||
filename = os.path.join(settings.CHARTER_PATH, 'charter-ietf-%s-%s.txt' % (wg.acronym, wg.charter.rev))
|
||||
try:
|
||||
charter_text = open(filename, 'r')
|
||||
info['charter_txt'] = charter_text.read()
|
||||
except IOError:
|
||||
info['charter_txt'] = "Error: couldn't read charter text"
|
||||
filename = os.path.join(settings.CHARTER_PATH, 'charter-ietf-%s-%s.txt' % (wg.acronym, wg.charter.rev))
|
||||
try:
|
||||
charter_text = open(filename, 'r')
|
||||
info['charter_txt'] = charter_text.read()
|
||||
except IOError:
|
||||
info['charter_txt'] = "Error: couldn't read charter text"
|
||||
|
||||
e.text = render_to_string("wgcharter/review_text.txt",
|
||||
dict(wg=wg,
|
||||
charter_url=settings.IDTRACKER_BASE_URL + charter.get_absolute_url(),
|
||||
info=info,
|
||||
review_type="new" if wg.state_id == "proposed" else "recharter",
|
||||
)
|
||||
)
|
||||
e.save()
|
||||
return e
|
||||
e.text = render_to_string("wgcharter/review_text.txt",
|
||||
dict(wg=wg,
|
||||
charter_url=settings.IDTRACKER_BASE_URL + charter.get_absolute_url(),
|
||||
info=info,
|
||||
review_type="new" if wg.state_id == "proposed" else "recharter",
|
||||
)
|
||||
)
|
||||
e.save()
|
||||
return e
|
||||
|
||||
BALLOT_CHOICES = (("yes", "Yes"),
|
||||
("no", "No"),
|
||||
|
@ -114,13 +110,13 @@ class EditPositionForm(forms.Form):
|
|||
return_to_url = forms.CharField(required=False, widget=forms.HiddenInput)
|
||||
|
||||
def clean_blocking(self):
|
||||
entered_blocking = self.cleaned_data["block_comment"]
|
||||
entered_pos = self.cleaned_data["position"]
|
||||
if entered_pos.slug == "block" and not entered_blocking:
|
||||
raise forms.ValidationError("You must enter a non-empty blocking comment")
|
||||
return entered_blocking
|
||||
entered_blocking = self.cleaned_data["block_comment"]
|
||||
entered_pos = self.cleaned_data["position"]
|
||||
if entered_pos.slug == "block" and not entered_blocking:
|
||||
raise forms.ValidationError("You must enter a non-empty blocking comment")
|
||||
return entered_blocking
|
||||
|
||||
@group_required('Area_Director','Secretariat')
|
||||
@role_required('Area Director','Secretariat')
|
||||
def edit_position(request, name):
|
||||
"""Vote and edit comments on Charter as Area Director."""
|
||||
try:
|
||||
|
@ -135,7 +131,7 @@ def edit_position(request, name):
|
|||
charter = set_or_create_charter(wg)
|
||||
started_process = charter.latest_event(type="started_iesg_process")
|
||||
if not started_process:
|
||||
raise Http404
|
||||
raise Http404
|
||||
|
||||
ad = login = request.user.get_profile()
|
||||
|
||||
|
@ -157,12 +153,12 @@ def edit_position(request, name):
|
|||
if request.method == 'POST':
|
||||
form = EditPositionForm(request.POST)
|
||||
if form.is_valid():
|
||||
|
||||
|
||||
# save the vote
|
||||
clean = form.cleaned_data
|
||||
|
||||
if clean['return_to_url']:
|
||||
return_to_url = clean['return_to_url']
|
||||
return_to_url = clean['return_to_url']
|
||||
|
||||
pos = GroupBallotPositionDocEvent(doc=charter, by=login)
|
||||
pos.type = "changed_ballot_position"
|
||||
|
@ -219,7 +215,7 @@ def edit_position(request, name):
|
|||
|
||||
for e in added_events:
|
||||
e.save() # save them after the position is saved to get later id
|
||||
|
||||
|
||||
charter.time = pos.time
|
||||
charter.save()
|
||||
|
||||
|
@ -252,7 +248,7 @@ def edit_position(request, name):
|
|||
),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
@group_required('Area_Director','Secretariat')
|
||||
@role_required('Area Director','Secretariat')
|
||||
def send_ballot_comment(request, name):
|
||||
"""Email Charter ballot comment for area director."""
|
||||
try:
|
||||
|
@ -311,13 +307,13 @@ def send_ballot_comment(request, name):
|
|||
dict(block_comment=d, comment=c, ad=ad.plain_name(), charter=charter, pos=pos.pos))
|
||||
frm = ad.formatted_email()
|
||||
to = "The IESG <iesg@ietf.org>"
|
||||
|
||||
|
||||
if request.method == 'POST':
|
||||
cc = [x.strip() for x in request.POST.get("cc", "").split(',') if x.strip()]
|
||||
send_mail_text(request, to, frm, subject, body, cc=", ".join(cc))
|
||||
|
||||
|
||||
return HttpResponseRedirect(return_to_url)
|
||||
|
||||
|
||||
return render_to_response('wgcharter/send_ballot_comment.html',
|
||||
dict(charter=charter,
|
||||
subject=subject,
|
||||
|
@ -327,16 +323,16 @@ def send_ballot_comment(request, name):
|
|||
ad=ad,
|
||||
can_send=d or c,
|
||||
back_url=back_url,
|
||||
),
|
||||
),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
|
||||
class AnnouncementTextForm(forms.Form):
|
||||
announcement_text = forms.CharField(widget=forms.Textarea, required=True)
|
||||
|
||||
def clean_announcement_text(self):
|
||||
return self.cleaned_data["announcement_text"].replace("\r", "")
|
||||
|
||||
@group_required('Area_Director','Secretariat')
|
||||
@role_required('Area Director','Secretariat')
|
||||
def announcement_text(request, name, ann):
|
||||
"""Editing of announcement text"""
|
||||
try:
|
||||
|
@ -358,10 +354,10 @@ 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")
|
||||
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")
|
||||
elif ann == "review":
|
||||
existing = default_review_text(wg, charter, login)
|
||||
|
||||
|
@ -381,28 +377,28 @@ def announcement_text(request, name, ann):
|
|||
|
||||
charter.time = e.time
|
||||
charter.save()
|
||||
return redirect('wg_view', name=wg.acronym)
|
||||
return redirect('doc_writeup', name=charter.name)
|
||||
|
||||
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")
|
||||
elif ann == "review":
|
||||
e = default_review_text(wg, charter, login)
|
||||
# make sure form has the updated text
|
||||
form = AnnouncementTextForm(initial=dict(announcement_text=e.text))
|
||||
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")
|
||||
elif ann == "review":
|
||||
e = default_review_text(wg, charter, login)
|
||||
# make sure form has the updated text
|
||||
form = AnnouncementTextForm(initial=dict(announcement_text=e.text))
|
||||
|
||||
if "send_text" in request.POST and form.is_valid():
|
||||
msg = form.cleaned_data['announcement_text']
|
||||
import email
|
||||
parsed_msg = email.message_from_string(msg.encode("utf-8"))
|
||||
msg = form.cleaned_data['announcement_text']
|
||||
import email
|
||||
parsed_msg = email.message_from_string(msg.encode("utf-8"))
|
||||
|
||||
send_mail_text(request, parsed_msg["To"],
|
||||
parsed_msg["From"], parsed_msg["Subject"],
|
||||
parsed_msg.get_payload())
|
||||
return redirect('wg_view', name=wg.acronym)
|
||||
send_mail_text(request, parsed_msg["To"],
|
||||
parsed_msg["From"], parsed_msg["Subject"],
|
||||
parsed_msg.get_payload())
|
||||
return redirect('doc_writeup', name=charter.name)
|
||||
|
||||
return render_to_response('wgcharter/announcement_text.html',
|
||||
dict(charter=charter,
|
||||
|
@ -418,7 +414,7 @@ class BallotWriteupForm(forms.Form):
|
|||
def clean_ballot_writeup(self):
|
||||
return self.cleaned_data["ballot_writeup"].replace("\r", "")
|
||||
|
||||
@group_required('Area_Director','Secretariat')
|
||||
@role_required('Area Director','Secretariat')
|
||||
def ballot_writeupnotes(request, name):
|
||||
"""Editing of ballot write-up and notes"""
|
||||
try:
|
||||
|
@ -495,7 +491,7 @@ def ballot_writeupnotes(request, name):
|
|||
),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
@group_required('Secretariat')
|
||||
@role_required('Secretariat')
|
||||
def approve_ballot(request, name):
|
||||
"""Approve ballot, changing state, copying charter"""
|
||||
try:
|
||||
|
@ -513,10 +509,10 @@ def approve_ballot(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
|
||||
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
|
||||
else:
|
||||
announcement = e.text
|
||||
|
||||
|
@ -524,7 +520,7 @@ def approve_ballot(request, name):
|
|||
new_state = GroupStateName.objects.get(slug="active")
|
||||
new_charter_state = State.objects.get(type="charter", slug="approved")
|
||||
|
||||
save_charter_in_history(charter)
|
||||
save_document_in_history(charter)
|
||||
save_group_in_history(wg)
|
||||
|
||||
prev_state = wg.state
|
||||
|
@ -540,7 +536,7 @@ def approve_ballot(request, name):
|
|||
change_description = e.desc + " and WG state has been changed to %s" % new_state.name
|
||||
|
||||
e = log_state_changed(request, charter, login, prev_state)
|
||||
|
||||
|
||||
wg.time = e.time
|
||||
wg.save()
|
||||
|
||||
|
@ -548,15 +544,15 @@ def approve_ballot(request, name):
|
|||
|
||||
filename = os.path.join(charter.get_file_path(), ch.name+"-"+ch.rev+".txt")
|
||||
try:
|
||||
source = open(filename, 'rb')
|
||||
raw_content = source.read()
|
||||
source = open(filename, 'rb')
|
||||
raw_content = source.read()
|
||||
|
||||
new_filename = os.path.join(charter.get_file_path(), 'charter-ietf-%s-%s.txt' % (wg.acronym, next_approved_revision(ch.rev)))
|
||||
destination = open(new_filename, 'wb+')
|
||||
destination.write(raw_content)
|
||||
destination.close()
|
||||
new_filename = os.path.join(charter.get_file_path(), 'charter-ietf-%s-%s.txt' % (wg.acronym, next_approved_revision(ch.rev)))
|
||||
destination = open(new_filename, 'wb+')
|
||||
destination.write(raw_content)
|
||||
destination.close()
|
||||
except IOError:
|
||||
raise Http404("Charter text %s" % filename)
|
||||
raise Http404("Charter text %s" % filename)
|
||||
|
||||
charter.rev = next_approved_revision(charter.rev)
|
||||
charter.save()
|
||||
|
@ -567,7 +563,7 @@ def approve_ballot(request, name):
|
|||
send_mail_preformatted(request, announcement)
|
||||
|
||||
return HttpResponseRedirect(charter.get_absolute_url())
|
||||
|
||||
|
||||
return render_to_response('wgcharter/approve_ballot.html',
|
||||
dict(charter=charter,
|
||||
announcement=announcement,
|
||||
|
|
|
@ -20,14 +20,12 @@ from ietf.person.models import *
|
|||
from ietf.group.models import *
|
||||
from ietf.group.utils import save_group_in_history
|
||||
|
||||
from views_search import json_emails
|
||||
|
||||
class ChangeStateForm(forms.Form):
|
||||
charter_state = forms.ModelChoiceField(State.objects.filter(type="charter"), label="Charter state", empty_label=None, required=False)
|
||||
confirm_state = forms.BooleanField(widget=forms.HiddenInput, required=False, initial=True)
|
||||
initial_time = forms.IntegerField(initial=0, label="Review time", help_text="(in weeks)", required=False)
|
||||
message = forms.CharField(widget=forms.Textarea, help_text="Message to the secretariat", required=False)
|
||||
comment = forms.CharField(widget=forms.Textarea, help_text="Comment for the WG history", required=False)
|
||||
message = forms.CharField(widget=forms.Textarea, help_text="Message to the Secretariat", required=False)
|
||||
comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the charter history", required=False)
|
||||
def __init__(self, *args, **kwargs):
|
||||
qs = kwargs.pop('queryset', None)
|
||||
self.hide = kwargs.pop('hide', None)
|
||||
|
@ -89,7 +87,7 @@ def change_state(request, name, option=None):
|
|||
if charter_state != charter.get_state():
|
||||
# Charter state changed
|
||||
change = True
|
||||
save_charter_in_history(charter)
|
||||
save_document_in_history(charter)
|
||||
|
||||
prev = charter.get_state()
|
||||
charter.set_state(charter_state)
|
||||
|
@ -99,14 +97,12 @@ def change_state(request, name, option=None):
|
|||
e = log_state_changed(request, charter, login, prev)
|
||||
else:
|
||||
# Special log for abandoned efforts
|
||||
e = DocEvent(doc=charter, by=login)
|
||||
e.type = "changed_document"
|
||||
e = DocEvent(type="changed_document", doc=charter, by=login)
|
||||
e.desc = "IESG has abandoned the chartering effort"
|
||||
e.save()
|
||||
|
||||
if comment:
|
||||
c = DocEvent(type="added_comment", by=login)
|
||||
c.doc = doc
|
||||
c = DocEvent(type="added_comment", doc=charter, by=login)
|
||||
c.desc = comment
|
||||
c.save()
|
||||
|
||||
|
@ -230,264 +226,3 @@ def telechat_date(request, name):
|
|||
user=request.user,
|
||||
login=login),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
def parse_emails_string(s):
|
||||
return Email.objects.filter(address__in=[x.strip() for x in s.split(",") if x.strip()]).select_related("person")
|
||||
|
||||
class EditInfoForm(forms.Form):
|
||||
name = forms.CharField(max_length=255, label="WG Name", required=True)
|
||||
acronym = forms.CharField(max_length=8, label="WG Acronym", required=True)
|
||||
chairs = forms.CharField(max_length=255, label="WG Chairs", help_text="Type in a name", required=False)
|
||||
secretaries = forms.CharField(max_length=255, label="WG Secretaries", help_text="Type in a name", required=False)
|
||||
techadv = forms.CharField(max_length=255, label="WG Technical Advisors", help_text="Type in a name", required=False)
|
||||
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active").order_by('name'), label="Shepherding AD", empty_label="-", required=False)
|
||||
parent = forms.ModelChoiceField(Group.objects.filter(type="area", state="active").order_by('name'), label="IETF Area", empty_label="-", 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/url (optional description). Separate multiple entries with newline.", required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.cur_acronym = kwargs.pop('cur_acronym')
|
||||
self.hide = kwargs.pop('hide', None)
|
||||
super(self.__class__, self).__init__(*args, **kwargs)
|
||||
|
||||
# hide requested fields
|
||||
if self.hide:
|
||||
for f in self.hide:
|
||||
self.fields[f].widget = forms.HiddenInput
|
||||
|
||||
# if previous AD is now ex-AD, append that person to the list
|
||||
ad_pk = self.initial.get('ad')
|
||||
choices = self.fields['ad'].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())]
|
||||
|
||||
def clean_acronym(self):
|
||||
acronym = self.cleaned_data['acronym']
|
||||
if self.cur_acronym and acronym != self.cur_acronym:
|
||||
if Group.objects.filter(acronym__iexact=acronym):
|
||||
raise forms.ValidationError("Acronym used in an existing WG. Please pick another.")
|
||||
if GroupHistory.objects.filter(acronym__iexact=acronym):
|
||||
raise forms.ValidationError("Acronym used in a previous WG. Please pick another.")
|
||||
return acronym
|
||||
|
||||
def clean_chairs(self):
|
||||
return parse_emails_string(self.cleaned_data["chairs"])
|
||||
|
||||
def clean_secretaries(self):
|
||||
return parse_emails_string(self.cleaned_data["secretaries"])
|
||||
|
||||
def clean_techadv(self):
|
||||
return parse_emails_string(self.cleaned_data["techadv"])
|
||||
|
||||
def clean_urls(self):
|
||||
return [x.strip() for x in self.cleaned_data["urls"].splitlines() if x.strip()]
|
||||
|
||||
def format_urls(urls, fs="\n"):
|
||||
res = []
|
||||
for u in urls:
|
||||
if u.name:
|
||||
res.append(u"%s (%s)" % (u.url, u.name))
|
||||
else:
|
||||
res.append(u.url)
|
||||
return fs.join(res)
|
||||
|
||||
@group_required('Area_Director','Secretariat')
|
||||
def edit_info(request, name=None):
|
||||
"""Edit or create a WG, notifying parties as
|
||||
necessary and logging changes as group events."""
|
||||
if request.path_info == reverse('wg_edit_info', kwargs={'name': name}):
|
||||
# Editing. Get group
|
||||
wg = get_object_or_404(Group, acronym=name)
|
||||
charter = set_or_create_charter(wg)
|
||||
new_wg = False
|
||||
elif request.path_info == reverse('wg_create'):
|
||||
wg = None
|
||||
new_wg = True
|
||||
|
||||
login = request.user.get_profile()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = EditInfoForm(request.POST, cur_acronym=wg.acronym if wg else None)
|
||||
if form.is_valid():
|
||||
r = form.cleaned_data
|
||||
if not new_wg:
|
||||
gh = save_group_in_history(wg)
|
||||
else:
|
||||
# Create WG
|
||||
wg = Group(name=r["name"],
|
||||
acronym=r["acronym"],
|
||||
type=GroupTypeName.objects.get(name="WG"),
|
||||
state=GroupStateName.objects.get(name="Proposed"))
|
||||
wg.save()
|
||||
|
||||
e = ChangeStateGroupEvent(group=wg, type="changed_state")
|
||||
e.time = datetime.datetime.now()
|
||||
e.by = login
|
||||
e.state_id = "proposed"
|
||||
e.desc = "Proposed group"
|
||||
e.save()
|
||||
|
||||
if not wg.charter:
|
||||
# Create adjoined charter
|
||||
charter = set_or_create_charter(wg)
|
||||
charter.set_state(State.objects.get(type="charter", slug="infrev"))
|
||||
charter.save()
|
||||
|
||||
e = DocEvent(doc=charter, type="started_iesg_process")
|
||||
e.time = datetime.datetime.now()
|
||||
e.by = login
|
||||
e.desc = "Started IESG process on charter"
|
||||
e.save()
|
||||
|
||||
changes = []
|
||||
|
||||
def desc(attr, new, old):
|
||||
entry = "%(attr)s changed to <b>%(new)s</b> from %(old)s"
|
||||
if new_wg:
|
||||
entry = "%(attr)s changed to <b>%(new)s</b>"
|
||||
|
||||
return entry % dict(attr=attr, new=new, old=old)
|
||||
|
||||
def get_model_fields_as_dict(obj):
|
||||
return dict((field.name, getattr(obj, field.name))
|
||||
for field in obj._meta.fields
|
||||
if field is not obj._meta.pk)
|
||||
|
||||
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 attr == "acronym":
|
||||
c = wg.charter
|
||||
save_charter_in_history(c)
|
||||
# and add a DocAlias
|
||||
DocAlias.objects.create(
|
||||
name = "charter-ietf-%s" % r['acronym'],
|
||||
document = charter
|
||||
)
|
||||
|
||||
# update the attributes, keeping track of what we're doing
|
||||
diff('name', "Name")
|
||||
diff('acronym', "Acronym")
|
||||
diff('ad', "Shepherding AD")
|
||||
diff('parent', "IETF Area")
|
||||
diff('list_email', "Mailing list email")
|
||||
diff('list_subscribe', "Mailing list subscribe address")
|
||||
diff('list_archive', "Mailing list archive")
|
||||
|
||||
# update roles
|
||||
for attr, slug in [('chairs', 'chair'), ('secretaries', 'secr'), ('techadv', 'techadv')]:
|
||||
rname = RoleName.objects.get(slug=slug)
|
||||
new = r[attr]
|
||||
old = Email.objects.filter(role__group=wg, role__name=rname).select_related("person")
|
||||
if set(new) != set(old):
|
||||
changes.append(desc(rname.name,
|
||||
",".join(x.get_name() for x in new),
|
||||
",".join(x.get_name() for x in new)))
|
||||
wg.role_set.filter(name=rname).delete()
|
||||
for e in new:
|
||||
Role.objects.get_or_create(name=rname, email=e, group=wg, person=e.person)
|
||||
|
||||
# update urls
|
||||
new_urls = r['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))
|
||||
wg.groupurl_set.all().delete()
|
||||
# Add new ones
|
||||
for u in new_urls:
|
||||
m = re.search('(?P<url>[\w\d:#@%/;$()~_?\+-=\\\.&]+)( \((?P<name>.+)\))?', u)
|
||||
if m:
|
||||
if m.group('name'):
|
||||
url = GroupURL(url=m.group('url'), name=m.group('name'), group=wg)
|
||||
else:
|
||||
url = GroupURL(url=m.group('url'), name='', group=wg)
|
||||
url.save()
|
||||
|
||||
wg.time = datetime.datetime.now()
|
||||
|
||||
if changes and not new_wg:
|
||||
for c in changes:
|
||||
log_info_changed(request, wg, login, c)
|
||||
|
||||
wg.save()
|
||||
if new_wg:
|
||||
return redirect('wg_startstop_process', name=wg.acronym, option="initcharter")
|
||||
else:
|
||||
return redirect('wg_charter', acronym=wg.acronym)
|
||||
else: # form.is_valid()
|
||||
if not new_wg:
|
||||
init = dict(name=wg.name,
|
||||
acronym=wg.acronym,
|
||||
chairs=json_emails([x.email for x in wg.role_set.filter(name="Chair")]),
|
||||
secretaries=json_emails([x.email for x in wg.role_set.filter(name="Secr")]),
|
||||
techadv=json_emails([x.email for x in wg.role_set.filter(name="Techadv")]),
|
||||
charter=wg.charter.name if wg.charter else None,
|
||||
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()),
|
||||
)
|
||||
hide = None
|
||||
else:
|
||||
init = dict(ad=login.id,
|
||||
)
|
||||
hide = ['chairs', 'techadv', 'list_email', 'list_subscribe', 'list_archive', 'urls']
|
||||
form = EditInfoForm(initial=init, cur_acronym=wg.acronym if wg else None, hide=hide)
|
||||
|
||||
return render_to_response('wgcharter/edit_info.html',
|
||||
dict(wg=wg,
|
||||
form=form,
|
||||
user=request.user,
|
||||
login=login),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
class ConcludeForm(forms.Form):
|
||||
instructions = forms.CharField(widget=forms.Textarea, required=True)
|
||||
|
||||
@group_required('Area_Director','Secretariat')
|
||||
def conclude(request, name):
|
||||
"""Request the closing of a WG, prompting for instructions."""
|
||||
try:
|
||||
wg = Group.objects.get(acronym=name)
|
||||
except Group.DoesNotExist:
|
||||
wglist = GroupHistory.objects.filter(acronym=name)
|
||||
if wglist:
|
||||
return redirect('wg_conclude', name=wglist[0].group.acronym)
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
login = request.user.get_profile()
|
||||
|
||||
# if state != wg.state:
|
||||
# # WG state changed
|
||||
# change = True
|
||||
# save_group_in_history(wg)
|
||||
|
||||
# prev = wg.state
|
||||
# wg.state = state
|
||||
|
||||
# ge = log_group_state_changed(request, wg, login, comment)
|
||||
|
||||
# wg.save()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ConcludeForm(request.POST)
|
||||
if form.is_valid():
|
||||
instructions = form.cleaned_data['instructions']
|
||||
|
||||
email_secretariat(request, wg, "conclude", instructions)
|
||||
|
||||
return redirect('wg_view', name=wg.acronym)
|
||||
else:
|
||||
form = ConcludeForm()
|
||||
|
||||
return render_to_response('wgcharter/conclude.html',
|
||||
dict(form=form,
|
||||
wg=wg),
|
||||
context_instance=RequestContext(request))
|
||||
|
|
|
@ -10,10 +10,10 @@ from django.conf import settings
|
|||
|
||||
from ietf.ietfauth.decorators import group_required
|
||||
from ietf.group.models import Group
|
||||
from ietf.doc.models import Document, DocHistory, DocEvent
|
||||
from ietf.doc.models import Document, DocHistory, DocEvent, save_document_in_history
|
||||
from ietf.group.utils import save_group_in_history
|
||||
|
||||
from utils import next_revision, set_or_create_charter, save_charter_in_history
|
||||
from utils import next_revision, set_or_create_charter
|
||||
|
||||
class UploadForm(forms.Form):
|
||||
content = forms.CharField(widget=forms.Textarea, label="Charter text", help_text="Edit the charter text", required=False)
|
||||
|
@ -57,7 +57,7 @@ def submit(request, name):
|
|||
if request.method == 'POST':
|
||||
form = UploadForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
save_charter_in_history(charter)
|
||||
save_document_in_history(charter)
|
||||
# Also save group history so we can search for it
|
||||
save_group_in_history(wg)
|
||||
|
||||
|
@ -87,7 +87,7 @@ def submit(request, name):
|
|||
filename = os.path.join(settings.CHARTER_PATH, 'charter-ietf-%s-%s.txt' % (wg.acronym, wg.charter.rev))
|
||||
try:
|
||||
charter_text = open(filename, 'r')
|
||||
init = dict(content = charter_text.read())
|
||||
init = dict(content=charter_text.read())
|
||||
except IOError:
|
||||
init = {}
|
||||
form = UploadForm(initial = init)
|
||||
|
|
258
ietf/wginfo/edit.py
Normal file
258
ietf/wginfo/edit.py
Normal file
|
@ -0,0 +1,258 @@
|
|||
# edit/create view for WGs
|
||||
|
||||
import re, os, string, datetime
|
||||
|
||||
from django.shortcuts import render_to_response, get_object_or_404, redirect
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template import RequestContext
|
||||
from django import forms
|
||||
from django.utils import simplejson
|
||||
|
||||
from ietf.ietfauth.decorators import role_required, has_role
|
||||
|
||||
from ietf.doc.models import *
|
||||
from ietf.name.models import *
|
||||
from ietf.person.models import *
|
||||
from ietf.group.models import *
|
||||
from ietf.group.utils import save_group_in_history
|
||||
from ietf.wgcharter.utils import set_or_create_charter
|
||||
from ietf.wgcharter.mails import email_secretariat
|
||||
from ietf.person.forms import EmailsField
|
||||
|
||||
|
||||
class WGForm(forms.Form):
|
||||
name = forms.CharField(max_length=255, label="WG Name", required=True)
|
||||
acronym = forms.CharField(max_length=8, label="WG Acronym", required=True)
|
||||
chairs = EmailsField(label="WG Chairs", required=False)
|
||||
secretaries = EmailsField(label="WG Secretaries", required=False)
|
||||
techadv = EmailsField(label="WG Technical Advisors", required=False)
|
||||
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)
|
||||
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.cur_acronym = kwargs.pop('cur_acronym')
|
||||
self.hide = kwargs.pop('hide', None)
|
||||
super(self.__class__, self).__init__(*args, **kwargs)
|
||||
|
||||
# hide requested fields
|
||||
if self.hide:
|
||||
for f in self.hide:
|
||||
self.fields[f].widget = forms.HiddenInput
|
||||
|
||||
# if previous AD is now ex-AD, append that person to the list
|
||||
ad_pk = self.initial.get('ad')
|
||||
choices = self.fields['ad'].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())]
|
||||
|
||||
def clean_acronym(self):
|
||||
acronym = self.cleaned_data['acronym']
|
||||
if self.cur_acronym and acronym != self.cur_acronym:
|
||||
if Group.objects.filter(acronym__iexact=acronym):
|
||||
raise forms.ValidationError("Acronym used in an existing WG. Please pick another.")
|
||||
if GroupHistory.objects.filter(acronym__iexact=acronym):
|
||||
raise forms.ValidationError("Acronym used in a previous WG. Please pick another.")
|
||||
return acronym
|
||||
|
||||
def clean_urls(self):
|
||||
return [x.strip() for x in self.cleaned_data["urls"].splitlines() if x.strip()]
|
||||
|
||||
def format_urls(urls, fs="\n"):
|
||||
res = []
|
||||
for u in urls:
|
||||
if u.name:
|
||||
res.append(u"%s (%s)" % (u.url, u.name))
|
||||
else:
|
||||
res.append(u.url)
|
||||
return fs.join(res)
|
||||
|
||||
@role_required('Area Director', 'Secretariat')
|
||||
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)
|
||||
charter = set_or_create_charter(wg)
|
||||
new_wg = False
|
||||
elif action == "create":
|
||||
wg = None
|
||||
new_wg = True
|
||||
else:
|
||||
raise Http404()
|
||||
|
||||
login = request.user.get_profile()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = WGForm(request.POST, cur_acronym=wg.acronym if wg else None)
|
||||
if form.is_valid():
|
||||
r = form.cleaned_data
|
||||
if not new_wg:
|
||||
gh = save_group_in_history(wg)
|
||||
else:
|
||||
# Create WG
|
||||
wg = Group(name=r["name"],
|
||||
acronym=r["acronym"],
|
||||
type=GroupTypeName.objects.get(name="WG"),
|
||||
state=GroupStateName.objects.get(name="Proposed"))
|
||||
wg.save()
|
||||
|
||||
e = ChangeStateGroupEvent(group=wg, type="changed_state")
|
||||
e.time = datetime.datetime.now()
|
||||
e.by = login
|
||||
e.state_id = "proposed"
|
||||
e.desc = "Proposed group"
|
||||
e.save()
|
||||
|
||||
if not wg.charter:
|
||||
# Create adjoined charter
|
||||
charter = set_or_create_charter(wg)
|
||||
charter.set_state(State.objects.get(type="charter", slug="infrev"))
|
||||
charter.save()
|
||||
|
||||
e = DocEvent(doc=charter, type="started_iesg_process")
|
||||
e.time = datetime.datetime.now()
|
||||
e.by = login
|
||||
e.desc = "Started IESG process on charter"
|
||||
e.save()
|
||||
|
||||
changes = []
|
||||
|
||||
def desc(attr, new, old):
|
||||
entry = "%(attr)s changed to <b>%(new)s</b> from %(old)s"
|
||||
if new_wg:
|
||||
entry = "%(attr)s changed to <b>%(new)s</b>"
|
||||
|
||||
return entry % dict(attr=attr, new=new, old=old)
|
||||
|
||||
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 attr == "acronym":
|
||||
c = wg.charter
|
||||
save_document_in_history(c)
|
||||
# and add a DocAlias
|
||||
DocAlias.objects.create(
|
||||
name = "charter-ietf-%s" % r['acronym'],
|
||||
document = charter
|
||||
)
|
||||
|
||||
# update the attributes, keeping track of what we're doing
|
||||
diff('name', "Name")
|
||||
diff('acronym', "Acronym")
|
||||
diff('ad', "Shepherding AD")
|
||||
diff('parent', "IETF Area")
|
||||
diff('list_email', "Mailing list email")
|
||||
diff('list_subscribe', "Mailing list subscribe address")
|
||||
diff('list_archive', "Mailing list archive")
|
||||
|
||||
# update roles
|
||||
for attr, slug, title in [('chairs', 'chair', "Chairs"), ('secretaries', 'secr', "Secretaries"), ('techadv', 'techadv', "Tech Advisors")]:
|
||||
new = r[attr]
|
||||
old = Email.objects.filter(role__group=wg, 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()
|
||||
for e in new:
|
||||
Role.objects.get_or_create(name_id=slug, email=e, group=wg, person=e.person)
|
||||
|
||||
# update urls
|
||||
new_urls = r['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))
|
||||
wg.groupurl_set.all().delete()
|
||||
# Add new ones
|
||||
for u in new_urls:
|
||||
m = re.search('(?P<url>[\w\d:#@%/;$()~_?\+-=\\\.&]+)( \((?P<name>.+)\))?', u)
|
||||
if m:
|
||||
if m.group('name'):
|
||||
url = GroupURL(url=m.group('url'), name=m.group('name'), group=wg)
|
||||
else:
|
||||
url = GroupURL(url=m.group('url'), name='', group=wg)
|
||||
url.save()
|
||||
|
||||
wg.time = datetime.datetime.now()
|
||||
|
||||
if changes and not new_wg:
|
||||
for c in changes:
|
||||
GroupEvent.objects.create(group=wg, by=login, type="info_changed", desc=c)
|
||||
|
||||
wg.save()
|
||||
|
||||
if new_wg:
|
||||
return redirect('wg_startstop_process', name=wg.acronym, option="initcharter")
|
||||
|
||||
return redirect('wg_charter', acronym=wg.acronym)
|
||||
else: # form.is_valid()
|
||||
if not new_wg:
|
||||
from ietf.person.forms import json_emails
|
||||
init = dict(name=wg.name,
|
||||
acronym=wg.acronym,
|
||||
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"),
|
||||
charter=wg.charter.name if wg.charter else None,
|
||||
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()),
|
||||
)
|
||||
hide = None
|
||||
else:
|
||||
init = dict(ad=login.id if has_role(request.user, "Area Director") else None,
|
||||
)
|
||||
hide = ['chairs', 'techadv', 'list_email', 'list_subscribe', 'list_archive', 'urls']
|
||||
form = WGForm(initial=init, cur_acronym=wg.acronym if wg else None, hide=hide)
|
||||
|
||||
return render_to_response('wginfo/edit.html',
|
||||
dict(wg=wg,
|
||||
form=form,
|
||||
user=request.user,
|
||||
login=login),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
|
||||
|
||||
class ConcludeForm(forms.Form):
|
||||
instructions = forms.CharField(widget=forms.Textarea, 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
|
||||
|
||||
login = request.user.get_profile()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ConcludeForm(request.POST)
|
||||
if form.is_valid():
|
||||
instructions = form.cleaned_data['instructions']
|
||||
|
||||
email_secretariat(request, wg, "conclude", instructions)
|
||||
|
||||
return redirect('wg_charter', acronym=wg.acronym)
|
||||
else:
|
||||
form = ConcludeForm()
|
||||
|
||||
return render_to_response('wginfo/conclude.html',
|
||||
dict(form=form,
|
||||
wg=wg),
|
||||
context_instance=RequestContext(request))
|
|
@ -30,10 +30,24 @@
|
|||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import os
|
||||
import unittest
|
||||
import os, unittest, shutil
|
||||
|
||||
import django.test
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
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 pyquery import PyQuery
|
||||
|
||||
from ietf.utils.test_utils import SimpleUrlTestCase
|
||||
from ietf.doc.models import *
|
||||
from ietf.group.models import *
|
||||
from ietf.group.utils import *
|
||||
from ietf.name.models import *
|
||||
from ietf.person.models import *
|
||||
|
||||
|
||||
class WgInfoUrlTestCase(SimpleUrlTestCase):
|
||||
def testUrls(self):
|
||||
|
@ -41,7 +55,6 @@ class WgInfoUrlTestCase(SimpleUrlTestCase):
|
|||
|
||||
class WgFileTestCase(unittest.TestCase):
|
||||
def testFileExistence(self):
|
||||
print " Testing if WG charter files exist locally"
|
||||
fpath = os.path.join(settings.IETFWG_DESCRIPTIONS_PATH, "tls.desc.txt")
|
||||
if not os.path.exists(fpath):
|
||||
print "\nERROR: charter files not found in "+settings.IETFWG_DESCRIPTIONS_PATH
|
||||
|
@ -49,6 +62,159 @@ class WgFileTestCase(unittest.TestCase):
|
|||
print "Download them to a local directory with:"
|
||||
print "wget -nd -nc -np -r http://www.ietf.org/wg-descriptions/"
|
||||
print "And set IETFWG_DESCRIPTIONS_PATH in settings_local.py\n"
|
||||
else:
|
||||
print "OK (they seem to exist)"
|
||||
|
||||
|
||||
class WgOverviewTestCase(django.test.TestCase):
|
||||
fixtures = ["names"]
|
||||
|
||||
def test_overview(self):
|
||||
make_test_data()
|
||||
|
||||
wg = Group.objects.get(acronym="mars")
|
||||
wg.charter.set_state(State.objects.get(type="charter", slug="intrev"))
|
||||
|
||||
url = urlreverse('ietf.wginfo.views.chartering_wgs')
|
||||
r = self.client.get(url)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
print
|
||||
self.assertEquals(len(q('table.ietf-doctable td.acronym a:contains("mars")')), 1)
|
||||
|
||||
|
||||
class WgEditTestCase(django.test.TestCase):
|
||||
fixtures = ["names"]
|
||||
|
||||
def setUp(self):
|
||||
self.charter_dir = os.path.abspath("tmp-charter-dir")
|
||||
os.mkdir(self.charter_dir)
|
||||
settings.CHARTER_PATH = self.charter_dir
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.charter_dir)
|
||||
|
||||
def test_create(self):
|
||||
make_test_data()
|
||||
|
||||
url = urlreverse('wg_create')
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
num_wgs = len(Group.objects.filter(type="wg"))
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEquals(len(q('form input[name=acronym]')), 1)
|
||||
|
||||
# faulty post
|
||||
r = self.client.post(url, dict(acronym="foobarbaz")) # No name
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertEquals(len(Group.objects.filter(type="wg")), num_wgs)
|
||||
|
||||
# creation
|
||||
r = self.client.post(url, dict(acronym="testwg", name="Testing WG"))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
self.assertEquals(len(Group.objects.filter(type="wg")), num_wgs + 1)
|
||||
group = Group.objects.get(acronym="testwg")
|
||||
self.assertEquals(group.name, "Testing WG")
|
||||
# check that a charter was created with the correct name
|
||||
self.assertEquals(group.charter.name, "charter-ietf-testwg")
|
||||
# and that it has no revision
|
||||
self.assertEquals(group.charter.rev, "")
|
||||
|
||||
|
||||
def test_edit_info(self):
|
||||
make_test_data()
|
||||
|
||||
# And make a charter for group
|
||||
group = Group.objects.get(acronym="mars")
|
||||
|
||||
url = urlreverse('wg_edit', kwargs=dict(acronym=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEquals(len(q('form select[name=parent]')), 1)
|
||||
self.assertEquals(len(q('form input[name=acronym]')), 1)
|
||||
|
||||
# faulty post
|
||||
Group.objects.create(name="Collision Test Group", acronym="collide")
|
||||
r = self.client.post(url, dict(acronym="collide"))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
|
||||
# Create old acronym
|
||||
group.acronym = "oldmars"
|
||||
group.save()
|
||||
save_group_in_history(group)
|
||||
group.acronym = "mars"
|
||||
group.save()
|
||||
|
||||
# post with warning
|
||||
r = self.client.post(url, dict(acronym="oldmars"))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
|
||||
# edit info
|
||||
area = group.parent
|
||||
ad = Person.objects.get(name="Aread Irector")
|
||||
r = self.client.post(url,
|
||||
dict(name="Mars Not Special Interest Group",
|
||||
acronym="mnsig",
|
||||
parent=area.pk,
|
||||
ad=ad.pk,
|
||||
chairs="aread@ietf.org, ad1@ietf.org",
|
||||
secretaries="aread@ietf.org, ad1@ietf.org, ad2@ietf.org",
|
||||
techadv="aread@ietf.org",
|
||||
list_email="mars@mail",
|
||||
list_subscribe="subscribe.mars",
|
||||
list_archive="archive.mars",
|
||||
urls="http://mars.mars (MARS site)"
|
||||
))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
|
||||
group = Group.objects.get(acronym="mnsig")
|
||||
self.assertEquals(group.name, "Mars Not Special Interest Group")
|
||||
self.assertEquals(group.parent, area)
|
||||
self.assertEquals(group.ad, ad)
|
||||
for k in ("chair", "secr", "techadv"):
|
||||
self.assertTrue(group.role_set.filter(name=k, email__address="aread@ietf.org"))
|
||||
self.assertEquals(group.list_email, "mars@mail")
|
||||
self.assertEquals(group.list_subscribe, "subscribe.mars")
|
||||
self.assertEquals(group.list_archive, "archive.mars")
|
||||
self.assertEquals(group.groupurl_set.all()[0].url, "http://mars.mars")
|
||||
self.assertEquals(group.groupurl_set.all()[0].name, "MARS site")
|
||||
|
||||
def test_conclude(self):
|
||||
make_test_data()
|
||||
|
||||
# And make a charter for group
|
||||
group = Group.objects.get(acronym="mars")
|
||||
|
||||
# -- Test conclude WG --
|
||||
url = urlreverse('wg_conclude', kwargs=dict(acronym=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEquals(len(q('form textarea[name=instructions]')), 1)
|
||||
|
||||
# faulty post
|
||||
r = self.client.post(url, dict(instructions="")) # No instructions
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
|
||||
# conclusion request
|
||||
r = self.client.post(url, dict(instructions="Test instructions"))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
# The WG remains active until the state is set to conclude via change_state
|
||||
group = Group.objects.get(acronym=group.acronym)
|
||||
self.assertEquals(group.state_id, "active")
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
# Copyright The IETF Trust 2008, All Rights Reserved
|
||||
|
||||
from django.conf.urls.defaults import patterns, include, url
|
||||
from ietf.wginfo import views
|
||||
from django.conf.urls.defaults import patterns, include
|
||||
from ietf.wginfo import views, edit
|
||||
from django.views.generic.simple import redirect_to
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^$', views.wg_dir),
|
||||
(r'^summary.txt', redirect_to, { 'url':'/wg/1wg-summary.txt' }),
|
||||
|
@ -13,10 +14,13 @@ 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'^chartering/create/$', edit.edit, {'action': "create"}, "wg_create"),
|
||||
(r'^chartering/$', views.chartering_wgs),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/documents/txt/$', views.wg_documents_txt),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/$', views.wg_documents_html),
|
||||
url(r'^(?P<acronym>[a-zA-Z0-9-]+)/charter/$', views.wg_charter, name='wg_charter'),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/charter/$', views.wg_charter, None, 'wg_charter'),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/history/', views.history),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/edit/', edit.edit, {'action': "edit"}, "wg_edit"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/conclude/', edit.conclude, None, "wg_conclude"),
|
||||
(r'^(?P<acronym>[^/]+)/management/', include('ietf.wgchairs.urls')),
|
||||
)
|
||||
|
|
|
@ -169,10 +169,10 @@ def wg_charter(request, acronym):
|
|||
fill_in_charter_info(wg)
|
||||
actions = []
|
||||
if wg.state_id != "conclude":
|
||||
actions.append(("Edit WG", urlreverse("wg_edit_info", kwargs=dict(name=wg.acronym))))
|
||||
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(name=wg.acronym))))
|
||||
actions.append(("Conclude WG", urlreverse("wg_conclude", kwargs=dict(acronym=wg.acronym))))
|
||||
|
||||
context = get_wg_menu_context(wg, "charter")
|
||||
context.update(dict(
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
.snapshots { margin: 0.5em 0; }
|
||||
.snapshots .revisions a:first-child { font-weight: bold }
|
||||
|
||||
.metabox .actions a { display: inline-block; margin-right: 0.4em; }
|
||||
|
||||
.diffTool { padding: 8px 4px; margin: 8px 0;}
|
||||
.diffTool h2 { margin-top:0;margin-bottom:4px; }
|
||||
.diffTool label { display: inline-block; width: 3em; padding: 0 0.5em; }
|
||||
|
|
10
static/js/emails-field.js
Normal file
10
static/js/emails-field.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
jQuery(function () {
|
||||
jQuery(".emails-field").each(function () {
|
||||
var e = jQuery(this);
|
||||
e.tokenInput(e.data("ajax-url"), {
|
||||
hintText: "",
|
||||
preventDuplicates: true,
|
||||
prePopulate: JSON.parse(e.val())
|
||||
});
|
||||
});
|
||||
});
|
487
static/js/lib/json2.js
Normal file
487
static/js/lib/json2.js
Normal file
|
@ -0,0 +1,487 @@
|
|||
/*
|
||||
http://www.JSON.org/json2.js
|
||||
2011-10-19
|
||||
|
||||
Public Domain.
|
||||
|
||||
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
|
||||
|
||||
See http://www.JSON.org/js.html
|
||||
|
||||
|
||||
This code should be minified before deployment.
|
||||
See http://javascript.crockford.com/jsmin.html
|
||||
|
||||
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
|
||||
NOT CONTROL.
|
||||
|
||||
|
||||
This file creates a global JSON object containing two methods: stringify
|
||||
and parse.
|
||||
|
||||
JSON.stringify(value, replacer, space)
|
||||
value any JavaScript value, usually an object or array.
|
||||
|
||||
replacer an optional parameter that determines how object
|
||||
values are stringified for objects. It can be a
|
||||
function or an array of strings.
|
||||
|
||||
space an optional parameter that specifies the indentation
|
||||
of nested structures. If it is omitted, the text will
|
||||
be packed without extra whitespace. If it is a number,
|
||||
it will specify the number of spaces to indent at each
|
||||
level. If it is a string (such as '\t' or ' '),
|
||||
it contains the characters used to indent at each level.
|
||||
|
||||
This method produces a JSON text from a JavaScript value.
|
||||
|
||||
When an object value is found, if the object contains a toJSON
|
||||
method, its toJSON method will be called and the result will be
|
||||
stringified. A toJSON method does not serialize: it returns the
|
||||
value represented by the name/value pair that should be serialized,
|
||||
or undefined if nothing should be serialized. The toJSON method
|
||||
will be passed the key associated with the value, and this will be
|
||||
bound to the value
|
||||
|
||||
For example, this would serialize Dates as ISO strings.
|
||||
|
||||
Date.prototype.toJSON = function (key) {
|
||||
function f(n) {
|
||||
// Format integers to have at least two digits.
|
||||
return n < 10 ? '0' + n : n;
|
||||
}
|
||||
|
||||
return this.getUTCFullYear() + '-' +
|
||||
f(this.getUTCMonth() + 1) + '-' +
|
||||
f(this.getUTCDate()) + 'T' +
|
||||
f(this.getUTCHours()) + ':' +
|
||||
f(this.getUTCMinutes()) + ':' +
|
||||
f(this.getUTCSeconds()) + 'Z';
|
||||
};
|
||||
|
||||
You can provide an optional replacer method. It will be passed the
|
||||
key and value of each member, with this bound to the containing
|
||||
object. The value that is returned from your method will be
|
||||
serialized. If your method returns undefined, then the member will
|
||||
be excluded from the serialization.
|
||||
|
||||
If the replacer parameter is an array of strings, then it will be
|
||||
used to select the members to be serialized. It filters the results
|
||||
such that only members with keys listed in the replacer array are
|
||||
stringified.
|
||||
|
||||
Values that do not have JSON representations, such as undefined or
|
||||
functions, will not be serialized. Such values in objects will be
|
||||
dropped; in arrays they will be replaced with null. You can use
|
||||
a replacer function to replace those with JSON values.
|
||||
JSON.stringify(undefined) returns undefined.
|
||||
|
||||
The optional space parameter produces a stringification of the
|
||||
value that is filled with line breaks and indentation to make it
|
||||
easier to read.
|
||||
|
||||
If the space parameter is a non-empty string, then that string will
|
||||
be used for indentation. If the space parameter is a number, then
|
||||
the indentation will be that many spaces.
|
||||
|
||||
Example:
|
||||
|
||||
text = JSON.stringify(['e', {pluribus: 'unum'}]);
|
||||
// text is '["e",{"pluribus":"unum"}]'
|
||||
|
||||
|
||||
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
|
||||
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
|
||||
|
||||
text = JSON.stringify([new Date()], function (key, value) {
|
||||
return this[key] instanceof Date ?
|
||||
'Date(' + this[key] + ')' : value;
|
||||
});
|
||||
// text is '["Date(---current time---)"]'
|
||||
|
||||
|
||||
JSON.parse(text, reviver)
|
||||
This method parses a JSON text to produce an object or array.
|
||||
It can throw a SyntaxError exception.
|
||||
|
||||
The optional reviver parameter is a function that can filter and
|
||||
transform the results. It receives each of the keys and values,
|
||||
and its return value is used instead of the original value.
|
||||
If it returns what it received, then the structure is not modified.
|
||||
If it returns undefined then the member is deleted.
|
||||
|
||||
Example:
|
||||
|
||||
// Parse the text. Values that look like ISO date strings will
|
||||
// be converted to Date objects.
|
||||
|
||||
myData = JSON.parse(text, function (key, value) {
|
||||
var a;
|
||||
if (typeof value === 'string') {
|
||||
a =
|
||||
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
|
||||
if (a) {
|
||||
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
|
||||
+a[5], +a[6]));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
|
||||
var d;
|
||||
if (typeof value === 'string' &&
|
||||
value.slice(0, 5) === 'Date(' &&
|
||||
value.slice(-1) === ')') {
|
||||
d = new Date(value.slice(5, -1));
|
||||
if (d) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
|
||||
This is a reference implementation. You are free to copy, modify, or
|
||||
redistribute.
|
||||
*/
|
||||
|
||||
/*jslint evil: true, regexp: true */
|
||||
|
||||
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
|
||||
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
|
||||
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
|
||||
lastIndex, length, parse, prototype, push, replace, slice, stringify,
|
||||
test, toJSON, toString, valueOf
|
||||
*/
|
||||
|
||||
|
||||
// Create a JSON object only if one does not already exist. We create the
|
||||
// methods in a closure to avoid creating global variables.
|
||||
|
||||
var JSON;
|
||||
if (!JSON) {
|
||||
JSON = {};
|
||||
}
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function f(n) {
|
||||
// Format integers to have at least two digits.
|
||||
return n < 10 ? '0' + n : n;
|
||||
}
|
||||
|
||||
if (typeof Date.prototype.toJSON !== 'function') {
|
||||
|
||||
Date.prototype.toJSON = function (key) {
|
||||
|
||||
return isFinite(this.valueOf())
|
||||
? this.getUTCFullYear() + '-' +
|
||||
f(this.getUTCMonth() + 1) + '-' +
|
||||
f(this.getUTCDate()) + 'T' +
|
||||
f(this.getUTCHours()) + ':' +
|
||||
f(this.getUTCMinutes()) + ':' +
|
||||
f(this.getUTCSeconds()) + 'Z'
|
||||
: null;
|
||||
};
|
||||
|
||||
String.prototype.toJSON =
|
||||
Number.prototype.toJSON =
|
||||
Boolean.prototype.toJSON = function (key) {
|
||||
return this.valueOf();
|
||||
};
|
||||
}
|
||||
|
||||
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
||||
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
||||
gap,
|
||||
indent,
|
||||
meta = { // table of character substitutions
|
||||
'\b': '\\b',
|
||||
'\t': '\\t',
|
||||
'\n': '\\n',
|
||||
'\f': '\\f',
|
||||
'\r': '\\r',
|
||||
'"' : '\\"',
|
||||
'\\': '\\\\'
|
||||
},
|
||||
rep;
|
||||
|
||||
|
||||
function quote(string) {
|
||||
|
||||
// If the string contains no control characters, no quote characters, and no
|
||||
// backslash characters, then we can safely slap some quotes around it.
|
||||
// Otherwise we must also replace the offending characters with safe escape
|
||||
// sequences.
|
||||
|
||||
escapable.lastIndex = 0;
|
||||
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
|
||||
var c = meta[a];
|
||||
return typeof c === 'string'
|
||||
? c
|
||||
: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
||||
}) + '"' : '"' + string + '"';
|
||||
}
|
||||
|
||||
|
||||
function str(key, holder) {
|
||||
|
||||
// Produce a string from holder[key].
|
||||
|
||||
var i, // The loop counter.
|
||||
k, // The member key.
|
||||
v, // The member value.
|
||||
length,
|
||||
mind = gap,
|
||||
partial,
|
||||
value = holder[key];
|
||||
|
||||
// If the value has a toJSON method, call it to obtain a replacement value.
|
||||
|
||||
if (value && typeof value === 'object' &&
|
||||
typeof value.toJSON === 'function') {
|
||||
value = value.toJSON(key);
|
||||
}
|
||||
|
||||
// If we were called with a replacer function, then call the replacer to
|
||||
// obtain a replacement value.
|
||||
|
||||
if (typeof rep === 'function') {
|
||||
value = rep.call(holder, key, value);
|
||||
}
|
||||
|
||||
// What happens next depends on the value's type.
|
||||
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
return quote(value);
|
||||
|
||||
case 'number':
|
||||
|
||||
// JSON numbers must be finite. Encode non-finite numbers as null.
|
||||
|
||||
return isFinite(value) ? String(value) : 'null';
|
||||
|
||||
case 'boolean':
|
||||
case 'null':
|
||||
|
||||
// If the value is a boolean or null, convert it to a string. Note:
|
||||
// typeof null does not produce 'null'. The case is included here in
|
||||
// the remote chance that this gets fixed someday.
|
||||
|
||||
return String(value);
|
||||
|
||||
// If the type is 'object', we might be dealing with an object or an array or
|
||||
// null.
|
||||
|
||||
case 'object':
|
||||
|
||||
// Due to a specification blunder in ECMAScript, typeof null is 'object',
|
||||
// so watch out for that case.
|
||||
|
||||
if (!value) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
// Make an array to hold the partial results of stringifying this object value.
|
||||
|
||||
gap += indent;
|
||||
partial = [];
|
||||
|
||||
// Is the value an array?
|
||||
|
||||
if (Object.prototype.toString.apply(value) === '[object Array]') {
|
||||
|
||||
// The value is an array. Stringify every element. Use null as a placeholder
|
||||
// for non-JSON values.
|
||||
|
||||
length = value.length;
|
||||
for (i = 0; i < length; i += 1) {
|
||||
partial[i] = str(i, value) || 'null';
|
||||
}
|
||||
|
||||
// Join all of the elements together, separated with commas, and wrap them in
|
||||
// brackets.
|
||||
|
||||
v = partial.length === 0
|
||||
? '[]'
|
||||
: gap
|
||||
? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
|
||||
: '[' + partial.join(',') + ']';
|
||||
gap = mind;
|
||||
return v;
|
||||
}
|
||||
|
||||
// If the replacer is an array, use it to select the members to be stringified.
|
||||
|
||||
if (rep && typeof rep === 'object') {
|
||||
length = rep.length;
|
||||
for (i = 0; i < length; i += 1) {
|
||||
if (typeof rep[i] === 'string') {
|
||||
k = rep[i];
|
||||
v = str(k, value);
|
||||
if (v) {
|
||||
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// Otherwise, iterate through all of the keys in the object.
|
||||
|
||||
for (k in value) {
|
||||
if (Object.prototype.hasOwnProperty.call(value, k)) {
|
||||
v = str(k, value);
|
||||
if (v) {
|
||||
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Join all of the member texts together, separated with commas,
|
||||
// and wrap them in braces.
|
||||
|
||||
v = partial.length === 0
|
||||
? '{}'
|
||||
: gap
|
||||
? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
|
||||
: '{' + partial.join(',') + '}';
|
||||
gap = mind;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
// If the JSON object does not yet have a stringify method, give it one.
|
||||
|
||||
if (typeof JSON.stringify !== 'function') {
|
||||
JSON.stringify = function (value, replacer, space) {
|
||||
|
||||
// The stringify method takes a value and an optional replacer, and an optional
|
||||
// space parameter, and returns a JSON text. The replacer can be a function
|
||||
// that can replace values, or an array of strings that will select the keys.
|
||||
// A default replacer method can be provided. Use of the space parameter can
|
||||
// produce text that is more easily readable.
|
||||
|
||||
var i;
|
||||
gap = '';
|
||||
indent = '';
|
||||
|
||||
// If the space parameter is a number, make an indent string containing that
|
||||
// many spaces.
|
||||
|
||||
if (typeof space === 'number') {
|
||||
for (i = 0; i < space; i += 1) {
|
||||
indent += ' ';
|
||||
}
|
||||
|
||||
// If the space parameter is a string, it will be used as the indent string.
|
||||
|
||||
} else if (typeof space === 'string') {
|
||||
indent = space;
|
||||
}
|
||||
|
||||
// If there is a replacer, it must be a function or an array.
|
||||
// Otherwise, throw an error.
|
||||
|
||||
rep = replacer;
|
||||
if (replacer && typeof replacer !== 'function' &&
|
||||
(typeof replacer !== 'object' ||
|
||||
typeof replacer.length !== 'number')) {
|
||||
throw new Error('JSON.stringify');
|
||||
}
|
||||
|
||||
// Make a fake root object containing our value under the key of ''.
|
||||
// Return the result of stringifying the value.
|
||||
|
||||
return str('', {'': value});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// If the JSON object does not yet have a parse method, give it one.
|
||||
|
||||
if (typeof JSON.parse !== 'function') {
|
||||
JSON.parse = function (text, reviver) {
|
||||
|
||||
// The parse method takes a text and an optional reviver function, and returns
|
||||
// a JavaScript value if the text is a valid JSON text.
|
||||
|
||||
var j;
|
||||
|
||||
function walk(holder, key) {
|
||||
|
||||
// The walk method is used to recursively walk the resulting structure so
|
||||
// that modifications can be made.
|
||||
|
||||
var k, v, value = holder[key];
|
||||
if (value && typeof value === 'object') {
|
||||
for (k in value) {
|
||||
if (Object.prototype.hasOwnProperty.call(value, k)) {
|
||||
v = walk(value, k);
|
||||
if (v !== undefined) {
|
||||
value[k] = v;
|
||||
} else {
|
||||
delete value[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return reviver.call(holder, key, value);
|
||||
}
|
||||
|
||||
|
||||
// Parsing happens in four stages. In the first stage, we replace certain
|
||||
// Unicode characters with escape sequences. JavaScript handles many characters
|
||||
// incorrectly, either silently deleting them, or treating them as line endings.
|
||||
|
||||
text = String(text);
|
||||
cx.lastIndex = 0;
|
||||
if (cx.test(text)) {
|
||||
text = text.replace(cx, function (a) {
|
||||
return '\\u' +
|
||||
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
||||
});
|
||||
}
|
||||
|
||||
// In the second stage, we run the text against regular expressions that look
|
||||
// for non-JSON patterns. We are especially concerned with '()' and 'new'
|
||||
// because they can cause invocation, and '=' because it can cause mutation.
|
||||
// But just to be safe, we want to reject all unexpected forms.
|
||||
|
||||
// We split the second stage into 4 regexp operations in order to work around
|
||||
// crippling inefficiencies in IE's and Safari's regexp engines. First we
|
||||
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
|
||||
// replace all simple value tokens with ']' characters. Third, we delete all
|
||||
// open brackets that follow a colon or comma or that begin the text. Finally,
|
||||
// we look to see that the remaining characters are only whitespace or ']' or
|
||||
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
|
||||
|
||||
if (/^[\],:{}\s]*$/
|
||||
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
|
||||
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
|
||||
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
|
||||
|
||||
// In the third stage we use the eval function to compile the text into a
|
||||
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
|
||||
// in JavaScript: it can begin a block or an object literal. We wrap the text
|
||||
// in parens to eliminate the ambiguity.
|
||||
|
||||
j = eval('(' + text + ')');
|
||||
|
||||
// In the optional fourth stage, we recursively walk the new structure, passing
|
||||
// each name/value pair to a reviver function for possible transformation.
|
||||
|
||||
return typeof reviver === 'function'
|
||||
? walk({'': j}, '')
|
||||
: j;
|
||||
}
|
||||
|
||||
// If the text is not JSON parseable, then a SyntaxError is thrown.
|
||||
|
||||
throw new SyntaxError('JSON.parse');
|
||||
};
|
||||
}
|
||||
}());
|
Loading…
Reference in a new issue