Merge charter branch with the UI hooks disabled for the time being
- Legacy-Id: 3800
This commit is contained in:
commit
31aef86164
|
@ -431,6 +431,15 @@ def stable_dictsort(value, arg):
|
|||
decorated.sort(lambda a, b: cmp(a[0], b[0]) if a[0] and b[0] else -1 if b[0] else 1 if a[0] else 0)
|
||||
return [item[1] for item in decorated]
|
||||
|
||||
@register.filter
|
||||
def ad_area(user):
|
||||
if user and user.is_authenticated():
|
||||
from redesign.group.models import Group
|
||||
g = Group.objects.filter(role__name="ad", role__person__user=user)
|
||||
if g:
|
||||
return g[0].acronym
|
||||
return None
|
||||
|
||||
def _test():
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
|
|
@ -22,6 +22,7 @@ from redesign.doc.utils import get_tags_for_stream_id
|
|||
from redesign.doc.models import save_document_in_history, DocEvent, Document
|
||||
from redesign.name.models import DocTagName, StreamName, RoleName
|
||||
from redesign.group.models import Group, GroupStateTransitions, Role
|
||||
from redesign.group.utils import save_group_in_history
|
||||
from redesign.person.models import Person, Email
|
||||
|
||||
class StreamDraftForm(forms.Form):
|
||||
|
@ -351,9 +352,10 @@ class StreamDelegatesForm(forms.Form):
|
|||
|
||||
def save(self):
|
||||
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
|
||||
# FIXME: should save group history here
|
||||
stream_group = Group.objects.get(acronym=self.stream.slug)
|
||||
save_group_in_history(stream_group)
|
||||
Role.objects.get_or_create(person=self.person,
|
||||
group=Group.objects.get(acronym=self.stream.slug),
|
||||
group=stream_group,
|
||||
name=RoleName.objects.get(slug="delegate"),
|
||||
email=Email.objects.get(address=self.cleaned_data.get('email')))
|
||||
return
|
||||
|
|
|
@ -19,6 +19,8 @@ from ietf.ietfworkflows.accounts import (can_edit_state, can_edit_stream,
|
|||
is_chair_of_stream, can_adopt)
|
||||
from redesign.doc.utils import get_tags_for_stream_id
|
||||
from redesign.name.models import DocTagName
|
||||
from redesign.group.utils import save_group_in_history
|
||||
from redesign.group.models import Group, Role
|
||||
|
||||
|
||||
REDUCED_HISTORY_LEN = 20
|
||||
|
@ -125,9 +127,9 @@ def stream_delegates(request, stream_name):
|
|||
if request.POST.get('delete', False):
|
||||
pk_list = request.POST.getlist('remove_delegate')
|
||||
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
|
||||
# FIXME: should save group history here
|
||||
from redesign.group.models import Role
|
||||
Role.objects.filter(person__in=pk_list, group__acronym=stream.slug, name="delegate").delete()
|
||||
stream_group = Group.objects.get(acronym=stream.slug)
|
||||
save_group_in_history(stream_group)
|
||||
Role.objects.filter(person__in=pk_list, group=stream_group, name="delegate").delete()
|
||||
else:
|
||||
StreamDelegate.objects.filter(stream=stream, person__pk__in=pk_list).delete()
|
||||
else:
|
||||
|
|
|
@ -156,6 +156,7 @@ INSTALLED_APPS = (
|
|||
'ietf.submit',
|
||||
'ietf.ietfworkflows',
|
||||
'ietf.wgchairs',
|
||||
'ietf.wgcharter',
|
||||
)
|
||||
|
||||
INTERNAL_IPS = (
|
||||
|
@ -187,6 +188,8 @@ MAX_WG_DELEGATES = 3
|
|||
INTERNET_DRAFT_PATH = '/a/www/ietf-ftp/internet-drafts/'
|
||||
INTERNET_DRAFT_PDF_PATH = '/a/www/ietf-datatracker/pdf/'
|
||||
RFC_PATH = '/a/www/ietf-ftp/rfc/'
|
||||
CHARTER_PATH = '/a/www/ietf-ftp/charters/'
|
||||
CHARTER_TXT_URL = 'http://www.ietf.org/charters/'
|
||||
AGENDA_PATH = '/a/www/www6s/proceedings/'
|
||||
AGENDA_PATH_PATTERN = '/a/www/www6s/proceedings/%(meeting)s/agenda/%(wg)s.%(ext)s'
|
||||
MINUTES_PATH_PATTERN = '/a/www/www6s/proceedings/%(meeting)s/minutes/%(wg)s.%(ext)s'
|
||||
|
|
|
@ -42,12 +42,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
<li><a href="{% url doc_search_by_ad name=user.get_profile.person.full_name_as_key %}">My Documents</a></li>
|
||||
<li><a href="{% url ietf.iesg.views.agenda_documents %}">Next Telechat</a></li>
|
||||
<li><a href="{% url ietf.iesg.views.discusses %}">Discusses</a></li>
|
||||
<li><a href="{% url iesg_working_group_actions %}">Working Groups</a></li>
|
||||
{# FIXME: wgcharter <li><a href="{% url wg_search_by_area name=user|ad_area %}">Working Groups</a></li> #}
|
||||
{% endif %}
|
||||
{% if user|in_group:"Secretariat" %}
|
||||
<li class="sect first">Secretariat</li>
|
||||
<li><a href="{% url ietf.iesg.views.telechat_dates %}">Telechat Dates</a></li>
|
||||
<li><a href="{% url iesg_working_group_actions %}">Working Groups</a></li>
|
||||
{# FIXME: wgcharter <li><a href="{% url wg_search_in_process %}">Working Groups</a></li> #}
|
||||
{% endif %}
|
||||
{% if user %}
|
||||
{% get_user_managed_streams user as stream_list %}
|
||||
|
@ -67,6 +67,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
</div></div></li>
|
||||
|
||||
<li style="padding-top:0;"><a href="/wg/">Active WGs</a></li>
|
||||
{# FIXME: wgcharter <li><a href="DEAD_BEEF_TASTY_TASTY">Proposed WGs</a></li> #}
|
||||
<li><a href="http://tools.ietf.org/wg/concluded">Concluded WGs</a></li>
|
||||
<li><a href="http://www.ietf.org/list/nonwg.html">Non-WG Lists</a></li>
|
||||
|
||||
|
@ -83,6 +84,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
<li><a href="http://www.ietf.org/meeting/upcoming.html">Upcoming</a></li>
|
||||
|
||||
<li class="sect">Other Documents</li>
|
||||
{# FIXME: wgcharter <li><a href="/wgcharter/">WG Charters</a></li> #}
|
||||
<li><a href="/ipr/">IPR Disclosures</a></li>
|
||||
<li><a href="/liaison/">Liaison Statements</a></li>
|
||||
<li><a href="/iesg/agenda/">IESG Agenda</a></li>
|
||||
|
|
28
ietf/templates/feeds/wg_charter_description.html
Normal file
28
ietf/templates/feeds/wg_charter_description.html
Normal file
|
@ -0,0 +1,28 @@
|
|||
{# Copyright The IETF Trust 2011, All Rights Reserved #}
|
||||
{% load ietf_filters %}
|
||||
{{ obj.info.text|safe }}<br/>
|
||||
<br/>
|
||||
{% with obj.group as wg %}
|
||||
WG name: {{ wg.name }}<br/>
|
||||
WG acronym: {{ wg.acronym }}<br/>
|
||||
IETF area: {{ wg.parent|default:"-" }}<br/>
|
||||
|
||||
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/>
|
||||
|
||||
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/>
|
||||
|
||||
WG State: {{ wg.state|safe }}<br/>
|
||||
Charter State: {{ wg.charter.charter_state|safe }}<br/>
|
||||
<br/>
|
||||
{% if obj.rev %}
|
||||
{{ obj.charter|safe }}
|
||||
{% else %}
|
||||
The WG does not yet have a charter
|
||||
{% endif %}
|
||||
{% endwith %}
|
1
ietf/templates/feeds/wg_charter_title.html
Normal file
1
ietf/templates/feeds/wg_charter_title.html
Normal file
|
@ -0,0 +1 @@
|
|||
{% load ietf_filters %}WG Charter for {{obj.group.name}} ({{ obj.group.acronym|safe }})
|
10
ietf/templates/wgcharter/action_text.txt
Normal file
10
ietf/templates/wgcharter/action_text.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% load ietf_filters %}{% autoescape off %}From: The IESG <iesg-secretary@ietf.org>
|
||||
To: IETF-Announce <ietf-announce@ietf.org>
|
||||
Subject: WG Action: {{ action_type }} {{ wg.name }} ({{wg.acronym}})
|
||||
|
||||
{% filter wordwrap:73 %}{% ifequal action_type "Formed" %}A new IETF working group has been formed in the {{ wg.parent.name }}.{% endifequal %}{% ifequal action_type "Rechartered" %}The {{ wg.name }} ({{wg.acronym}}) working group in the {{ wg.parent.name }} of the IETF has been rechartered.{% endifequal %} For additional information please contact the Area Directors or the WG Chair.
|
||||
|
||||
{% include "wgcharter/wg_info.txt" %}
|
||||
|
||||
{% endfilter %}
|
||||
{% endautoescape %}
|
36
ietf/templates/wgcharter/add_comment.html
Normal file
36
ietf/templates/wgcharter/add_comment.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
{% 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 %}
|
34
ietf/templates/wgcharter/announcement_text.html
Normal file
34
ietf/templates/wgcharter/announcement_text.html
Normal file
|
@ -0,0 +1,34 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}WG {{ announcement }} announcement writeup for {{ charter.chartered_group.acronym }}{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
form #id_announcement_text {
|
||||
width: 700px;
|
||||
height: 600px;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>WG {{ announcement }} announcement writeup for {{ charter.chartered_group.acronym }}</h1>
|
||||
|
||||
<form action="" method="POST">
|
||||
|
||||
{{ announcement_text_form.announcement_text }}
|
||||
|
||||
<div class="actions">
|
||||
<a href="{{ back_url }}">Back</a>
|
||||
<input type="submit" name="save_text" value="Save WG {{ announcement }} announcement text" />
|
||||
<input type="submit" name="regenerate_text" value="Regenerate {{ announcement }} text" />
|
||||
</div>
|
||||
|
||||
{% load ietf_filters %}
|
||||
{% if user|in_group:"Secretariat" %}
|
||||
<div class="actions">
|
||||
<input type="submit" name="send_text" value="Send WG {{ announcement }} announcement" />
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock%}
|
37
ietf/templates/wgcharter/approve_ballot.html
Normal file
37
ietf/templates/wgcharter/approve_ballot.html
Normal file
|
@ -0,0 +1,37 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Approve ballot for {{ wg.acronym }}{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
form.approve-ballot pre {
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
border-top: 4px solid #eee;
|
||||
border-bottom: 4px solid #eee;
|
||||
}
|
||||
form.approve-ballot .announcement {
|
||||
overflow-x: auto;
|
||||
overflow-y: scroll;
|
||||
width: 800px;
|
||||
height: 400px;
|
||||
border: 1px solid #bbb;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Approve Ballot for {{ wg.acronym }}</h1>
|
||||
|
||||
<div>IETF announcement:</div>
|
||||
|
||||
<form class="approve-ballot" action="" method="POST">
|
||||
|
||||
<div class="announcement">
|
||||
<pre>{{ announcement }}</pre>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<a href="{% url wg_view name=wg.acronym %}">Back</a>
|
||||
<input type="submit" value="Send out the announcement, close ballot and update revision"/>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
25
ietf/templates/wgcharter/ballot_comment_mail.txt
Normal file
25
ietf/templates/wgcharter/ballot_comment_mail.txt
Normal file
|
@ -0,0 +1,25 @@
|
|||
{% autoescape off %}{{ ad }} has entered the following ballot position for
|
||||
{{ charter.name }}-{{ charter.rev }}: {{ pos.name }}
|
||||
|
||||
When responding, please keep the subject line intact and reply to all
|
||||
email addresses included in the To and CC lines. (Feel free to cut this
|
||||
introductory paragraph, however.)
|
||||
|
||||
{% if not block_comment and not comment %}
|
||||
There is no BLOCK or COMMENT text associated with this position.
|
||||
{% endif %}
|
||||
|
||||
{% if block_comment %}----------------------------------------------------------------------
|
||||
BLOCK:
|
||||
----------------------------------------------------------------------
|
||||
|
||||
{{ block_comment|safe|wordwrap:73 }}
|
||||
|
||||
|
||||
{% endif %}{% if comment %}----------------------------------------------------------------------
|
||||
COMMENT:
|
||||
----------------------------------------------------------------------
|
||||
|
||||
{{ comment|safe|wordwrap:73 }}
|
||||
{% endif %}
|
||||
{% endautoescape %}
|
13
ietf/templates/wgcharter/ballot_issued.html
Normal file
13
ietf/templates/wgcharter/ballot_issued.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Ballot for {{ charter.chartered_group }} issued{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Ballot for {{ charter.chartered_group }} issued</h1>
|
||||
|
||||
<p>Ballot has been sent out.</p>
|
||||
|
||||
<div class="actions">
|
||||
<a href="{{ back_url }}">Back to WG</a>
|
||||
</div>
|
||||
{% endblock %}
|
51
ietf/templates/wgcharter/ballot_writeup.txt
Normal file
51
ietf/templates/wgcharter/ballot_writeup.txt
Normal file
|
@ -0,0 +1,51 @@
|
|||
|
||||
Technical Summary
|
||||
|
||||
Relevant content can frequently be found in the abstract
|
||||
and/or introduction of the document. If not, this may be
|
||||
an indication that there are deficiencies in the abstract
|
||||
or introduction.
|
||||
|
||||
Working Group Summary
|
||||
|
||||
Was there anything in the WG process that is worth noting?
|
||||
For example, was there controversy about particular points
|
||||
or were there decisions where the consensus was
|
||||
particularly rough?
|
||||
|
||||
Document Quality
|
||||
|
||||
Are there existing implementations of the protocol? Have a
|
||||
significant number of vendors indicated their plan to
|
||||
implement the specification? Are there any reviewers that
|
||||
merit special mention as having done a thorough review,
|
||||
e.g., one that resulted in important changes or a
|
||||
conclusion that the document had no substantive issues? If
|
||||
there was a MIB Doctor, Media Type, or other Expert Review,
|
||||
what was its course (briefly)? In the case of a Media Type
|
||||
Review, on what date was the request posted?
|
||||
|
||||
Personnel
|
||||
|
||||
Who is the Document Shepherd for this document? Who is the
|
||||
Responsible Area Director? If the document requires IANA
|
||||
experts(s), insert 'The IANA Expert(s) for the registries
|
||||
in this document are <TO BE ADDED BY THE AD>.'
|
||||
|
||||
RFC Editor Note
|
||||
|
||||
(Insert RFC Editor Note here or remove section)
|
||||
|
||||
IRTF Note
|
||||
|
||||
(Insert IRTF Note here or remove section)
|
||||
|
||||
IESG Note
|
||||
|
||||
(Insert IESG Note here or remove section)
|
||||
|
||||
IANA Note
|
||||
|
||||
(Insert IANA Note here or remove section)
|
||||
|
||||
|
30
ietf/templates/wgcharter/ballot_writeupnotes.html
Normal file
30
ietf/templates/wgcharter/ballot_writeupnotes.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Ballot writeup and notes for {{ charter.chartered_group }}{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
form #id_ballot_writeup {
|
||||
width: 700px;
|
||||
height: 600px;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Ballot writeup and notes for {{ charter.chartered_group }}</h1>
|
||||
|
||||
|
||||
<form action="" method="POST">
|
||||
|
||||
<p>(Working Group Summary, Personnel, IAB Note, IESG Note, IANA Note)</p>
|
||||
|
||||
{{ ballot_writeup_form.ballot_writeup }}
|
||||
|
||||
<div class="actions">
|
||||
<a href="{{ charter.get_absolute_url }}">Back</a>
|
||||
<input type="submit" name="save_ballot_writeup" value="Save Ballot Writeup" />
|
||||
<input style="margin-left: 8px" type="submit" name="issue_ballot" value="Save and {% if reissue %}Re-{% endif %}Issue Ballot" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
{% endblock%}
|
29
ietf/templates/wgcharter/by_area.html
Normal file
29
ietf/templates/wgcharter/by_area.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
Copyright The IETF Trust 2011, All Rights Reserved
|
||||
{% endcomment %}
|
||||
|
||||
{% block title %}Working Groups for {{ area_name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Working Groups for {{ area_name }}</h1>
|
||||
|
||||
<p>
|
||||
<span id="create_wg" class="yui-button yui-link-button" style="margin-left:2px;"><span class="first-child"><a href="/wgcharter/create/">Start new WG charter effort</a></span></span>
|
||||
</p>
|
||||
|
||||
{% if not recs %}
|
||||
<p><b>No WGs match your query.</b></p>
|
||||
{% else %}
|
||||
<table class="ietf-table ietf-doctable">
|
||||
<tr>
|
||||
{% for hdr in meta.hdrs %}
|
||||
{% include "wgcharter/table_header.html" %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for wg in recs %}
|
||||
{% include "wgcharter/search_result_row.html" %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
91
ietf/templates/wgcharter/change_state.html
Normal file
91
ietf/templates/wgcharter/change_state.html
Normal file
|
@ -0,0 +1,91 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
form.change-state select {
|
||||
width: 22em;
|
||||
}
|
||||
|
||||
#id_message, #id_comment {
|
||||
width: 40em;
|
||||
}
|
||||
|
||||
form.change-state .actions {
|
||||
text-align: right;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ title }}</h1>
|
||||
|
||||
{% if "Change state" in title %}<p class="helptext">For help on the states, see the <a href="{% url help_charter_states %}">state table</a>.</p>{% endif %}
|
||||
|
||||
<form class="change-state" action="" method="post">
|
||||
<table>
|
||||
{% for field in form.visible_fields %}
|
||||
<tr>
|
||||
{% if field.name == "initial_time" %}
|
||||
{% if option == "recharter" %}
|
||||
<th>{{ field.label_tag }}:</th>
|
||||
<td>{{ field }}
|
||||
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
|
||||
{% else %}
|
||||
{% if option == "initcharter" %}
|
||||
<th>{{ field.label_tag }}:</th>
|
||||
<td>{{ field }}
|
||||
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<th>{{ field.label_tag }}:</th>
|
||||
<td>{{ field }}
|
||||
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
|
||||
{% endif %}
|
||||
{% if field.name == "charter_state" and field.errors == "warning" %}
|
||||
<ul><li>The initial review time hasn't elapsed. Select this checkbox to proceed anyway: <label><input type="checkbox" name="confirm_state" /></label></li></ul>
|
||||
{% else %}
|
||||
{{ field.errors }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td colspan="2" class="actions">
|
||||
{% if option %}
|
||||
<input type="submit" value="Submit"/>
|
||||
{% else %}
|
||||
<a href="{% url wg_view name=wg.acronym %}">Back</a>
|
||||
<input type="submit" value="Save"/>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
{% if prev_state %}
|
||||
<h3>Or revert to previous state</h3>
|
||||
|
||||
<div class="prev-state">
|
||||
<form action="" method="post">
|
||||
<input type="hidden" name="charter_state" value="{{ prev_charter_state.pk }}" />
|
||||
<input type="hidden" name="state" value="{{ prev_state.slug }}" />
|
||||
<input type="submit" value="Back to {{ prev_charter_state.name }}" />
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content_end %}
|
||||
<script type="text/javascript">
|
||||
var message = {
|
||||
"infrev": "The WG {{ wg.name }} ({{ wg.acronym }}) has been set to Informal IESG review by {{ login.name }}",
|
||||
"intrev": "The WG {{ wg.name }} ({{ wg.acronym }}) has been set to Internal review by {{ login.name }}. Please place it on the next IESG telechat and inform the IAB.",
|
||||
"extrev": "The WG {{ wg.name }} ({{ wg.acronym }}) has been set to External review by {{ login.name }}. Please send out the external review announcement to the appropriate lists.\n\nSend the announcement to other SDOs: Yes\nAdditional recipients of the announcement: "
|
||||
};
|
||||
|
||||
</script>
|
||||
<script type="text/javascript" src="/js/wg-change-state.js"></script>
|
||||
{% endblock %}
|
34
ietf/templates/wgcharter/charter_diffs.html
Normal file
34
ietf/templates/wgcharter/charter_diffs.html
Normal file
|
@ -0,0 +1,34 @@
|
|||
<div class="ietf-box diffTool">
|
||||
<h2 style="margin-top:0;margin-bottom:4px;">Diffs</h2>
|
||||
<form action="http{% if request.is_secure %}s{% endif %}://tools.ietf.org/rfcdiff" method="get" target="_blank" style="margin:0;">
|
||||
<table>
|
||||
<tr><td>
|
||||
<label>From:</label> <select name="url1">
|
||||
{% for c in versions %}
|
||||
<option value="{{ charter_text_url }}{{c.name}}-{{c.rev}}.txt" {% ifequal forloop.counter 2 %} selected="selected" {% endifequal %}>{{c.name}}-{{c.rev}} ({{c.date}})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
<td rowspan="2" valign="top">
|
||||
Format:
|
||||
<select name="difftype">
|
||||
<option value="--html" selected="selected">Side-by-side</option>
|
||||
<option value="--abdiff">Before-after</option>
|
||||
<option value="--chbars">Change bars</option>
|
||||
<option value="--hwdiff">Wdiff</option>
|
||||
</select> <input name="submit" value="Go!" type="submit" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label>To:</label>
|
||||
<select name="url2">
|
||||
{% for c in versions %}
|
||||
<option value="{{ charter_text_url }}{{c.name}}-{{c.rev}}.txt" {% ifequal forloop.counter 1 %} selected="selected" {% endifequal %}>{{c.name}}-{{c.rev}} ({{c.date}})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
38
ietf/templates/wgcharter/conclude.html
Normal file
38
ietf/templates/wgcharter/conclude.html
Normal file
|
@ -0,0 +1,38 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Request closing of WG {{ wg.acronym }}{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
#id_instructions {
|
||||
width: 40em;
|
||||
}
|
||||
|
||||
form.conclude .actions {
|
||||
text-align: right;
|
||||
padding-top: 10px;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Request closing of {{ wg.acronym }}</h1>
|
||||
|
||||
<p>
|
||||
Please provide instructions regarding the disposition of each
|
||||
active Internet-Draft (such as to withdraw the draft, move it to
|
||||
another WG, convert it to an individual submission, and so on),
|
||||
wording for the closure announcement, and the status of the WG
|
||||
mailing list (will it remain open or should it be closed).
|
||||
</p>
|
||||
<form class="conclude" action="" method="post">
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
<tr>
|
||||
<td colspan="2" class="actions">
|
||||
<a href="{% url wg_view name=wg.acronym %}">Back</a>
|
||||
<input type="submit" value="Send request"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
5
ietf/templates/wgcharter/date_column.html
Normal file
5
ietf/templates/wgcharter/date_column.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
{% comment %}
|
||||
Copyright The IETF Trust 2011, All Rights Reserved
|
||||
{% endcomment %}
|
||||
{% load ietf_filters %}<td class="date">{{ wg.time|date:"Y-m-d" }}
|
||||
</td>
|
102
ietf/templates/wgcharter/edit_info.html
Normal file
102
ietf/templates/wgcharter/edit_info.html
Normal file
|
@ -0,0 +1,102 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}
|
||||
{% if wg %}
|
||||
Edit info on {{ wg.acronym }}
|
||||
{% else %}
|
||||
Create WG
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
form.edit-info #id_name {
|
||||
width: 396px;
|
||||
}
|
||||
|
||||
form.edit-info #id_list_email {
|
||||
width: 396px;
|
||||
}
|
||||
|
||||
form.edit-info #id_list_subscribe {
|
||||
width: 396px;
|
||||
}
|
||||
|
||||
form.edit-info #id_list_archive {
|
||||
width: 396px;
|
||||
}
|
||||
|
||||
form.edit-info #id_urls {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
form.edit-info #id_comments {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" type="text/css" href="/css/token-input.css"></link>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% load ietf_filters %}
|
||||
<h1>{% if wg %}
|
||||
Edit info on {{ wg.acronym }}
|
||||
{% else %}
|
||||
Create WG
|
||||
{% endif %}
|
||||
</h1>
|
||||
|
||||
<form class="edit-info" action="" method="POST">
|
||||
<table>
|
||||
{% for field in form.visible_fields %}
|
||||
<tr>
|
||||
<th>{{ field.label_tag }}:</th>
|
||||
<td>{{ field }}
|
||||
{% ifequal field.name "ad" %}
|
||||
{% if user|in_group:"Area_Director" %}
|
||||
<label><input type="checkbox" name="ad" value="{{ login.pk }}" /> Assign to me</label>
|
||||
{% endif %}
|
||||
{% endifequal %}
|
||||
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
|
||||
{{ field.errors }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td class="actions">
|
||||
{% if wg %}
|
||||
<a href="{% url wg_view name=wg.acronym %}">Back</a>
|
||||
<input type="submit" value="Save"/>
|
||||
{% else %}
|
||||
<input type="submit" value="Create"/>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</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>
|
||||
{% endblock %}
|
71
ietf/templates/wgcharter/edit_position.html
Normal file
71
ietf/templates/wgcharter/edit_position.html
Normal file
|
@ -0,0 +1,71 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Change position for {{ ad.name }} on {{ wg.acronym }}{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
form.position-form .position ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
form.position-form .position li {
|
||||
list-style-type: none;
|
||||
float: left;
|
||||
padding-right: 10px;
|
||||
}
|
||||
form.position-form .last-edited {
|
||||
font-style: italic;
|
||||
}
|
||||
form.position-form .block_comment {
|
||||
padding-top: 20px
|
||||
}
|
||||
form.position-form #id_block_comment,
|
||||
form.position-form #id_comment {
|
||||
width: 700px;
|
||||
height: 250px;
|
||||
}
|
||||
form.position-form .comment {
|
||||
margin-top: 20px;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Change position for {{ ad.name }} on {{ wg.acronym }}</h1>
|
||||
|
||||
<form class="position-form" action="" method="POST">
|
||||
<div>
|
||||
<span class="position">{{ form.position }}</span>
|
||||
<span class="actions">
|
||||
<input type="submit" name="send_mail" value="Save and send email"/>
|
||||
<input type="submit" value="Save"/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style="clear:left"></div>
|
||||
|
||||
<div class="block_comment-widgets" {% ifnotequal form.position.initial "block" %}style="display:none"{% endifnotequal %}>
|
||||
<div class="block_comment">
|
||||
{{ form.block_comment.label_tag }}:
|
||||
{% if old_pos and old_pos.block_comment_time %}<span class="last-edited">(last edited {{ old_pos.block_comment_time }})</span>{% endif %}
|
||||
</div>
|
||||
{{ form.block_comment.errors }}
|
||||
{{ form.block_comment }}
|
||||
</div>
|
||||
|
||||
<div class="comment">
|
||||
{{ form.comment.label_tag }}:
|
||||
{% if old_pos and old_pos.comment_time %}<span class="last-edited">(last edited {{ old_pos.comment_time }}){% endif %}</span>
|
||||
</div>
|
||||
{{ form.comment }}
|
||||
|
||||
<div class="actions">
|
||||
<a href="{{ return_to_url }}">Back</a>
|
||||
</div>
|
||||
|
||||
{{ form.return_to_url }}
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block content_end %}
|
||||
<script type="text/javascript" src="/js/wg-edit-position.js"></script>
|
||||
{% endblock %}
|
5
ietf/templates/wgcharter/email_secretariat.txt
Normal file
5
ietf/templates/wgcharter/email_secretariat.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
{% autoescape off %}
|
||||
{{ text }}
|
||||
|
||||
WG Record URL: {{ url }}
|
||||
{% endautoescape %}
|
29
ietf/templates/wgcharter/in_process.html
Normal file
29
ietf/templates/wgcharter/in_process.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
Copyright The IETF Trust 2011, All Rights Reserved
|
||||
{% endcomment %}
|
||||
|
||||
{% block title %}Working Groups in IESG process{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Working Groups in IESG process</h1>
|
||||
|
||||
<p>
|
||||
<span id="create_wg" class="yui-button yui-link-button" style="margin-left:2px;"><span class="first-child"><a href="/wgcharter/create/">Start new WG charter effort</a></span></span>
|
||||
</p>
|
||||
|
||||
{% if not recs %}
|
||||
<p><b>No WGs match your query.</b></p>
|
||||
{% else %}
|
||||
<table class="ietf-table ietf-doctable">
|
||||
<tr>
|
||||
{% for hdr in meta.hdrs %}
|
||||
{% include "wgcharter/table_header.html" %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for wg in recs %}
|
||||
{% include "wgcharter/search_result_row.html" %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
38
ietf/templates/wgcharter/issue_ballot_mail.txt
Normal file
38
ietf/templates/wgcharter/issue_ballot_mail.txt
Normal file
|
@ -0,0 +1,38 @@
|
|||
{% autoescape off %}To: Internet Engineering Steering Group <iesg@ietf.org>
|
||||
From: IESG Secretary <iesg-secretary@ietf.org>
|
||||
Reply-To: IESG Secretary <iesg-secretary@ietf.org>
|
||||
Subject: Evaluation: {{ charter.chartered_group }} ({{ charter.chartered_group.acronym }})
|
||||
|
||||
{% filter wordwrap:73 %}Evaluation for {{ charter.chartered_group }} ({{ charter.chartered_group.acronym }}) can be found at {{ charter_url }}
|
||||
{% endfilter %}
|
||||
Please return the full line with your position.
|
||||
|
||||
Yes No Block Abstain
|
||||
{% for fmt in active_ad_positions %}{{ fmt }}
|
||||
{% endfor %}{% if inactive_ad_positions %}
|
||||
|
||||
{% for fmt in inactive_ad_positions %}{{ fmt }}
|
||||
{% endfor %}{% endif %}
|
||||
|
||||
No "Block" positions, are needed for approval.
|
||||
|
||||
BLOCKING AND NON-BLOCKING COMMENTS
|
||||
==================================
|
||||
{% filter wordwrap:79 %}{% for p in ad_feedback %}{{ p.ad }}:
|
||||
|
||||
{% if p.block_comment %}Blocking comment [{{ p.time }}]:
|
||||
{{ p.block_comment }}
|
||||
|
||||
{% endif %}{% if p.comment %}Comment [{{ p.time }}]:
|
||||
{{ p.comment }}
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}{% endfilter %}
|
||||
---- following is a DRAFT of message to be sent AFTER approval ---
|
||||
{{ approval_text }}
|
||||
|
||||
---- ballot text ----
|
||||
|
||||
{{ ballot_writeup }}
|
||||
|
||||
{% endautoescape%}
|
10
ietf/templates/wgcharter/review_text.txt
Normal file
10
ietf/templates/wgcharter/review_text.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% load ietf_filters %}{% autoescape off %}From: The IESG <iesg-secretary@ietf.org>
|
||||
To: IETF-Announce <ietf-announce@ietf.org>
|
||||
Subject: WG Review: {{ wg.name }} ({{wg.acronym}})
|
||||
|
||||
{% filter wordwrap:73 %}{% ifequal review_type "new" %}A new IETF working group has been proposed in the {{ wg.parent.name }}.{% endifequal %}{% ifequal review_type "recharter" %}The {{ wg.name }} ({{wg.acronym}}) working group in the {{ wg.parent.name }} of the IETF is undergoing rechartering.{% endifequal %} The IESG has not made any determination yet. The following draft charter was submitted, and is provided for informational purposes only. Please send your comments to the IESG mailing list (iesg at ietf.org) by {{ info.bydate }}.
|
||||
|
||||
{% include "wgcharter/wg_info.txt" %}
|
||||
|
||||
{% endfilter %}
|
||||
{% endautoescape %}
|
144
ietf/templates/wgcharter/search_form.html
Normal file
144
ietf/templates/wgcharter/search_form.html
Normal file
|
@ -0,0 +1,144 @@
|
|||
{% comment %}
|
||||
Copyright The IETF Trust 2011, All Rights Reserved
|
||||
{% endcomment %}
|
||||
|
||||
<form name="search_form" id="search_form" class="search_form" action="/wgcharter/search/" method="get">
|
||||
|
||||
<div class="search_field">
|
||||
<label>Name/acronym:</label> {{ form.nameacronym }}
|
||||
</div>
|
||||
<div class="search_field">
|
||||
<label>Types:</label>
|
||||
<table id="search_types">
|
||||
<tr><td>{{ form.inprocess }} WGs (in chartering process)</td></tr>
|
||||
<tr><td>{{ form.active }} WGs (approved charter)</td></tr>
|
||||
<tr><td>{{ form.concluded }} WGs (concluded or not under review)</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<span onclick="toggleAdvanced();"><b><img src="/images/{% if meta.advanced %}minus{% else %}plus{% endif %}.png" alt="" id="search_advanced-img" /> Advanced</b></span>
|
||||
|
||||
<div id="search_advanced" style="{% if not meta.advanced %}display:none;{%endif%}margin-top:1em;">
|
||||
Additional search criteria:
|
||||
|
||||
<div class="search_field">
|
||||
<label><input type="radio" class="radio" name="by" value="state" {% ifequal meta.by "state" %}checked="checked"{% endifequal %} onclick="changeBy();"/> State:</label> {{ form.state }} :: {{ form.charter_state }}
|
||||
</div>
|
||||
<div class="search_field">
|
||||
<label><input type="radio" class="radio" name="by" value="ad" {% ifequal meta.by "ad" %}checked="checked"{% endifequal %} onclick="changeBy();"/> Assigned AD:</label> {{ form.ad }}
|
||||
</div>
|
||||
<div class="search_field">
|
||||
<label><input type="radio" class="radio" name="by" value="area" {% ifequal meta.by "area" %}checked="checked"{% endifequal %} onclick="changeBy();"/> Area:</label> {{ form.area }}
|
||||
</div>
|
||||
<div class="search_field">
|
||||
<label><input type="radio" class="radio" name="by" value="anyfield" {% ifequal meta.by "anyfield" %}checked="checked"{% endifequal %} onclick="changeBy();"/> Text in any field:</label> {{ form.anyfield }}
|
||||
</div>
|
||||
<div class="search_field">
|
||||
<label><input type="radio" class="radio" name="by" value="eacronym" {% ifequal meta.by "eacronym" %}checked="checked"{% endifequal %} onclick="changeBy();"/> Earlier acronym:</label> {{ form.eacronym }}
|
||||
</div>
|
||||
</div><!-- end of advanced -->
|
||||
|
||||
<div style="padding-top:0.5em;">
|
||||
<span class="first-child">
|
||||
<button type="submit" name="search_submit" id="id_search_submit">Search</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style="padding-top:0.5em;">
|
||||
Quick search: <a href="{% url wg_search %}?nameacronym=&inprocess=on&by=state&state=&charter_state=intrev">WGs in Internal review</a> | <a href="{% url wg_search %}?nameacronym=&inprocess=on&by=state&state=&charter_state=extrev">WGs in External review</a> | <a href="{% url wg_search %}?nameacronym=&inprocess=on&by=state&state=&charter_state=iesgrev">WGs in IESG review</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
// we want to disable our submit button if we have no search text,
|
||||
// and we have no advanced options selected
|
||||
function toggleSubmit() {
|
||||
var button = document.getElementById("id_search_submit");
|
||||
var by = findCheckedSearchBy();
|
||||
var value = findSearchByValue(by);
|
||||
var concluded = document.getElementById("id_concluded");
|
||||
var text = document.getElementById("id_nameacronym");
|
||||
if ((value == "") && (text.value == "" && concluded.checked)) {
|
||||
button.disabled = true;
|
||||
} else {
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// check our button status after every change to text fields
|
||||
// Internet Explorer uses 'onpropertychange', everyone else 'oninput'
|
||||
window.oninput = toggleSubmit;
|
||||
window.onpropertychange = toggleSubmit;
|
||||
|
||||
// check our button status after every change to selection pulldowns
|
||||
window.onchange = toggleSubmit;
|
||||
|
||||
function togglePlusMinus(id) {
|
||||
var el = document.getElementById(id);
|
||||
var imgEl = document.getElementById(id+"-img");
|
||||
if (el.style.display == 'none') {
|
||||
el.style.display = 'block';
|
||||
imgEl.src = "/images/minus.png";
|
||||
} else {
|
||||
el.style.display = 'none';
|
||||
imgEl.src = "/images/plus.png";
|
||||
}
|
||||
}
|
||||
|
||||
function findCheckedSearchBy() {
|
||||
var by='';
|
||||
var f = document.search_form;
|
||||
for (var i = 0; i < f.by.length; i++) {
|
||||
if (f.by[i].checked) {
|
||||
by = f.by[i].value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return by;
|
||||
}
|
||||
|
||||
function findSearchByValue(by) {
|
||||
if (by == 'state') {
|
||||
// state might be wg state...
|
||||
state_value = document.getElementById("id_state").value;
|
||||
if (state_value) { return state_value; }
|
||||
// ...or charter state
|
||||
return document.getElementById("id_charter_state").value;
|
||||
}
|
||||
if (by == 'ad') { return document.getElementById("id_ad").value; }
|
||||
if (by == 'area') { return document.getElementById("id_area").value; }
|
||||
if (by == 'anyfield') { return document.getElementById("id_anyfield").value; }
|
||||
if (by == 'eacronym') { return document.getElementById("id_eacronym").value; }
|
||||
return '';
|
||||
}
|
||||
|
||||
function changeBy() {
|
||||
var by=findCheckedSearchBy();
|
||||
var f = document.search_form;
|
||||
f.state.disabled=true; f.charter_state.disabled=true;
|
||||
f.ad.disabled=true;
|
||||
f.area.disabled=true;
|
||||
f.anyfield.disabled=true;
|
||||
f.eacronym.disabled=true;
|
||||
if (by=='state') { f.state.disabled=false; f.charter_state.disabled=false;}
|
||||
if (by=='ad') { f.ad.disabled=false; }
|
||||
if (by=='area') { f.area.disabled=false;}
|
||||
if (by=='anyfield') { f.anyfield.disabled=false;}
|
||||
if (by=='eacronym') { f.eacronym.disabled=false;}
|
||||
|
||||
toggleSubmit();
|
||||
}
|
||||
|
||||
function toggleAdvanced() {
|
||||
togglePlusMinus("search_advanced");
|
||||
var f = document.search_form;
|
||||
for (var i = 0; i < f.by.length; i++) { f.by[i].checked = false; }
|
||||
changeBy();
|
||||
}
|
||||
|
||||
changeBy();
|
||||
|
||||
//]]>
|
||||
</script>
|
25
ietf/templates/wgcharter/search_main.html
Normal file
25
ietf/templates/wgcharter/search_main.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
Copyright 2011 The IETF Trust. All rights reserved.
|
||||
{% endcomment %}
|
||||
|
||||
{% block title %}Working Group Charters{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Working Group Charters</h1>
|
||||
|
||||
<div class="ietf-box search_form_box">
|
||||
{% include "wgcharter/search_form.html" %}
|
||||
</div>
|
||||
|
||||
<div id="search_results">
|
||||
{% if meta.searching %}
|
||||
{% include "wgcharter/search_results.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
{% block scripts %}
|
||||
YAHOO.util.Event.onContentReady("search_submit_button", function () {
|
||||
var oButton = new YAHOO.widget.Button("search_submit_button", {});
|
||||
});
|
||||
{% endblock scripts %}
|
13
ietf/templates/wgcharter/search_result_row.html
Normal file
13
ietf/templates/wgcharter/search_result_row.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
{% comment %}
|
||||
Copyright The IETF Trust 2011, All Rights Reserved
|
||||
{% endcomment %}
|
||||
|
||||
{% load ietf_filters %}
|
||||
<tr class="{% cycle oddrow,evenrow %}">
|
||||
<td class="acronym">
|
||||
<a href="{% url wg_view name=wg.acronym %}">{{ wg.acronym|safe }}</a>
|
||||
</td>
|
||||
<td class="title">{{ wg.name }}</td>
|
||||
{% include "wgcharter/date_column.html" %}
|
||||
{% include "wgcharter/status_columns.html" %}
|
||||
</tr>
|
22
ietf/templates/wgcharter/search_results.html
Normal file
22
ietf/templates/wgcharter/search_results.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
{% comment %}
|
||||
Copyright The IETF Trust 2011, All Rights Reserved
|
||||
{% endcomment %}
|
||||
|
||||
{% if meta.max %}
|
||||
<p><b>Too many WGs match the query! Returning partial result only.</b></p>
|
||||
{% endif %}
|
||||
{% if not recs %}
|
||||
<p><b>No WGs match your query.</b></p>
|
||||
{% else %}
|
||||
<table class="ietf-table ietf-doctable">
|
||||
<tr>
|
||||
{% for hdr in meta.hdrs %}
|
||||
{% include "wgcharter/table_header.html" %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for wg in recs %}
|
||||
{% include "wgcharter/search_result_row.html" %}
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
{% endif %}
|
40
ietf/templates/wgcharter/send_ballot_comment.html
Normal file
40
ietf/templates/wgcharter/send_ballot_comment.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
{% extends "base.html" %}
|
||||
{% load ietf_filters %}
|
||||
{% block title %}Send ballot position email for {{ ad }}{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
form.send-ballot pre {
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
border-top: 4px solid #eee;
|
||||
border-bottom: 4px solid #eee;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Send ballot position email for {{ ad }}</h1>
|
||||
|
||||
<form class="send-ballot" action="" method="POST">
|
||||
<table>
|
||||
<tr><th>From:</th> <td>{{ frm }}</td></tr>
|
||||
<tr><th>To:</th> <td>{{ to }}</td></tr>
|
||||
<tr>
|
||||
<th>Cc:<br/>
|
||||
<span class="help">separated<br/> by comma</span></th>
|
||||
<td><input type="text" name="cc" value="" size="75" /></td>
|
||||
</tr>
|
||||
<tr><th>Subject:</th> <td>{{ subject }}</td></tr>
|
||||
<tr>
|
||||
<th>Body:</th>
|
||||
<td><pre>{{ body|wrap_text }}</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td class="actions">
|
||||
<a href="{{ back_url }}">Back</a>
|
||||
<input type="submit" value="Send"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
{% endblock %}
|
32
ietf/templates/wgcharter/states.html
Normal file
32
ietf/templates/wgcharter/states.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
{% extends "base.html" %}
|
||||
{# Copyright The IETF Trust 2011, All Rights Reserved #}
|
||||
|
||||
{% block title %}Charter States{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
.state_column {
|
||||
width: 13em;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>Charter States</h1>
|
||||
|
||||
<table class="ietf-table">
|
||||
|
||||
<tr>
|
||||
<th class="state_column">State</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
|
||||
<tr class="{% cycle oddrow,evenrow as cycle1 %}"></tr>
|
||||
{% for state in states %}
|
||||
<tr class="{% cycle cycle1 %}">
|
||||
<td>{{ state.name|escape }}</td>
|
||||
<td>{{ state.desc|escape }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
17
ietf/templates/wgcharter/status_columns.html
Normal file
17
ietf/templates/wgcharter/status_columns.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
{% comment %}
|
||||
Copyright The IETF Trust 2011, All Rights Reserved
|
||||
{% endcomment %}
|
||||
{% load ietf_filters ietf_streams %}{% load wg_ballot_icon %}
|
||||
<td class="status">
|
||||
{% if wg.charter %}
|
||||
{{ wg.charter.get_state|safe }} {% ifequal wg.state_id "proposed" %}{% ifnotequal wg.charter.get_state_slug "notrev" %}(Initial Chartering){% endifnotequal %}{% else %}{% ifequal wg.state_id "active" %}{% ifnotequal wg.charter.get_state_slug "approved" %}(Rechartering){% endifnotequal %}{% endifequal %}{% ifequal wg.state_id "conclude" %}(Concluded){% endifequal %}{% endifequal %}
|
||||
{% else %}
|
||||
(data missing)
|
||||
{% endif %}
|
||||
{% if wg.charter.telechat_date %}<br/>IESG Telechat: {{ wg.charter.telechat_date }}{% endif %}
|
||||
|
||||
{% block extra_status %}{% endblock %}
|
||||
</td>
|
||||
<td class="ballot">
|
||||
{% if wg.charter.get_state_slug == "iesgrev" %}{% wg_ballot_icon wg.acronym %}{% endif %}
|
||||
</td>
|
40
ietf/templates/wgcharter/submit.html
Normal file
40
ietf/templates/wgcharter/submit.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block morecss %}
|
||||
form #id_content {
|
||||
width: 40em;
|
||||
height: 600px;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
Charter submission for {{ wg.acronym }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Charter submission for {{ wg.acronym }}</h1>
|
||||
|
||||
<p>The text will be submitted as <strong>charter-ietf-{{ wg.acronym }}-{{ next_rev }}</strong></p>
|
||||
<form class="edit-info" action="" enctype="multipart/form-data" method="POST">
|
||||
<table>
|
||||
{% for field in form.visible_fields %}
|
||||
<tr>
|
||||
<th>{{ field.label_tag }}:</th>
|
||||
<td>
|
||||
{{ field }}
|
||||
{% if field.help_text %}<div class="help">{{ field.help_text }}</div>{% endif %}
|
||||
{{ field.errors }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td class="actions">
|
||||
<a href="{% url wg_view name=wg.acronym %}">Back</a>
|
||||
<input type="submit" value="Submit"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
15
ietf/templates/wgcharter/table_header.html
Normal file
15
ietf/templates/wgcharter/table_header.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
{# Copyright The IETF Trust 2011, All Rights Reserved #}
|
||||
|
||||
<th class="{{hdr.htype}}"{% if hdr.colspan %}colspan="{{ hdr.colspan }}" {% endif %}
|
||||
onclick="location=unescape('{{ meta.rqps }}&sortBy={{hdr.htype}}');"
|
||||
style="white-space: nowrap;"
|
||||
>
|
||||
<span>
|
||||
<label>{{hdr.htitle}}</label>
|
||||
{% if hdr.selected %}
|
||||
<img style="border-style: none;vertical-align:top" src="/images/sort-header-filled.png"/>
|
||||
{% else %}
|
||||
<img style="border-style: none;vertical-align:top" src="/images/sort-header-clear.png"/>
|
||||
{% endif %}
|
||||
</span>
|
||||
</th>
|
57
ietf/templates/wgcharter/wg_ballot.html
Normal file
57
ietf/templates/wgcharter/wg_ballot.html
Normal file
|
@ -0,0 +1,57 @@
|
|||
{% comment %}
|
||||
Copyright The IETF Trust 2011, All Rights Reserved
|
||||
{% endcomment %}
|
||||
{% load ietf_filters %}
|
||||
<table class="ietf-ballot"><tr valign="top"><td class="left">
|
||||
|
||||
{% if user|in_group:"Area_Director,Secretariat" %}
|
||||
{% if user|in_group:"Area_Director" %}
|
||||
<div style="margin-top:8px; margin-bottom:8px;"><span id="wg_ballot_button" class="yui-button yui-link-button"><span class="first-child"><a href="{% url wg_edit_position name=wg.acronym %}">Edit position</a></span></span></div>
|
||||
{% endif %}
|
||||
{% if user|in_group:"Secretariat" %}
|
||||
{% if not info.pos_block %}<div style="margin-top:8px; margin-bottom:8px;"><span id="wg_ballot_button" class="yui-button yui-link-button"><span class="first-child"><a href="{% url wg_approve_ballot name=wg.acronym %}">Approve ballot</a></span></span></div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<p style="margin-top:1em;"><span class="square" style="background:#c00000;"></span><b>Blocking</b><br/>
|
||||
{% with info.pos_block as positions %}{% include "wgcharter/wg_ballot_list.html" %}{% endwith %}</p>
|
||||
|
||||
<p><span class="square" style="background:#80ff80;"></span><b>Yes</b><br/>
|
||||
{% with info.pos_yes as positions %}{% include "wgcharter/wg_ballot_list.html" %}{% endwith %}</p>
|
||||
|
||||
<p><span class="square" style="background:#80ff80;"></span><b>No</b><br/>
|
||||
{% with info.pos_no as positions %}{% include "wgcharter/wg_ballot_list.html" %}{% endwith %}</p>
|
||||
|
||||
<p><span class="square" style="background:#ffff00;"></span><b>Abstain</b><br/>
|
||||
{% with info.pos_abstain as positions %}{% include "wgcharter/wg_ballot_list.html" %}{% endwith %}</p>
|
||||
|
||||
<p><span class="square" style="background:white;"></span><b>No Record</b><br/>
|
||||
{% for p in info.pos_no_record %}
|
||||
<a{% if user|in_group:"Secretariat" %} href="{% url wg_edit_position name=wg.acronym %}?ad={{ p.id }}" title="Click to edit the position of {{ p.name }}"{% endif %}>{{p.name}}{% if user|in_group:"Secretariat" %}</a>{% endif %}<br/>
|
||||
{% empty %}
|
||||
<i>none</i>
|
||||
{% endfor %}
|
||||
</p>
|
||||
|
||||
</td>
|
||||
<td class="right">
|
||||
|
||||
<h2 style="margin-top:12px;">Comments</h2>
|
||||
|
||||
{% for pos in info.positions %}
|
||||
{% if pos.comment or pos.block_comment %}
|
||||
<h2 class="ballot_ad"><a name="{{pos.ad|slugify}}">{{pos.ad|escape}}</a></h2>
|
||||
|
||||
{% if pos.block_comment %}
|
||||
<p><b>Blocking ({{pos.block_comment_time}})</b> <img src="/images/comment.png" width="14" height="12" alt=""/></p>
|
||||
<pre>{{pos.block_comment|fill:"80"|escape }}</pre>
|
||||
{% endif %}
|
||||
|
||||
{% if pos.comment %}
|
||||
<p><b>Comment ({{pos.comment_time}})</b> <img src="/images/comment.png" width="14" height="12" alt=""/></p>
|
||||
<pre>{{pos.comment|fill:"80"|escape }}</pre>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</td></tr></table>
|
6
ietf/templates/wgcharter/wg_ballot_list.html
Normal file
6
ietf/templates/wgcharter/wg_ballot_list.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
{% load ietf_filters %}
|
||||
{% for p in positions %}
|
||||
{% for oad in info.old_ads %}{% ifequal oad p.ad %}[{%endifequal%}{% endfor %}<a{% if user|in_group:"Secretariat" %} href="{% url wg_edit_position name=wg.acronym %}?ad={{ p.ad_id }}" title="Click to edit the position of {{ p.ad }}"{% endif %}>{{p.ad}}</a>{% for oad in info.old_ads %}{% ifequal oad p.ad %}]{%endifequal%}{% endfor %}{% if p.comment or p.block_comment %} <a href="#{{p.ad|slugify}}"><img src="/images/comment.png" width="14" height="12" alt="*" border="0"/></a>{% endif %}<br/>
|
||||
{% empty %}
|
||||
<i>none</i>
|
||||
{% endfor %}
|
1
ietf/templates/wgcharter/wg_description.html
Normal file
1
ietf/templates/wgcharter/wg_description.html
Normal file
|
@ -0,0 +1 @@
|
|||
IETF Working Group {{ wg.name }} ({{ wg.acronym }}){% if wg.parent %} under the {{ wg.parent }}{% endif %}
|
40
ietf/templates/wgcharter/wg_history.html
Normal file
40
ietf/templates/wgcharter/wg_history.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
{% comment %}
|
||||
Copyright The IETF Trust 2011, All Rights Reserved
|
||||
{% endcomment %}
|
||||
|
||||
{% load ietf_filters %}
|
||||
<table class="ietf-table">
|
||||
<tr><th class="comment_date">Date</th><th>Version</th><th>By</th><th>Text</th></tr>
|
||||
|
||||
{% for c in history %}
|
||||
<tr class="{% cycle oddrow,evenrow %}"{% if c.is_com %} id="history-{{c.comment.id }}"{% endif %}>
|
||||
<td class="comment_date">{{ c.date|date:"Y-m-d" }}</td>
|
||||
|
||||
{% if c.is_rev %}
|
||||
<td>{{ c.info.version }}</td>
|
||||
<td>(System)</td>
|
||||
<td>New version available: <a href="{{ c.txt_url }}charter-ietf-{{ c.group.acronym }}-{{c.charter.rev}}.txt">charter-ietf-{{ c.group.acronym }}-{{ c.charter.rev }}</a> {% if c.prev_charter %}(<a href="http://tools.ietf.org/rfcdiff?url1={{ c.txt_url }}charter-ietf-{{ c.group.acronym }}-{{c.charter.rev}}.txt&url2={{ c.txt_url }}charter-ietf-{{ c.prev_group.acronym }}-{{c.prev_charter.rev}}.txt">diff from -{{ c.prev_charter.rev }}</a>){% endif %}</td>
|
||||
{% endif %}
|
||||
|
||||
{% if c.is_com %}
|
||||
<td>{{ c.info.version }}</td>
|
||||
<td>{{ c.info.by|escape }}</td>
|
||||
<td>{% if c.comment.ballot %}
|
||||
[Ballot {{ c.comment.get_ballot_display }}]<br />
|
||||
{% endif %}
|
||||
{% if c.info.snipped %}
|
||||
<div id="commentS{{c.comment.id}}">{{ c.info.textSnippet|safe }}</div>
|
||||
<span class="comment_toggle" onclick="toggleComment({{c.comment.id}})" id="commentT{{c.comment.id}}">[show all]</span>
|
||||
<div id="commentF{{c.comment.id}}" style="display:none;">
|
||||
{{ c.info.text|fill:"80"|safe|urlize|linebreaksbr|keep_spacing|sanitize_html|safe }}
|
||||
</div>
|
||||
{% else %}
|
||||
{{ c.info.text|fill:"80"|safe|urlize|linebreaksbr|keep_spacing|sanitize_html|safe }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
24
ietf/templates/wgcharter/wg_info.txt
Normal file
24
ietf/templates/wgcharter/wg_info.txt
Normal file
|
@ -0,0 +1,24 @@
|
|||
{{ wg.name }} ({{ wg.acronym }})
|
||||
------------------------------------------------
|
||||
Current Status: {{ wg.state.name }} Working Group
|
||||
|
||||
Chairs:
|
||||
{% for p in info.chairs %} {{ p.name }} <{{p.email}}>
|
||||
{% endfor %}
|
||||
Secretaries:
|
||||
{% for p in info.secr %} {{ p.name }} <{{p.email}}>
|
||||
{% endfor %}
|
||||
Technical advisors:
|
||||
{% for p in info.techadv %} {{ p.name }} <{{p.email}}>
|
||||
{% endfor %}
|
||||
Assigned Area Director:
|
||||
{{ info.ad.0.name }} <{{ info.ad.0.email }}>
|
||||
|
||||
Mailing list:
|
||||
Address: {{ info.list.0 }}
|
||||
To Subscribe: {{ info.list_subscribe.0 }}
|
||||
Archive: {{ info.list_archive.0 }}
|
||||
|
||||
Charter:
|
||||
|
||||
{{ info.charter_txt }}
|
72
ietf/templates/wgcharter/wg_main.html
Normal file
72
ietf/templates/wgcharter/wg_main.html
Normal file
|
@ -0,0 +1,72 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% comment %}
|
||||
Copyright The IETF Trust 2011, All Rights Reserved
|
||||
{% endcomment %}
|
||||
|
||||
{% load ietf_filters %}
|
||||
{% block morecss %}
|
||||
.metabox { width: 99%; margin-top:8px; padding:4px; margin-bottom:1em; }
|
||||
#metatable { border: 0; border-spacing: 0; }
|
||||
#metatable tr { vertical-align:top ;}
|
||||
.comment_toggle { text-decoration: underline; color: blue; }
|
||||
.comment_date { white-space: nowrap; }
|
||||
|
||||
div.diffTool { padding: 8px 4px; margin: 8px 0;}
|
||||
.diffTool label { float:left; width:50px; }
|
||||
|
||||
.markup_draft pre {line-height: 1.2em; margin: 0; }
|
||||
.m_hdr, .m_ftr { color: #808080; }
|
||||
.m_ftr { border-bottom: 1px solid #a0a0a0; }
|
||||
.m_h { font-family: arial; font-weight:bold;}
|
||||
.ietf-concluded-bg {background-color: #F8F8D0; }
|
||||
.ietf-concluded-warning { background:red;color:white;padding:2px 2px;}
|
||||
.ietf-proposed-bg { }
|
||||
.ietf-proposed-warning { background:green;color:white;padding:2px 2px;}
|
||||
{% endblock %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="alternate" type="application/atom+xml" href="/feed/wgcomments/{{ wg.acronym }}/" />
|
||||
<meta name="description" content="{% include "wgcharter/wg_description.html" %}" />
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}{% include "wgcharter/wg_title.html" %}{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% include "wgcharter/wg_title.html" %}
|
||||
{% ifequal wg.state_id "conclude" %}<br/><span class="ietf-concluded-warning">(concluded WG)</span>{% endifequal %}
|
||||
{% ifequal wg.state_id "proposed" %}<br/><span class="ietf-proposed-warning">(proposed WG)</span>{% endifequal %}
|
||||
</h1>
|
||||
|
||||
<div id="mytabs" class="yui-navset">
|
||||
<ul class="yui-nav">
|
||||
<li{% ifequal tab "charter" %} class="selected"{% endifequal %}><a href="/wgcharter/{{ wg.acronym }}"><em>Charter {% if snapshot %}(snapshot){% endif %}</em></a></li>
|
||||
<li{% if wg.charter %}{% ifequal wg.charter.get_state_slug "iesgrev" %}{% ifequal tab "ballot" %} class="selected"{% endifequal %}{%else%} class="disabled"{%endifequal%}{% else %} class="disabled"{% endif %}{% if snapshot %} class="disabled"{% endif %}><a href="/wgcharter/{{ wg.acronym }}/ballot/"><em>IESG Review</em></a></li>
|
||||
<li{% if wg.charter %}{% ifequal tab "writeup" %} class="selected"{% endifequal %}{% ifequal wg.charter.get_state_slug "notrev" %} class="disabled"{%endifequal%}{% else %} class="disabled"{% endif %}{% if snapshot %} class="disabled"{% endif %}><a href="/wgcharter/{{ wg.acronym }}/writeup/"><em>IESG Writeups</em></a></li>
|
||||
<li{% ifequal tab "history" %} class="selected"{% endifequal %}{% if snapshot %} class="disabled"{% endif %}><a href="/wgcharter/{{ wg.acronym }}/history/"><em>History</em></a></li>
|
||||
</ul>
|
||||
<div class="yui-content">
|
||||
|
||||
{% block tab_content %}{% endblock %}
|
||||
|
||||
</div> <!-- yui-content -->
|
||||
</div> <!-- mytabs -->
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block scripts %}
|
||||
function toggleComment(n) {
|
||||
var el = document.getElementById("commentF"+n);
|
||||
var el2 = document.getElementById("commentS"+n);
|
||||
var el3 = document.getElementById("commentT"+n);
|
||||
if (el.style.display == 'none') {
|
||||
el.style.display = 'block';
|
||||
el2.style.display = 'none';
|
||||
el3.innerHTML = ""; //[hide]";
|
||||
} else {
|
||||
el.style.display = 'none';
|
||||
el2.style.display= 'block';
|
||||
el3.innerHTML = "[show all]";
|
||||
}
|
||||
}
|
||||
{% endblock scripts %}
|
11
ietf/templates/wgcharter/wg_tab_ballot.html
Normal file
11
ietf/templates/wgcharter/wg_tab_ballot.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
{% extends "wgcharter/wg_main.html" %}
|
||||
{% comment %}
|
||||
Copyright The IETF Trust 2011, All Rights Reserved
|
||||
{% endcomment %}
|
||||
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% block tab_content %}
|
||||
{% include "wgcharter/wg_ballot.html" %}
|
||||
{% endblock tab_content %}
|
||||
|
59
ietf/templates/wgcharter/wg_tab_base.html
Normal file
59
ietf/templates/wgcharter/wg_tab_base.html
Normal file
|
@ -0,0 +1,59 @@
|
|||
{% extends "wgcharter/wg_main.html" %}
|
||||
|
||||
{% comment %}
|
||||
Copyright The IETF Trust 2011, All Rights Reserved
|
||||
{% endcomment %}
|
||||
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% block tab_content %}
|
||||
{% block wg_revision %}{% endblock %}
|
||||
<div class="ietf-box metabox">
|
||||
<table id="metatable" width="100%">
|
||||
{% block wg_metatable %}{% endblock %}
|
||||
</table>
|
||||
|
||||
<div style="padding-top:6px;padding-bottom:6px;padding-left:2px;">
|
||||
{% block wg_metalinks %}{% endblock %}
|
||||
</div>
|
||||
|
||||
{% block wg_metabuttons %}
|
||||
{% if user|in_group:"Area_Director,Secretariat" %}
|
||||
{% if not snapshot %}
|
||||
<div style="padding-bottom:2px;">
|
||||
{% ifnotequal wg.charter.get_state_slug "notrev" %}
|
||||
{% ifnotequal wg.charter.get_state_slug "approved" %}
|
||||
<span id="wg_edit_state_button" class="yui-button yui-link-button" style="margin-left:2px;"><span class="first-child"><a href="{% url wg_startstop_process name=wg.acronym option='abandon' %}">Abandon effort</a></span></span>
|
||||
<span id="wg_edit_state_button" class="yui-button yui-link-button" style="margin-left:2px;"><span class="first-child"><a href="{% url wg_change_state name=wg.acronym %}">Change state</a></span></span>
|
||||
{% ifnotequal wg.state_id "conclude" %}
|
||||
<span id="wg_edit_info_button" class="yui-button yui-link-button" style="margin-left:2px;"><span class="first-child"><a href="{% url wg_submit name=wg.acronym %}">Edit charter</a></span></span>
|
||||
{% endifnotequal %}
|
||||
{% else %}
|
||||
<span id="wg_edit_state_button" class="yui-button yui-link-button" style="margin-left:2px;"><span class="first-child"><a href="{% url wg_startstop_process name=wg.acronym option='recharter' %}">Recharter</a></span></span>
|
||||
{% endifnotequal %}
|
||||
{% else %}
|
||||
<span id="wg_edit_state_button" class="yui-button yui-link-button" style="margin-left:2px;"><span class="first-child"><a href="{% url wg_startstop_process name=wg.acronym option='recharter' %}">Recharter</a></span></span>
|
||||
{% endifnotequal %}
|
||||
|
||||
{% ifequal wg.state_id "active" %}{% ifequal wg.charter.get_state_slug "approved" %}
|
||||
<span id="wg_conclude_button" class="yui-button yui-link-button" style="margin-left:2px;"><span class="first-child"><a href="{% url wg_conclude name=wg.acronym %}">Conclude WG</a></span></span>
|
||||
{% endifequal %}{% endifequal %}
|
||||
|
||||
{% ifnotequal wg.state_id "conclude" %}
|
||||
<span id="wg_edit_info_button" class="yui-button yui-link-button" style="margin-left:2px;"><span class="first-child"><a href="{% url wg_edit_info name=wg.acronym %}">Edit WG</a></span></span>
|
||||
{% endifnotequal %}
|
||||
|
||||
|
||||
</div>
|
||||
{% endif %}{# if not snapshot #}
|
||||
{% endif %}{# if user in group #}
|
||||
{% endblock wg_metabuttons%}
|
||||
</div> <!-- metabox -->
|
||||
|
||||
<div id="charterText">
|
||||
{% block charter_text %}{% endblock %}
|
||||
</div> <!-- charterText -->
|
||||
{% endblock tab_content %}
|
||||
|
||||
{% block content_end %}
|
||||
{% endblock content_end %}
|
81
ietf/templates/wgcharter/wg_tab_charter.html
Normal file
81
ietf/templates/wgcharter/wg_tab_charter.html
Normal file
|
@ -0,0 +1,81 @@
|
|||
{% extends "wgcharter/wg_tab_base.html" %}
|
||||
|
||||
{% comment %}
|
||||
Copyright The IETF Trust 2011, All Rights Reserved
|
||||
{% endcomment %}
|
||||
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% block wg_revision %}
|
||||
Snapshots: {% for d in versions reversed %}{% if forloop.first %}<strong>{% ifnotequal rev wg.charter.rev %}<a href="{% url wg_view name=wg.acronym %}">{% endifnotequal %}{% else %}{% ifnotequal rev d.rev %}{% ifequal d.rev wg.charter.rev %}<a href="{% url wg_view name=wg.acronym %}">{% else %}<a href="{% url wg_view name=wg.acronym %}{{d.rev}}/">{% endifequal %}{% endifnotequal %}{% endif %}{{ d.rev }}{% ifnotequal rev d.rev %}</a>{% endifnotequal %}{% if forloop.first %}{% ifnotequal rev wg.charter.rev %}</a>{% endifnotequal %}</strong>{% endif %} {% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block wg_metatable %}
|
||||
<tr><td style="width: 15em">WG name:</td><td>{% if not snapshot %}{{ wg.name }}{% else %} {{ gh.name }} {% endif %}</td></tr>
|
||||
<tr><td>WG acronym:</td><td>{% if not snapshot %}{{ wg.acronym }} {% if info.prev_acronyms %}(previous acronyms: {% for a in info.prev_acronyms %}{{ a }}{% if not forloop.last %}, {% endif %}{% endfor %}){% endif %}{% else %}{{ gh.acronym }}{% endif %}</td></tr>
|
||||
<tr><td>IETF area:</td><td>{% if not snapshot %}{{ wg.parent|default:"-" }}{% else %}{{ gh.parent|default:"-" }}{% endif %}</td></tr>
|
||||
|
||||
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
|
||||
|
||||
<tr><td>WG chairs:</td><td>{% if not snapshot %}{% for n in info.chairs %}{{ n }}{% if not forloop.last %}, {% endif %}{% endfor %}{% else %}{% for n in info.history_chairs %}{{ n }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endif %}</td></tr>
|
||||
{% if not snapshot %}
|
||||
<tr><td>WG secretaries:</td><td>{% for n in info.secr %}{{ n }}{% if not forloop.last %}, {% endif %}{% endfor %}</td></tr>
|
||||
<tr><td>WG technical advisors:</td><td>{% for n in info.techadv %}{{ n }}{% if not forloop.last %}, {% endif %}{% endfor %}</td></tr>
|
||||
{% endif %}
|
||||
<tr><td>Assigned AD:</td><td>{% if not snapshot %}{{ wg.ad }}{% else %}{{ gh.ad }}{% endif %}</td></tr>
|
||||
|
||||
{% if not snapshot %}
|
||||
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
|
||||
|
||||
<tr><td>Mailing list:</td><td><a href="mailto:{{ wg.list_email }}">{{ wg.list_email }}</a>{% if info.prev_list_email %}<br />(previous lists: {% for a in info.prev_list_email %}{{ a }}{% if not forloop.last %}, {% else %}){% endif %}{% endfor %}{% endif %}</td></tr>
|
||||
<tr><td>Mailing list subscribe:</td><td><a href="{{ wg.list_subscribe }}">{{ wg.list_subscribe }}</a>{% if info.prev_list_subscribe %}<br />(previous archives: {% for a in info.prev_list_subscribe %}<a href="{{ a }}">{{ a }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}){% endif %}</td></tr>
|
||||
<tr><td>Mailing list archive:</td><td><a href="{{ wg.list_archive }}">{{ wg.list_archive }}</a>{% if info.prev_list_archive %}<br />(previous archives: {% for a in info.prev_list_archive %}<a href="{{ a }}">{{ a }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}){% endif %}</td></tr>
|
||||
<tr><td>Other web sites:</td><td>{% for a in wg.groupurl_set.all %}<a href="{{ a.url }}">{{ a.url }}</a> {% if a.name %}({{ a.name }}){% endif %}{% if not forloop.last %}<br />{% endif %}{% endfor %}</td></tr>
|
||||
|
||||
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
|
||||
|
||||
<tr><td>WG State:</td><td> {{ wg.state|safe }} </td>
|
||||
{% ifnotequal wg.state_id "conclude" %}<tr><td><a href="/wgcharter/help/state/">Charter State</>:</td><td> {{ wg.charter.get_state|safe }} {% ifequal wg.state_id "proposed" %}{% ifnotequal wg.charter.get_state_slug "notrev" %}(Initial Chartering){% endifnotequal %}{% else %}{% ifnotequal wg.charter.get_state_slug "approved" %}(Rechartering){% endifnotequal %}{% endifequal %}</td>{% endifnotequal %}
|
||||
{% ifequal wg.state_id "proposed" %}
|
||||
{% if wg.comments %}
|
||||
<tr><td>Reason for chartering:</td><td>{{ wg.comments }}</td></tr>
|
||||
{% endif %}
|
||||
{% endifequal %}
|
||||
{% ifequal wg.state_id "active" %}
|
||||
{% ifnotequal wg.charter.get_state_slug "approved" %}
|
||||
{% if wg.comments %}
|
||||
<tr><td>Reason for rechartering:</td><td>{{ wg.comments }}</td></tr>
|
||||
{% endif %}
|
||||
{% endifnotequal %}
|
||||
{% endifequal %}
|
||||
|
||||
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
|
||||
|
||||
<tr><td>Last updated:</td><td> {{ info.last_update|date:"Y-m-d"|default:"(data missing)" }}</td></tr>
|
||||
<tr><td colspan='2'><hr size='1' noshade /></td></tr>
|
||||
|
||||
{% endif %}{# if not snapshot #}
|
||||
|
||||
{% endblock wg_metatable %}
|
||||
|
||||
{% block wg_metalinks %}
|
||||
{% if not snapshot %}
|
||||
<a href="/feed/wgcomments/{{ wg.acronym }}/">Atom feed</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block charter_text %}
|
||||
{% ifnotequal charter.rev "" %}
|
||||
<p>Other versions: <a href="{{ charter_text_url }}{{charter.name}}-{{rev}}.txt">plain text</a></p>
|
||||
|
||||
<h3>Charter {{ charter.name }}-{{ rev }}</h3>
|
||||
|
||||
<div class="markup_draft">
|
||||
{{ content|fill:"80"|safe|linebreaksbr|keep_spacing|sanitize_html|safe }}
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<h3>The WG does not yet have a charter</h3>
|
||||
{% endifnotequal %}
|
||||
{% endblock %}{# charter_text #}
|
||||
|
18
ietf/templates/wgcharter/wg_tab_history.html
Normal file
18
ietf/templates/wgcharter/wg_tab_history.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
{% extends "wgcharter/wg_main.html" %}
|
||||
{% comment %}
|
||||
Copyright The IETF Trust 2011, All Rights Reserved
|
||||
{% endcomment %}
|
||||
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% block tab_content %}
|
||||
{% include "wgcharter/charter_diffs.html" %}
|
||||
<h2 style="margin-top:1em;">WG History</h2>
|
||||
{% if user|in_group:"Area_Director,Secretariat" %}
|
||||
<div style="margin-bottom:8px" id="history_actions">
|
||||
<span id="wg_add_comment_button" class="yui-button yui-link-button" style="margin-left:2px;"><span class="first-child"><a href="{% url wg_add_comment name=wg.acronym %}">Add comment</a></span></span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% include "wgcharter/wg_history.html" %}
|
||||
{% endblock tab_content %}
|
52
ietf/templates/wgcharter/wg_tab_writeup.html
Normal file
52
ietf/templates/wgcharter/wg_tab_writeup.html
Normal file
|
@ -0,0 +1,52 @@
|
|||
{% extends "wgcharter/wg_main.html" %}
|
||||
{% comment %}
|
||||
Copyright The IETF Trust 2011, All Rights Reserved
|
||||
{% endcomment %}
|
||||
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% block tab_content %}
|
||||
<h3>WG Review Announcement</h3>
|
||||
{% if user|in_group:"Area_Director,Secretariat" %}
|
||||
<div style="background:#E0E0FF">
|
||||
<p align=right>
|
||||
<span id="wg_edit_announce_button" class="yui-button yui-link-button"><span class="first-child">
|
||||
<a href="{% url wg_announcement_text name=wg.acronym ann="review" %}">Edit WG Review Announcement</a>
|
||||
</span></span>
|
||||
</p>
|
||||
{% endif %}
|
||||
<pre>
|
||||
{{ info.review_text|escape|urlize }}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<h3>WG Action Announcement</h3>
|
||||
{% if user|in_group:"Area_Director,Secretariat" %}
|
||||
<div style="background:#E0E0FF">
|
||||
<p align=right>
|
||||
<span id="wg_edit_announce_button" class="yui-button yui-link-button"><span class="first-child">
|
||||
<a href="{% url wg_announcement_text name=wg.acronym ann="action" %}">Edit WG Action Announcement</a>
|
||||
</span></span>
|
||||
</p>
|
||||
{% endif %}
|
||||
<pre>
|
||||
{{ info.action_text|escape|urlize }}
|
||||
</pre>
|
||||
|
||||
</div>
|
||||
|
||||
<h3>Ballot Announcement</h3>
|
||||
{% if user|in_group:"Area_Director,Secretariat" %}
|
||||
<div style="background:#E0E0FF">
|
||||
<p align=right>
|
||||
<span id="wg_edit_ballot_button" class="yui-button yui-link-button"><span class="first-child">
|
||||
<a href="{% url wg_ballot_writeupnotes name=wg.acronym %}">Edit Ballot Announcement</a>
|
||||
</span></span>
|
||||
</p>
|
||||
{% endif %}
|
||||
<pre>
|
||||
{{ info.ballot_text|escape|urlize }}
|
||||
</pre>
|
||||
|
||||
</div>
|
||||
{% endblock tab_content %}
|
1
ietf/templates/wgcharter/wg_title.html
Normal file
1
ietf/templates/wgcharter/wg_title.html
Normal file
|
@ -0,0 +1 @@
|
|||
{{ wg.name }} ({{wg.acronym}})
|
|
@ -49,11 +49,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
.ietf-wg-details tr { vertical-align: top; }
|
||||
.ietf-concluded-bg {background-color: #F8F8D0; }
|
||||
.ietf-concluded-warning { background:red;color:white;padding:2px 2px;}
|
||||
.ietf-proposed-bg { }
|
||||
.ietf-proposed-warning { background:green;color:white;padding:2px 2px;}
|
||||
{% endblock morecss %}
|
||||
|
||||
{% block content %}
|
||||
<div {% if concluded %}class="ietf-concluded-bg"{% endif %}>
|
||||
<h1>{{wg.group_acronym.name}} ({{wg.group_acronym.acronym}}){% if concluded %}<br/><span class="ietf-concluded-warning">(concluded WG)</span>{% endif %}</h1>
|
||||
<div {% if concluded %}class="ietf-concluded-bg"{% endif %} {% if proposed %}class="ietf-proposed-bg"{% endif %}>
|
||||
<h1>{{wg.group_acronym.name}} ({{wg.group_acronym.acronym}}){% if concluded %}<br/><span class="ietf-concluded-warning">(concluded WG)</span>{% endif %}{% if proposed %}<br/><span class="ietf-proposed-warning">(proposed WG)</span>{% endif %}</h1>
|
||||
|
||||
<div class="ietf-navset">
|
||||
{% ifequal selected "documents" %}<span class="selected">Documents</span>{% else %}<a href="/wg/{{wg}}/">Documents</a>{% endifequal %} |
|
||||
|
|
|
@ -59,7 +59,7 @@ is occasionally incorrect.</span>
|
|||
<tr><td>Area Director:</td>
|
||||
<td>
|
||||
{% if not wg.ad %}?{% else %}
|
||||
<a href="mailto:{{ wg.ad_email }}">{{ wg.ad.name }} <{{ wg.ad_email }}></a>{% endif %}
|
||||
<a href="mailto:{{ wg.areadirector.address }}">{{ wg.ad.name }} <{{ wg.areadirector.address }}></a>{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if wg.techadvisors %}
|
||||
|
@ -115,7 +115,7 @@ is occasionally incorrect.</span>
|
|||
{% if wg.additional_urls %}
|
||||
<p>In addition to the charter maintained by the IETF Secretariat, there is additional information about this working group on the Web at:
|
||||
{% for url in wg.additional_urls %}
|
||||
<a href="{{ url.url }}">{{ url.description}}</a>{% if not forloop.last %}, {% endif %}
|
||||
<a href="{{ url.url }}">{{ url.name }}</a>{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
|
|
@ -9,6 +9,7 @@ from ietf.idtracker.feeds import DocumentComments, InLastCall
|
|||
from ietf.ipr.feeds import LatestIprDisclosures
|
||||
from ietf.proceedings.feeds import LatestWgProceedingsActivity
|
||||
from ietf.liaisons.feeds import Liaisons
|
||||
from ietf.wgcharter.feeds import GroupComments
|
||||
|
||||
from ietf.idtracker.sitemaps import IDTrackerMap, DraftMap
|
||||
from ietf.liaisons.sitemaps import LiaisonMap
|
||||
|
@ -24,6 +25,7 @@ feeds = {
|
|||
'iesg-agenda': IESGAgenda,
|
||||
'last-call': InLastCall,
|
||||
'comments': DocumentComments,
|
||||
'wgcomments': GroupComments,
|
||||
'ipr': LatestIprDisclosures,
|
||||
'liaison': Liaisons,
|
||||
'wg-proceedings' : LatestWgProceedingsActivity
|
||||
|
@ -59,6 +61,7 @@ urlpatterns = patterns('',
|
|||
(r'^accounts/', include('ietf.ietfauth.urls')),
|
||||
(r'^doc/', include('ietf.idrfc.urls')),
|
||||
(r'^wg/', include('ietf.wginfo.urls')),
|
||||
(r'^wgcharter/', include('ietf.wgcharter.urls')),
|
||||
(r'^cookies/', include('ietf.cookies.urls')),
|
||||
(r'^submit/', include('ietf.submit.urls')),
|
||||
(r'^streams/', include('ietf.ietfworkflows.urls')),
|
||||
|
|
|
@ -10,6 +10,14 @@ from redesign.group.models import *
|
|||
from redesign.person.models import *
|
||||
|
||||
def make_test_data():
|
||||
# telechat dates
|
||||
t = datetime.date.today()
|
||||
old = TelechatDate.objects.create(date=t - datetime.timedelta(days=14)).date
|
||||
date1 = TelechatDate.objects.create(date=t).date
|
||||
date2 = TelechatDate.objects.create(date=t + datetime.timedelta(days=14)).date
|
||||
date3 = TelechatDate.objects.create(date=t + datetime.timedelta(days=14 * 2)).date
|
||||
date4 = TelechatDate.objects.create(date=t + datetime.timedelta(days=14 * 3)).date
|
||||
|
||||
# groups
|
||||
secretariat = Group.objects.create(
|
||||
name="Secretariat",
|
||||
|
@ -36,6 +44,13 @@ def make_test_data():
|
|||
state_id="active",
|
||||
type_id="area",
|
||||
parent=ietf)
|
||||
individ = Group.objects.create(
|
||||
name="Individual submissions",
|
||||
acronym="none",
|
||||
state_id="active",
|
||||
type_id="individ",
|
||||
parent=None)
|
||||
# mars WG
|
||||
group = Group.objects.create(
|
||||
name="Martian Special Interest Group",
|
||||
acronym="mars",
|
||||
|
@ -43,13 +58,52 @@ def make_test_data():
|
|||
type_id="wg",
|
||||
parent=area,
|
||||
)
|
||||
individ = Group.objects.create(
|
||||
name="Individual submissions",
|
||||
acronym="none",
|
||||
state_id="active",
|
||||
type_id="individ",
|
||||
parent=None)
|
||||
|
||||
charter = Document.objects.create(
|
||||
name="charter-ietf-" + group.acronym,
|
||||
type_id="charter",
|
||||
title=group.name,
|
||||
group=group,
|
||||
rev="00",
|
||||
)
|
||||
charter.set_state(State.objects.get(slug="approved", type="charter"))
|
||||
group.charter = charter
|
||||
group.save()
|
||||
DocAlias.objects.create(
|
||||
name=charter.name,
|
||||
document=charter
|
||||
)
|
||||
# ames WG
|
||||
group = Group.objects.create(
|
||||
name="Asteroid Mining Equipment Standardization Group",
|
||||
acronym="ames",
|
||||
state_id="proposed",
|
||||
type_id="wg",
|
||||
parent=area,
|
||||
)
|
||||
charter = Document.objects.create(
|
||||
name="charter-ietf-" + group.acronym,
|
||||
type_id="charter",
|
||||
title=group.name,
|
||||
group=group,
|
||||
rev="00",
|
||||
)
|
||||
charter.set_state(State.objects.get(slug="infrev", type="charter"))
|
||||
DocAlias.objects.create(
|
||||
name=charter.name,
|
||||
document=charter
|
||||
)
|
||||
group.charter = charter
|
||||
group.save()
|
||||
WGAction.objects.create(
|
||||
pk=group.pk,
|
||||
note="",
|
||||
status_date=datetime.date.today(),
|
||||
agenda=1,
|
||||
token_name="Aread",
|
||||
category=13,
|
||||
telechat_date=date2
|
||||
)
|
||||
|
||||
# persons
|
||||
|
||||
# system
|
||||
|
@ -109,6 +163,20 @@ def make_test_data():
|
|||
group=area,
|
||||
person=p,
|
||||
email=email)
|
||||
else:
|
||||
areahist = GroupHistory.objects.create(
|
||||
group=area,
|
||||
name=area.name,
|
||||
acronym=area.acronym,
|
||||
type_id=area.type_id,
|
||||
state_id=area.state_id,
|
||||
parent=area.parent
|
||||
)
|
||||
RoleHistory.objects.create(
|
||||
name_id="ad",
|
||||
group=areahist,
|
||||
person=p,
|
||||
email=email)
|
||||
|
||||
# group chair
|
||||
u = User.objects.create(username="marschairman")
|
||||
|
@ -224,33 +292,7 @@ def make_test_data():
|
|||
rev="00",
|
||||
)
|
||||
|
||||
# telechat dates
|
||||
t = datetime.date.today()
|
||||
old = TelechatDate.objects.create(date=t - datetime.timedelta(days=14)).date
|
||||
date1 = TelechatDate.objects.create(date=t).date
|
||||
date2 = TelechatDate.objects.create(date=t + datetime.timedelta(days=14)).date
|
||||
date3 = TelechatDate.objects.create(date=t + datetime.timedelta(days=14 * 2)).date
|
||||
date4 = TelechatDate.objects.create(date=t + datetime.timedelta(days=14 * 3)).date
|
||||
|
||||
# WG Actions
|
||||
group = Group.objects.create(
|
||||
name="Asteroid Mining Equipment Standardization Group",
|
||||
acronym="ames",
|
||||
state_id="proposed",
|
||||
type_id="wg",
|
||||
parent=area,
|
||||
)
|
||||
WGAction.objects.create(
|
||||
pk=group.pk,
|
||||
note="",
|
||||
status_date=datetime.date.today(),
|
||||
agenda=1,
|
||||
token_name="Aread",
|
||||
category=13,
|
||||
telechat_date=date2
|
||||
)
|
||||
|
||||
# Meeting
|
||||
# meeting
|
||||
Meeting.objects.create(
|
||||
number="42",
|
||||
type_id="ietf",
|
||||
|
|
|
@ -21,6 +21,7 @@ from workflows.models import Transition
|
|||
from redesign.doc.models import WriteupDocEvent
|
||||
from redesign.person.models import Person, Email
|
||||
from redesign.group.models import Role, RoleName
|
||||
from redesign.group.utils import save_group_in_history
|
||||
from redesign.name.models import DocTagName
|
||||
|
||||
|
||||
|
@ -45,7 +46,7 @@ class RelatedWGForm(forms.Form):
|
|||
|
||||
class TagForm(RelatedWGForm):
|
||||
|
||||
tags = forms.ModelMultipleChoiceField(AnnotationTag.objects.filter(wgworkflow__name='Default WG Workflow'),
|
||||
tags = forms.ModelMultipleChoiceField(AnnotationTag.objects.filter(workflow__name='Default WG Workflow'),
|
||||
widget=forms.CheckboxSelectMultiple, required=False)
|
||||
|
||||
def save(self):
|
||||
|
@ -58,7 +59,7 @@ class TagForm(RelatedWGForm):
|
|||
|
||||
class StateForm(RelatedWGForm):
|
||||
|
||||
states = forms.ModelMultipleChoiceField(State.objects.filter(wgworkflow__name='Default WG Workflow'),
|
||||
states = forms.ModelMultipleChoiceField(State.objects.filter(workflow__name='Default WG Workflow'),
|
||||
widget=forms.CheckboxSelectMultiple, required=False)
|
||||
|
||||
def update_transitions(self, workflow):
|
||||
|
@ -103,7 +104,7 @@ class DeleteTransitionForm(RelatedWGForm):
|
|||
|
||||
class TransitionForm(forms.ModelForm):
|
||||
|
||||
states = forms.ModelMultipleChoiceField(State.objects.filter(wgworkflow__name='Default WG Workflow'))
|
||||
states = forms.ModelMultipleChoiceField(State.objects.filter(workflow__name='Default WG Workflow'))
|
||||
|
||||
class Meta:
|
||||
model = Transition
|
||||
|
@ -182,6 +183,7 @@ class RemoveDelegateForm(RelatedWGForm):
|
|||
|
||||
def save(self):
|
||||
delegates = self.cleaned_data.get('delete')
|
||||
save_group_in_history(self.wg)
|
||||
WGDelegate.objects.filter(pk__in=delegates).delete()
|
||||
self.set_message('success', 'Delegates removed')
|
||||
|
||||
|
@ -281,7 +283,9 @@ class AddDelegateForm(RelatedWGForm):
|
|||
created = False
|
||||
e = Email.objects.get(address=self.cleaned_data.get('email'))
|
||||
if not Role.objects.filter(name="delegate", group=self.wg, person=person, email=e):
|
||||
delegate, created = Role.objects.get_or_create(
|
||||
created = True
|
||||
save_group_in_history(self.wg)
|
||||
delegate, _ = Role.objects.get_or_create(
|
||||
name=RoleName.objects.get(slug="delegate"), group=self.wg, person=e.person, email=e)
|
||||
else:
|
||||
(delegate, created) = WGDelegate.objects.get_or_create(wg=self.wg,
|
||||
|
|
|
@ -13,7 +13,7 @@ from ietf.utils.mail import outbox
|
|||
|
||||
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
|
||||
from redesign.person.models import Person, Email
|
||||
from redesign.group.models import Group, Role, GroupStateTransitions
|
||||
from redesign.group.models import Group, GroupHistory, Role, GroupStateTransitions
|
||||
from redesign.doc.models import Document, State, WriteupDocEvent
|
||||
from redesign.name.models import DocTagName
|
||||
|
||||
|
@ -87,6 +87,7 @@ class ManageDelegatesTestCase(django.test.TestCase):
|
|||
self.assertEquals(len(q('form input[name=email]')), 1)
|
||||
|
||||
# add existing person
|
||||
history_before = GroupHistory.objects.filter(acronym="mars").count()
|
||||
r = self.client.post(url,
|
||||
dict(email="plain@example.com",
|
||||
form_type="single"))
|
||||
|
@ -94,7 +95,9 @@ class ManageDelegatesTestCase(django.test.TestCase):
|
|||
q = PyQuery(r.content)
|
||||
self.assertTrue("new delegate" in r.content)
|
||||
self.assertTrue(Email.objects.get(address="plain@example.com").person.name in r.content)
|
||||
|
||||
self.assertEquals(Role.objects.filter(name="delegate", group__acronym="mars", email__address="plain@example.com").count(), 1)
|
||||
self.assertEquals(history_before + 1, GroupHistory.objects.filter(acronym="mars").count())
|
||||
|
||||
|
||||
class ManageShepherdsTestCase(django.test.TestCase):
|
||||
fixtures = ['names']
|
||||
|
|
1
ietf/wgcharter/__init__.py
Normal file
1
ietf/wgcharter/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
#
|
67
ietf/wgcharter/feeds.py
Normal file
67
ietf/wgcharter/feeds.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
# Copyright The IETF Trust 2011, All Rights Reserved
|
||||
|
||||
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 ietf.utils.history import find_history_active_at
|
||||
|
||||
from redesign.group.models import Group
|
||||
from ietf.wgcharter.views import _get_history, _get_html
|
||||
from wgcharter import markup_txt
|
||||
import datetime
|
||||
import re, os
|
||||
|
||||
class GroupComments(Feed):
|
||||
feed_type = Atom1Feed
|
||||
title_template = "feeds/wg_charter_title.html"
|
||||
description_template = "feeds/wg_charter_description.html"
|
||||
def get_object(self, bits):
|
||||
if len(bits) != 1:
|
||||
raise ObjectDoesNotExist
|
||||
return Group.objects.get(acronym=bits[0])
|
||||
|
||||
def title(self, obj):
|
||||
return "WG Record changes for %s" % obj.acronym
|
||||
|
||||
def link(self, obj):
|
||||
if obj is None:
|
||||
raise FeedDoesNotExist
|
||||
return reverse('wg_view', kwargs={'name': obj.acronym})
|
||||
|
||||
def description(self, obj):
|
||||
return self.title(obj)
|
||||
|
||||
def items(self, obj):
|
||||
history = _get_history(obj)
|
||||
for h in history:
|
||||
gh = find_history_active_at(obj, h['date'])
|
||||
if gh:
|
||||
h['chairs'] = [x.email.person.name for x in gh.rolehistory_set.filter(name__slug="chair")]
|
||||
h['secr'] = [x.email.person.name for x in gh.rolehistory_set.filter(name__slug="secr")]
|
||||
h['techadv'] = [x.email.person.name for x in gh.rolehistory_set.filter(name__slug="techadv")]
|
||||
else:
|
||||
h['chairs'] = [x.email.person.name for x in obj.role_set.filter(name__slug="chair")]
|
||||
h['secr'] = [x.email.person.name for x in obj.role_set.filter(name__slug="secr")]
|
||||
h['techadv'] = [x.email.person.name for x in obj.role_set.filter(name__slug="techadv")]
|
||||
dh = find_history_active_at(obj.charter, h['date'])
|
||||
if dh:
|
||||
h['rev'] = dh.rev
|
||||
h['charter'] = _get_html(
|
||||
str(dh.name)+"-"+str(dh.rev)+",html",
|
||||
os.path.join(dh.get_file_path(), dh.name+"-"+dh.rev+".txt"))
|
||||
else:
|
||||
h['rev'] = obj.charter.rev
|
||||
h['charter'] = _get_html(
|
||||
"charter-ietf-"+str(obj.acronym)+"-"+str(obj.charter.rev)+",html",
|
||||
os.path.join(obj.charter.get_file_path(), "charter-ietf-"+obj.acronym+"-"+obj.charter.rev+".txt"))
|
||||
return history
|
||||
|
||||
def item_link(self, obj):
|
||||
return reverse('wg_view', kwargs={'name': obj['group'].acronym})
|
||||
|
||||
def item_pubdate(self, obj):
|
||||
return obj['date']
|
||||
|
||||
|
114
ietf/wgcharter/mails.py
Normal file
114
ietf/wgcharter/mails.py
Normal file
|
@ -0,0 +1,114 @@
|
|||
# generation of mails
|
||||
|
||||
import textwrap
|
||||
from datetime import datetime, date, time, timedelta
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
|
||||
from ietf.utils.mail import send_mail, send_mail_text
|
||||
from ietf.idtracker.models import *
|
||||
from ietf.ipr.search import iprs_from_docs
|
||||
from redesign.doc.models import WriteupDocEvent, DocAlias, GroupBallotPositionDocEvent
|
||||
from redesign.person.models import Person
|
||||
|
||||
# These become part of the subject of the email
|
||||
types = {}
|
||||
types['state'] = "State changed"
|
||||
types['state-notrev'] = "State changed to Not currently under review"
|
||||
types['state-infrev'] = "State changed to Informal review"
|
||||
types['state-intrev'] = "State changed to Internal review"
|
||||
types['state-extrev'] = "State changed to External review"
|
||||
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"]
|
||||
|
||||
text = strip_tags(text)
|
||||
send_mail(request, to, None,
|
||||
"Regarding WG %s: %s" % (wg.acronym, types[type]),
|
||||
"wgcharter/email_secretariat.txt",
|
||||
dict(text=text,
|
||||
url=settings.IDTRACKER_BASE_URL + urlreverse('wg_view', kwargs=dict(name=wg.acronym))))
|
||||
|
||||
def generate_ballot_writeup(request, doc):
|
||||
e = WriteupDocEvent()
|
||||
e.type = "changed_ballot_writeup_text"
|
||||
e.by = request.user.get_profile()
|
||||
e.doc = doc
|
||||
e.desc = u"Ballot writeup was generated"
|
||||
e.text = unicode(render_to_string("wgcharter/ballot_writeup.txt"))
|
||||
e.save()
|
||||
|
||||
return e
|
||||
|
||||
def generate_issue_ballot_mail(request, charter):
|
||||
active_ads = Person.objects.filter(email__role__name="ad", email__role__group__state="active").distinct()
|
||||
|
||||
e = charter.latest_event(type="started_iesg_process")
|
||||
seen = []
|
||||
positions = []
|
||||
for p in GroupBallotPositionDocEvent.objects.filter(doc=charter, type="changed_ballot_position", time__gte=e.time).order_by("-time", '-id').select_related('ad'):
|
||||
if p.ad not in seen:
|
||||
positions.append(p)
|
||||
seen.append(p.ad)
|
||||
|
||||
# format positions and setup blocking and non-blocking comments
|
||||
ad_feedback = []
|
||||
seen = set()
|
||||
active_ad_positions = []
|
||||
inactive_ad_positions = []
|
||||
for p in positions:
|
||||
if p.ad in seen:
|
||||
continue
|
||||
|
||||
seen.add(p.ad)
|
||||
|
||||
def formatted(val):
|
||||
if val:
|
||||
return "[ X ]"
|
||||
else:
|
||||
return "[ ]"
|
||||
|
||||
fmt = u"%-21s%-6s%-6s%-8s%-7s" % (
|
||||
p.ad.name,
|
||||
formatted(p.pos_id == "yes"),
|
||||
formatted(p.pos_id == "no"),
|
||||
formatted(p.pos_id == "block"),
|
||||
formatted(p.pos_id == "abstain"),
|
||||
)
|
||||
|
||||
if p.ad in active_ads:
|
||||
active_ad_positions.append(fmt)
|
||||
if not p.pos_id == "block":
|
||||
p.block_comment = ""
|
||||
if p.comment or p.block_comment:
|
||||
ad_feedback.append(p)
|
||||
else:
|
||||
inactive_ad_positions.append(fmt)
|
||||
|
||||
active_ad_positions.sort()
|
||||
inactive_ad_positions.sort()
|
||||
ad_feedback.sort(key=lambda p: p.ad.name)
|
||||
|
||||
e = charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
|
||||
approval_text = e.text if e else ""
|
||||
|
||||
e = charter.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text")
|
||||
ballot_writeup = e.text if e else ""
|
||||
|
||||
return render_to_string("wgcharter/issue_ballot_mail.txt",
|
||||
dict(charter=charter,
|
||||
charter_url=settings.IDTRACKER_BASE_URL + charter.get_absolute_url(),
|
||||
active_ad_positions=active_ad_positions,
|
||||
inactive_ad_positions=inactive_ad_positions,
|
||||
ad_feedback=ad_feedback,
|
||||
approval_text=approval_text,
|
||||
ballot_writeup=ballot_writeup,
|
||||
)
|
||||
)
|
||||
|
66
ietf/wgcharter/markup_txt.py
Normal file
66
ietf/wgcharter/markup_txt.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
# Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
|
||||
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
#
|
||||
# * Neither the name of the Nokia Corporation and/or its
|
||||
# subsidiary(-ies) nor the names of its contributors may be used
|
||||
# to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from django.utils.html import escape
|
||||
import string
|
||||
import re
|
||||
|
||||
def markup(content):
|
||||
# normalize line endings to LF only
|
||||
content = content.replace("\r\n", "\n")
|
||||
content = content.replace("\r", "\n")
|
||||
|
||||
# at this point, "content" is normal string
|
||||
# fix most common non-ASCII characters
|
||||
t1 = string.maketrans("\x91\x92\x93\x94\x95\x96\x97\xc6\xe8\xe9", "\'\'\"\"o--\'ee")
|
||||
# map everything except printable ASCII, TAB, LF, FF to "?"
|
||||
t2 = string.maketrans('','')
|
||||
t3 = "?"*9 + "\t\n?\f" + "?"*19 + t2[32:127] + "?"*129
|
||||
t4 = t1.translate(t3)
|
||||
content = content.translate(t4)
|
||||
|
||||
# remove leading white space
|
||||
content = content.lstrip()
|
||||
# remove runs of blank lines
|
||||
content = re.sub("\n\n\n+", "\n\n", content)
|
||||
|
||||
# expand tabs + escape
|
||||
content = escape(content.expandtabs())
|
||||
|
||||
content = re.sub("\n(.+\[Page \d+\])\n\f\n(.+)\n", """\n<span class="m_ftr">\g<1></span>\n<span class="m_hdr">\g<2></span>\n""", content)
|
||||
content = re.sub("\n(.+\[Page \d+\])\n\s*$", """\n<span class="m_ftr">\g<1></span>\n""", content)
|
||||
# remove remaining FFs (to be valid XHTML)
|
||||
content = content.replace("\f","\n")
|
||||
|
||||
content = re.sub("\n\n([0-9]+\\.|[A-Z]\\.[0-9]|Appendix|Status of|Abstract|Table of|Full Copyright|Copyright|Intellectual Property|Acknowled|Author|Index)(.*)(?=\n\n)", """\n\n<span class="m_h">\g<1>\g<2></span>""", content)
|
||||
|
||||
return "<pre>"+content+"</pre>\n"
|
0
ietf/wgcharter/models.py
Normal file
0
ietf/wgcharter/models.py
Normal file
1
ietf/wgcharter/templatetags/__init__.py
Normal file
1
ietf/wgcharter/templatetags/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
#
|
124
ietf/wgcharter/templatetags/wg_ballot_icon.py
Normal file
124
ietf/wgcharter/templatetags/wg_ballot_icon.py
Normal file
|
@ -0,0 +1,124 @@
|
|||
# Copyright The IETF Trust 2011, All Rights Reserved
|
||||
|
||||
from django import template
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
from django.conf import settings
|
||||
from ietf.idtracker.templatetags.ietf_filters import in_group, timesince_days
|
||||
from redesign.doc.models import GroupBallotPositionDocEvent
|
||||
from redesign.person.models import Person
|
||||
from redesign.group.models import Group
|
||||
|
||||
register = template.Library()
|
||||
|
||||
def get_user_adid(context):
|
||||
if 'user' in context and in_group(context['user'], "Area_Director"):
|
||||
return context['user'].get_profile().id
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_user_name(context):
|
||||
if 'user' in context and context['user'].is_authenticated():
|
||||
from person.models import Person
|
||||
try:
|
||||
return context['user'].get_profile().name
|
||||
except Person.DoesNotExist:
|
||||
return None
|
||||
|
||||
def render_ballot_icon(context, name):
|
||||
wg = Group.objects.get(acronym=name)
|
||||
doc = wg.charter
|
||||
adId = get_user_adid(context)
|
||||
red = 0
|
||||
green = 0
|
||||
yellow = 0
|
||||
gray = 0
|
||||
blank = 0
|
||||
my = None
|
||||
|
||||
active_ads = list(Person.objects.filter(email__role__name="ad",
|
||||
email__role__group__type="area",
|
||||
email__role__group__state="active").distinct())
|
||||
started_process = doc.latest_event(type="started_iesg_process")
|
||||
latest_positions = []
|
||||
for p in active_ads:
|
||||
p_pos = list(GroupBallotPositionDocEvent.objects.filter(doc=doc, ad=p).order_by("-time"))
|
||||
if p_pos != []:
|
||||
latest_positions.append(p_pos[0])
|
||||
for p in latest_positions:
|
||||
if not p.pos_id:
|
||||
blank = blank + 1
|
||||
elif (p.pos_id == "yes"):
|
||||
green = green + 1
|
||||
elif (p.pos_id == "no"):
|
||||
green = green + 1
|
||||
elif (p.pos_id == "block"):
|
||||
red = red + 1
|
||||
elif (p.pos_id == "abstain"):
|
||||
yellow = yellow + 1
|
||||
else:
|
||||
blank = blank + 1
|
||||
if adId and (p.ad_id == adId):
|
||||
my = p.pos.name
|
||||
return render_ballot_icon2(wg.acronym, red,yellow,green,gray,blank, my, adId)+"<!-- adId="+str(adId)+" my="+str(my)+"-->"
|
||||
|
||||
def render_ballot_icon2(acronym, red,yellow,green,gray,blank, my, adId):
|
||||
edit_position_url = urlreverse('wg_edit_position', kwargs=dict(name=acronym))
|
||||
if adId:
|
||||
res_cm = ' oncontextmenu="editWGBallot(\''+str(edit_position_url)+'\');return false;"'
|
||||
else:
|
||||
res_cm = ''
|
||||
res = '<table class="ballot_icon" title="IESG Review (click to show more, right-click to edit position)" onclick="showWGBallot(\'' + acronym + '\',\'' + str(edit_position_url) + '\')"'+res_cm+'>'
|
||||
for y in range(3):
|
||||
res = res + "<tr>"
|
||||
for x in range(5):
|
||||
myMark = False
|
||||
if red > 0:
|
||||
c = "ballot_icon_red"
|
||||
red = red - 1
|
||||
myMark = (my == "Block")
|
||||
elif yellow > 0:
|
||||
c = "ballot_icon_yellow"
|
||||
yellow = yellow - 1
|
||||
myMark = (my == "Abstain")
|
||||
elif green > 0:
|
||||
c = "ballot_icon_green"
|
||||
green = green - 1
|
||||
myMark = (my == "Yes") or (my == "No")
|
||||
else:
|
||||
c = ""
|
||||
myMark = (y == 2) and (x == 4) and (my == "No Record")
|
||||
if myMark:
|
||||
res = res + '<td class="'+c+' ballot_icon_my" />'
|
||||
my = None
|
||||
else:
|
||||
res = res + '<td class="'+c+'" />'
|
||||
res = res + '</tr>'
|
||||
res = res + '</table>'
|
||||
return res
|
||||
|
||||
|
||||
class BallotIconNode(template.Node):
|
||||
def __init__(self, doc_var):
|
||||
self.doc_var = doc_var
|
||||
def render(self, context):
|
||||
doc = template.resolve_variable(self.doc_var, context)
|
||||
return render_ballot_icon(context, doc)
|
||||
|
||||
def do_ballot_icon(parser, token):
|
||||
try:
|
||||
tagName, docName = token.split_contents()
|
||||
except ValueError:
|
||||
raise template.TemplateSyntaxError, "%r tag requires exactly two arguments" % token.contents.split()[0]
|
||||
return BallotIconNode(docName)
|
||||
|
||||
register.tag('wg_ballot_icon', do_ballot_icon)
|
||||
|
||||
@register.filter
|
||||
def my_position(doc, user):
|
||||
user_name = get_user_name({'user':user})
|
||||
p_pos = list(GroupBallotPositionDocEvent.objects.filter(doc=doc, ad=Person.objects.get(user__name=user_name)).order_by("-time"))
|
||||
if p_pos == []:
|
||||
return "No record"
|
||||
else:
|
||||
return p_pos[0].pos.name
|
||||
|
559
ietf/wgcharter/tests.py
Normal file
559
ietf/wgcharter/tests.py
Normal file
|
@ -0,0 +1,559 @@
|
|||
# Copyright The IETF Trust 2011, All Rights Reserved
|
||||
|
||||
import os, shutil, datetime
|
||||
from StringIO import StringIO
|
||||
|
||||
import django.test
|
||||
from django.conf import settings
|
||||
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 redesign.doc.models import *
|
||||
from redesign.group.models import *
|
||||
from redesign.group.utils import *
|
||||
from redesign.name.models import *
|
||||
from redesign.person.models import *
|
||||
from ietf.iesg.models import TelechatDate
|
||||
|
||||
from utils import *
|
||||
|
||||
class SearchTestCase(django.test.TestCase):
|
||||
fixtures = ['names']
|
||||
|
||||
def test_search(self):
|
||||
make_test_data()
|
||||
|
||||
group = Group.objects.get(acronym="mars")
|
||||
group.charter.set_state(State.objects.get(slug="infrev", type="charter"))
|
||||
|
||||
r = self.client.get("/wgcharter/")
|
||||
self.assertEquals(r.status_code, 200)
|
||||
|
||||
r = self.client.get(urlreverse("wg_search"))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
|
||||
r = self.client.get(urlreverse("wg_search_in_process"))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
|
||||
r = self.client.get(urlreverse("wg_search_by_area", kwargs=dict(name=group.parent.acronym)))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
|
||||
r = self.client.get(urlreverse("wg_search") + "?nameacronym=%s" % group.name.replace(" ", "+"))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
|
||||
r = self.client.get(urlreverse("wg_search") + "?nameacronym=something")
|
||||
self.assertEquals(r.status_code, 200)
|
||||
|
||||
r = self.client.get(urlreverse("wg_search") + "?nameacronym=something&by=acronym&acronym=some")
|
||||
self.assertEquals(r.status_code, 200)
|
||||
|
||||
r = self.client.get(urlreverse("wg_search") + "?nameacronym=something&by=state&state=active&charter_state=")
|
||||
self.assertEquals(r.status_code, 200)
|
||||
|
||||
r = self.client.get(urlreverse("wg_search") + "?nameacronym=something&by=state&state=&charter_state=%s" % State.objects.get(type="charter", slug="approved").pk)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
|
||||
r = self.client.get(urlreverse("wg_search") + "?nameacronym=something&by=ad&ad=%s" % Person.objects.get(name="Aread Irector").pk)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
|
||||
r = self.client.get(urlreverse("wg_search") + "?nameacronym=something&by=area&area=%s" % group.parent.pk)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
|
||||
r = self.client.get(urlreverse("wg_search") + "?nameacronym=something&by=anyfield&anyfield=something")
|
||||
self.assertEquals(r.status_code, 200)
|
||||
|
||||
r = self.client.get(urlreverse("wg_search") + "?nameacronym=something&by=eacronym&eacronym=someold")
|
||||
self.assertEquals(r.status_code, 200)
|
||||
|
||||
class WgStateTestCase(django.test.TestCase):
|
||||
fixtures = ['names']
|
||||
|
||||
def test_change_state(self):
|
||||
make_test_data()
|
||||
|
||||
group = Group.objects.get(acronym="ames")
|
||||
charter = group.charter
|
||||
|
||||
# -- Test change state --
|
||||
url = urlreverse('wg_change_state', kwargs=dict(name=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
first_state = charter.get_state()
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEquals(len(q('form select[name=charter_state]')), 1)
|
||||
|
||||
# faulty post
|
||||
r = self.client.post(url, dict(charter_state="-12345"))
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertEquals(charter.get_state(), first_state)
|
||||
|
||||
# change state
|
||||
for slug in ("intrev", "extrev", "iesgrev", "approved"):
|
||||
s = State.objects.get(type="charter", slug=slug)
|
||||
events_before = charter.docevent_set.count()
|
||||
mailbox_before = len(outbox)
|
||||
|
||||
r = self.client.post(url, dict(state="active", charter_state=s.pk, message="test message"))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
|
||||
charter = Document.objects.get(name="charter-ietf-%s" % group.acronym)
|
||||
self.assertEquals(charter.get_state_slug(), slug)
|
||||
self.assertEquals(charter.docevent_set.count(), events_before + 1)
|
||||
self.assertTrue("State changed" in charter.docevent_set.all()[0].desc)
|
||||
if slug == "extrev":
|
||||
self.assertEquals(len(outbox), mailbox_before + 2)
|
||||
self.assertTrue("State changed" in outbox[-1]['Subject'])
|
||||
self.assertTrue("State changed" in outbox[-2]['Subject'])
|
||||
else:
|
||||
self.assertEquals(len(outbox), mailbox_before + 1)
|
||||
if slug == "approved":
|
||||
self.assertTrue("Charter approved" in outbox[-1]['Subject'])
|
||||
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']
|
||||
|
||||
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()
|
||||
|
||||
# -- 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()
|
||||
|
||||
# And make a charter for group
|
||||
group = Group.objects.get(acronym="mars")
|
||||
charter = group.charter
|
||||
|
||||
url = urlreverse('wg_edit_info', kwargs=dict(name=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
# add to telechat
|
||||
self.assertTrue(not charter.latest_event(TelechatDocEvent, "scheduled_for_telechat"))
|
||||
telechat_date = TelechatDate.objects.active()[0].date
|
||||
r = self.client.post(url, dict(name=group.name, acronym=group.acronym, telechat_date=telechat_date.isoformat()))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
|
||||
charter = Document.objects.get(name=charter.name)
|
||||
self.assertTrue(charter.latest_event(TelechatDocEvent, "scheduled_for_telechat"))
|
||||
self.assertEquals(charter.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date, telechat_date)
|
||||
|
||||
# change telechat
|
||||
telechat_date = TelechatDate.objects.active()[1].date
|
||||
r = self.client.post(url, dict(name=group.name, acronym=group.acronym, telechat_date=telechat_date.isoformat()))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
|
||||
charter = Document.objects.get(name=charter.name)
|
||||
self.assertEquals(charter.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date, telechat_date)
|
||||
|
||||
# remove from agenda
|
||||
telechat_date = ""
|
||||
r = self.client.post(url, dict(name=group.name, acronym=group.acronym, telechat_date=telechat_date))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
|
||||
charter = Document.objects.get(name=charter.name)
|
||||
self.assertTrue(not charter.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date)
|
||||
|
||||
def test_submit_charter(self):
|
||||
make_test_data()
|
||||
|
||||
# And make a charter for group
|
||||
group = Group.objects.get(acronym="mars")
|
||||
charter = group.charter
|
||||
|
||||
url = urlreverse('wg_submit', 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 input[name=txt]')), 1)
|
||||
|
||||
prev_rev = charter.rev
|
||||
|
||||
test_file = StringIO("hello world")
|
||||
test_file.name = "unnamed"
|
||||
|
||||
r = self.client.post(url, dict(txt=test_file))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
|
||||
charter = Document.objects.get(name="charter-ietf-%s" % group.acronym)
|
||||
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']
|
||||
|
||||
def test_edit_position(self):
|
||||
make_test_data()
|
||||
|
||||
group = Group.objects.get(acronym="mars")
|
||||
charter = group.charter
|
||||
|
||||
url = urlreverse('wg_edit_position', kwargs=dict(name=group.acronym))
|
||||
login_testing_unauthorized(self, "ad", url)
|
||||
|
||||
p = Person.objects.get(name="Aread Irector")
|
||||
|
||||
e = DocEvent()
|
||||
e.type = "started_iesg_process"
|
||||
e.by = p
|
||||
e.doc = charter
|
||||
e.desc = "IESG process started"
|
||||
e.save()
|
||||
|
||||
charter.set_state(State.objects.get(type="charter", slug="iesgrev"))
|
||||
charter.save()
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form input[name=position]')) > 0)
|
||||
self.assertEquals(len(q('form textarea[name=comment]')), 1)
|
||||
|
||||
# vote
|
||||
pos_before = charter.docevent_set.filter(type="changed_ballot_position").count()
|
||||
self.assertTrue(not charter.docevent_set.filter(type="changed_ballot_position", by__name="Aread Irector"))
|
||||
|
||||
r = self.client.post(url, dict(position="block",
|
||||
block_comment="This is a blocking test.",
|
||||
comment="This is a test."))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
|
||||
self.assertEquals(charter.docevent_set.filter(type="changed_ballot_position").count(), pos_before + 1)
|
||||
e = charter.latest_event(GroupBallotPositionDocEvent)
|
||||
self.assertTrue("This is a blocking test." in e.block_comment)
|
||||
self.assertTrue("This is a test." in e.comment)
|
||||
self.assertTrue(e.pos_id, "block")
|
||||
|
||||
# recast vote
|
||||
pos_before = charter.docevent_set.filter(type="changed_ballot_position").count()
|
||||
|
||||
r = self.client.post(url, dict(position="yes"))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
|
||||
self.assertEquals(charter.docevent_set.filter(type="changed_ballot_position").count(), pos_before + 1)
|
||||
e = charter.latest_event(GroupBallotPositionDocEvent)
|
||||
self.assertTrue(e.pos_id, "yes")
|
||||
|
||||
# clear vote
|
||||
pos_before = charter.docevent_set.filter(type="changed_ballot_position").count()
|
||||
|
||||
r = self.client.post(url, dict(position="norecord"))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
|
||||
self.assertEquals(charter.docevent_set.filter(type="changed_ballot_position").count(), pos_before + 1)
|
||||
e = charter.latest_event(GroupBallotPositionDocEvent)
|
||||
self.assertTrue(e.pos_id, "norecord")
|
||||
|
||||
def test_edit_position_as_secretary(self):
|
||||
make_test_data()
|
||||
|
||||
group = Group.objects.get(acronym="mars")
|
||||
charter = group.charter
|
||||
|
||||
url = urlreverse('wg_edit_position', kwargs=dict(name=group.acronym))
|
||||
p = Person.objects.get(name="Aread Irector")
|
||||
url += "?ad=%d" % p.id
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
e = DocEvent()
|
||||
e.type = "started_iesg_process"
|
||||
e.by = p
|
||||
e.doc = charter
|
||||
e.desc = "IESG process started"
|
||||
e.save()
|
||||
|
||||
charter.set_state(State.objects.get(type="charter", slug="iesgrev"))
|
||||
charter.save()
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form input[name=position]')) > 0)
|
||||
|
||||
# vote for rhousley
|
||||
pos_before = charter.docevent_set.filter(type="changed_ballot_position").count()
|
||||
self.assertTrue(not charter.docevent_set.filter(type="changed_ballot_position", by__name="Sec Retary"))
|
||||
|
||||
r = self.client.post(url, dict(position="no"))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
|
||||
self.assertEquals(charter.docevent_set.filter(type="changed_ballot_position").count(), pos_before + 1)
|
||||
e = charter.latest_event(GroupBallotPositionDocEvent)
|
||||
self.assertTrue(e.pos_id, "no")
|
||||
|
||||
def test_send_ballot_comment(self):
|
||||
make_test_data()
|
||||
|
||||
group = Group.objects.get(acronym="mars")
|
||||
charter = group.charter
|
||||
|
||||
url = urlreverse('wg_send_ballot_comment', kwargs=dict(name=group.acronym))
|
||||
login_testing_unauthorized(self, "ad", url)
|
||||
|
||||
p = Person.objects.get(name="Aread Irector")
|
||||
|
||||
e = DocEvent()
|
||||
e.type = "started_iesg_process"
|
||||
e.by = p
|
||||
e.doc = charter
|
||||
e.desc = "IESG process started"
|
||||
e.save()
|
||||
|
||||
charter.set_state(State.objects.get(type="charter", slug="iesgrev"))
|
||||
charter.save()
|
||||
|
||||
GroupBallotPositionDocEvent.objects.create(
|
||||
doc=charter,
|
||||
by=p,
|
||||
type="changed_ballot_position",
|
||||
pos=GroupBallotPositionName.objects.get(slug="block"),
|
||||
ad=p,
|
||||
block_comment="This is a block test",
|
||||
comment="This is a test",
|
||||
)
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form input[name="cc"]')) > 0)
|
||||
|
||||
# send
|
||||
p = Person.objects.get(name="Aread Irector")
|
||||
mailbox_before = len(outbox)
|
||||
|
||||
r = self.client.post(url, dict(cc="test@example.com", cc_state_change="1"))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
|
||||
self.assertEquals(len(outbox), mailbox_before + 1)
|
||||
self.assertTrue("BLOCKING COMMENT" in outbox[-1]['Subject'])
|
||||
self.assertTrue("COMMENT" in outbox[-1]['Subject'])
|
||||
|
||||
class WgApproveBallotTestCase(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_approve_ballot(self):
|
||||
make_test_data()
|
||||
|
||||
group = Group.objects.get(acronym="ames")
|
||||
charter = group.charter
|
||||
|
||||
url = urlreverse('wg_approve_ballot', kwargs=dict(name=group.acronym))
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
with open(os.path.join(self.charter_dir, "charter-ietf-%s-%s.txt" % (group.acronym, charter.rev)), "w") as f:
|
||||
f.write("This is a charter.")
|
||||
|
||||
p = Person.objects.get(name="Aread Irector")
|
||||
|
||||
e = DocEvent()
|
||||
e.type = "started_iesg_process"
|
||||
e.by = p
|
||||
e.doc = charter
|
||||
e.desc = "IESG process started"
|
||||
e.save()
|
||||
|
||||
charter.set_state(State.objects.get(type="charter", slug="iesgrev"))
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue("Send out the announcement" in q('input[type=submit]')[0].get('value'))
|
||||
self.assertEquals(len(q('pre')), 1)
|
||||
|
||||
# approve
|
||||
mailbox_before = len(outbox)
|
||||
|
||||
r = self.client.post(url, dict())
|
||||
self.assertEquals(r.status_code, 302)
|
||||
|
||||
charter = Document.objects.get(name=charter.name)
|
||||
self.assertEquals(charter.get_state_slug(), "approved")
|
||||
|
||||
self.assertEquals(charter.rev, "01")
|
||||
self.assertTrue(os.path.exists(os.path.join(self.charter_dir, "charter-ietf-%s-%s.txt" % (group.acronym, charter.rev))))
|
||||
|
||||
self.assertEquals(len(outbox), mailbox_before + 2)
|
||||
self.assertTrue("WG Action" in outbox[-1]['Subject'])
|
||||
self.assertTrue("Charter approved" in outbox[-2]['Subject'])
|
55
ietf/wgcharter/testurl.list
Normal file
55
ietf/wgcharter/testurl.list
Normal file
|
@ -0,0 +1,55 @@
|
|||
200 /
|
||||
200 /wgcharter/
|
||||
|
||||
# In IESG process
|
||||
200 /wgcharter/in_process/
|
||||
|
||||
# by AD
|
||||
# current AD -- needs to be updated at some point
|
||||
200 /wgcharter/ad/aread.irector/
|
||||
# former AD
|
||||
200 /wgcharter/ad/exaread.irector/
|
||||
404 /wgcharter/ad/no.body/
|
||||
|
||||
# create
|
||||
302 /wgcharter/create/
|
||||
|
||||
# WG
|
||||
200 /wgcharter/mars/
|
||||
200 /wgcharter/mars/00-00/
|
||||
200 /wgcharter/mars/_ballot.data
|
||||
|
||||
# Edit WG
|
||||
302 /wgcharter/mars/edit/state/
|
||||
302 /wgcharter/mars/edit/info/
|
||||
302 /wgcharter/mars/edit/conclude/
|
||||
302 /wgcharter/mars/edit/addcomment/
|
||||
302 /wgcharter/mars/edit/action/
|
||||
302 /wgcharter/mars/edit/review/
|
||||
|
||||
# ballots
|
||||
302 /wgcharter/mars/edit/position/
|
||||
302 /wgcharter/mars/edit/sendballotcomment/
|
||||
302 /wgcharter/mars/edit/approveballot/
|
||||
|
||||
# submission of charters
|
||||
302 /wgcharter/mars/submit/
|
||||
|
||||
# search
|
||||
200 /wgcharter/search/
|
||||
302 /wgcharter/search/?name=martian
|
||||
200 /wgcharter/search/?name=something
|
||||
200 /wgcharter/search/?name=something&by=acronym&acronym=some
|
||||
200 /wgcharter/search/?name=something&by=state&state=active&charter_state=
|
||||
200 /wgcharter/search/?name=something&by=state&state=&charter_state=approved
|
||||
200 /wgcharter/search/?name=something&by=ad&ad=1
|
||||
200 /wgcharter/search/?name=something&by=area&area=2
|
||||
200 /wgcharter/search/?name=something&by=anyfield&anyfield=something
|
||||
200 /wgcharter/search/?name=something&by=eacronym&eacronym=someold
|
||||
|
||||
# searchPerson (ajax)
|
||||
200 /wgcharter/searchPerson/
|
||||
|
||||
#
|
||||
|
||||
|
31
ietf/wgcharter/urls.py
Normal file
31
ietf/wgcharter/urls.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# 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 redesign.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('',
|
||||
(r'^/?$', views_search.search_main),
|
||||
url(r'^create/$', views_edit.edit_info, name="wg_create"),
|
||||
url(r'^search/$', views_search.search_results, name="wg_search"),
|
||||
(r'^searchPerson/$', views_search.search_person),
|
||||
url(r'^area/(?P<name>[A-Za-z0-9.-]+)/$', views_search.by_area, name="wg_search_by_area"),
|
||||
url(r'^in_process/$', views_search.in_process, name="wg_search_in_process"),
|
||||
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/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/addcomment/$', views_edit.add_comment, name='wg_add_comment'),
|
||||
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'),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/ballotwriteupnotes/$', views_ballot.ballot_writeupnotes, name='wg_ballot_writeupnotes'),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/edit/approveballot/$', views_ballot.approve_ballot, name='wg_approve_ballot'),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/submit/$', views_submit.submit, name='wg_submit'),
|
||||
|
||||
)
|
216
ietf/wgcharter/utils.py
Normal file
216
ietf/wgcharter/utils.py
Normal file
|
@ -0,0 +1,216 @@
|
|||
from django.conf import settings
|
||||
import re
|
||||
|
||||
from datetime import datetime
|
||||
from group.models import GroupEvent
|
||||
from doc.models import Document, DocAlias, DocHistory, RelatedDocument, DocumentAuthor
|
||||
from ietf.utils.history import find_history_active_at
|
||||
|
||||
def set_or_create_charter(wg):
|
||||
try:
|
||||
charter = Document.objects.get(docalias__name="charter-ietf-%s" % wg.acronym)
|
||||
except Document.DoesNotExist:
|
||||
charter = Document.objects.create(
|
||||
name="charter-ietf-" + wg.acronym,
|
||||
time=datetime.now(),
|
||||
type_id="charter",
|
||||
title=wg.name,
|
||||
group=wg,
|
||||
abstract=wg.name,
|
||||
rev="",
|
||||
)
|
||||
# Create an alias as well
|
||||
DocAlias.objects.create(
|
||||
name=charter.name,
|
||||
document=charter
|
||||
)
|
||||
if wg.charter != charter:
|
||||
wg.charter = charter
|
||||
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()
|
||||
else:
|
||||
login = None
|
||||
|
||||
e = GroupEvent(group=wg, type="added_comment", by=login)
|
||||
e.desc = text
|
||||
e.save()
|
||||
|
||||
def log_state_changed(request, doc, by, prev_state, note=''):
|
||||
from doc.models import DocEvent
|
||||
|
||||
e = DocEvent(doc=doc, by=by)
|
||||
e.type = "changed_document"
|
||||
e.desc = u"State changed to <b>%s</b> from %s" % (
|
||||
doc.get_state().name,
|
||||
prev_state.name if prev_state else "None")
|
||||
|
||||
if note:
|
||||
e.desc += "<br>%s" % note
|
||||
|
||||
e.save()
|
||||
return e
|
||||
|
||||
def log_group_state_changed(request, wg, by, note=''):
|
||||
from group.models import GroupEvent
|
||||
|
||||
e = GroupEvent(group=wg, by=by)
|
||||
if wg.state_id == "proposed":
|
||||
e.type = "proposed"
|
||||
elif wg.state_id == "active":
|
||||
e.type = "started"
|
||||
elif wg.state_id == "conclude":
|
||||
e.type = "concluded"
|
||||
e.desc = u"%s group" % e.type.capitalize()
|
||||
|
||||
if note:
|
||||
e.desc += "<br>%s" % note
|
||||
|
||||
e.save()
|
||||
return e
|
||||
|
||||
def log_info_changed(request, wg, by, note=''):
|
||||
from group.models import GroupEvent
|
||||
|
||||
e = GroupEvent(group=wg, by=by)
|
||||
e.type = "info_changed"
|
||||
e.desc = "WG info changed: "
|
||||
if note:
|
||||
e.desc += "<br>%s" % note
|
||||
|
||||
e.save()
|
||||
return e
|
||||
|
||||
def get_charter_for_revision(charter, r):
|
||||
if r == None:
|
||||
return None
|
||||
else:
|
||||
l = list(charter.history_set.filter(rev=r).order_by('-time'))
|
||||
if l != []:
|
||||
return l[0]
|
||||
else:
|
||||
# Get the lastest history entry
|
||||
l = list(charter.history_set.all().order_by('-time'))
|
||||
if l != []:
|
||||
class FakeHistory(object):
|
||||
def __init__(self, name, rev, time):
|
||||
self.name = name
|
||||
self.rev = rev
|
||||
self.time = time
|
||||
|
||||
return FakeHistory(l[0].name, charter.rev, charter.time)
|
||||
else:
|
||||
# no history, just return charter
|
||||
return charter
|
||||
|
||||
def get_group_for_revision(wg, r):
|
||||
if r == None:
|
||||
return None
|
||||
else:
|
||||
l = list(wg.charter.history_set.filter(rev=r).order_by('-time'))
|
||||
if l != []:
|
||||
o = list(wg.history_set.filter(time__lte=l[0].time).order_by('-time'))
|
||||
if o != []:
|
||||
return o[0]
|
||||
else:
|
||||
return wg
|
||||
else:
|
||||
return wg
|
||||
|
||||
def prev_revision(rev):
|
||||
m = re.match(r"(?P<major>[0-9][0-9])(-(?P<minor>[0-9][0-9]))?", rev)
|
||||
if m.group('minor') and m.group('minor') != "00":
|
||||
return "%s-%#02d" % (m.group('major'), int(m.group('minor')) - 1)
|
||||
else:
|
||||
return None
|
||||
|
||||
def next_revision(rev):
|
||||
if rev == "":
|
||||
return "00-00"
|
||||
m = re.match(r"(?P<major>[0-9][0-9])(-(?P<minor>[0-9][0-9]))?", rev)
|
||||
if m.group('minor'):
|
||||
return "%s-%#02d" % (m.group('major'), int(m.group('minor')) + 1)
|
||||
else:
|
||||
return "%s-00" % (m.group('major'))
|
||||
|
||||
def approved_revision(rev):
|
||||
if rev == "":
|
||||
return ""
|
||||
m = re.match(r"(?P<major>[0-9][0-9])(-(?P<minor>[0-9][0-9]))?", rev)
|
||||
return m.group('major')
|
||||
|
||||
def next_approved_revision(rev):
|
||||
if rev == "":
|
||||
return "01"
|
||||
m = re.match(r"(?P<major>[0-9][0-9])(-(?P<minor>[0-9][0-9]))?", rev)
|
||||
return "%#02d" % (int(m.group('major')) + 1)
|
||||
|
||||
def update_telechat(request, doc, by, new_telechat_date):
|
||||
from doc.models import TelechatDocEvent
|
||||
|
||||
on_agenda = bool(new_telechat_date)
|
||||
|
||||
prev = doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
|
||||
prev_telechat = prev.telechat_date if prev else None
|
||||
prev_agenda = bool(prev_telechat)
|
||||
|
||||
e = TelechatDocEvent()
|
||||
e.type = "scheduled_for_telechat"
|
||||
e.by = by
|
||||
e.doc = doc
|
||||
e.telechat_date = new_telechat_date
|
||||
|
||||
if on_agenda != prev_agenda:
|
||||
if on_agenda:
|
||||
e.desc = "Placed on agenda for telechat - %s by %s" % (
|
||||
new_telechat_date, by.name)
|
||||
else:
|
||||
e.desc = "Removed from agenda for telechat by %s" % by.name
|
||||
e.save()
|
||||
elif on_agenda and new_telechat_date != prev_telechat:
|
||||
e.desc = "Telechat date has been changed to <b>%s</b> from <b>%s</b> by %s" % (new_telechat_date, prev_telechat, by.name)
|
||||
e.save()
|
255
ietf/wgcharter/views.py
Normal file
255
ietf/wgcharter/views.py
Normal file
|
@ -0,0 +1,255 @@
|
|||
# Copyright The IETF Trust 2011, All Rights Reserved
|
||||
|
||||
import re, os
|
||||
from datetime import datetime, time
|
||||
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.shortcuts import render_to_response, get_object_or_404, redirect
|
||||
from django.template import RequestContext
|
||||
from django.template.defaultfilters import truncatewords_html
|
||||
from django.utils import simplejson as json
|
||||
from django.utils.decorators import decorator_from_middleware
|
||||
from django.middleware.gzip import GZipMiddleware
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from doc.models import GroupBallotPositionDocEvent, WriteupDocEvent
|
||||
from group.models import Group, GroupHistory
|
||||
from person.models import Person
|
||||
from wgcharter import markup_txt
|
||||
from django.conf import settings
|
||||
|
||||
from wgcharter.utils import *
|
||||
from ietf.utils.history import find_history_active_at
|
||||
from ietf.idtracker.templatetags.ietf_filters import format_textarea, fill
|
||||
|
||||
def _get_html(key, filename):
|
||||
f = None
|
||||
try:
|
||||
f = open(filename, 'rb')
|
||||
raw_content = f.read()
|
||||
except IOError:
|
||||
return "Error; cannot read ("+key+")"
|
||||
finally:
|
||||
if f:
|
||||
f.close()
|
||||
content = markup_txt.markup(raw_content)
|
||||
return content
|
||||
|
||||
@decorator_from_middleware(GZipMiddleware)
|
||||
def wg_main(request, name, rev, tab):
|
||||
if tab is None:
|
||||
tab = "charter"
|
||||
try:
|
||||
wg = Group.objects.get(acronym=name)
|
||||
except ObjectDoesNotExist:
|
||||
wglist = GroupHistory.objects.filter(acronym=name)
|
||||
if wglist:
|
||||
return redirect('wg_view', name=wglist[0].group.acronym)
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
if not wg.charter:
|
||||
set_or_create_charter(wg)
|
||||
|
||||
if rev != None:
|
||||
ch = get_charter_for_revision(wg.charter, rev)
|
||||
gh = get_group_for_revision(wg, rev)
|
||||
else:
|
||||
ch = get_charter_for_revision(wg.charter, wg.charter.rev)
|
||||
gh = get_group_for_revision(wg, wg.charter.rev)
|
||||
|
||||
|
||||
info = {}
|
||||
|
||||
info['prev_acronyms'] = list(set([x.acronym for x in wg.history_set.exclude(acronym=wg.acronym)]))
|
||||
prev_list_email = list(set([x.list_email for x in wg.history_set.exclude(list_email=wg.list_email) if x.list_email != u'']))
|
||||
if prev_list_email != [u'']:
|
||||
info['prev_list_email'] = prev_list_email
|
||||
prev_list_subscribe = list(set([x.list_subscribe for x in wg.history_set.exclude(list_subscribe=wg.list_subscribe) if x.list_subscribe != u'']))
|
||||
if prev_list_subscribe != [u'']:
|
||||
info['prev_list_subscribe'] = prev_list_subscribe
|
||||
prev_list_archive = list(set([x.list_archive for x in wg.history_set.exclude(list_archive=wg.list_archive) if x.list_archive != u'']))
|
||||
if prev_list_archive != [u'']:
|
||||
info['prev_list_archive'] = prev_list_archive
|
||||
info['chairs'] = [x.email.person.name for x in wg.role_set.filter(name__slug="chair")]
|
||||
if hasattr(gh, 'rolehistory_set'):
|
||||
info['history_chairs'] = [x.email.person.name for x in gh.rolehistory_set.filter(name__slug="chair")]
|
||||
else:
|
||||
info['history_chairs'] = [x.email.person.name for x in gh.role_set.filter(name__slug="chair")]
|
||||
info['secr'] = [x.email.person.name for x in wg.role_set.filter(name__slug="secr")]
|
||||
info['techadv'] = [x.email.person.name for x in wg.role_set.filter(name__slug="techadv")]
|
||||
|
||||
if ch:
|
||||
file_path = wg.charter.get_file_path() # Get from wg.charter
|
||||
content = _get_html(
|
||||
"charter-ietf-"+str(gh.acronym)+"-"+str(ch.rev)+".txt",
|
||||
os.path.join(file_path, "charter-ietf-"+gh.acronym+"-"+ch.rev+".txt"))
|
||||
active_ads = Person.objects.filter(email__role__name="ad", email__role__group__state="active").distinct()
|
||||
started_process = datetime.datetime.min
|
||||
e = wg.charter.latest_event(type="started_iesg_process")
|
||||
if e:
|
||||
started_process = e.time
|
||||
seen = []
|
||||
latest_positions = []
|
||||
for p in GroupBallotPositionDocEvent.objects.filter(doc=wg.charter, type="changed_ballot_position", time__gte=started_process).order_by("-time", '-id').select_related('ad'):
|
||||
if p.ad not in seen:
|
||||
latest_positions.append(p)
|
||||
seen.append(p.ad)
|
||||
no_record = []
|
||||
old_ads = []
|
||||
for p in latest_positions:
|
||||
if p.ad not in active_ads:
|
||||
old_ads.append(p.ad)
|
||||
for ad in active_ads:
|
||||
has_no_record = True
|
||||
for p in latest_positions:
|
||||
if p.ad == ad:
|
||||
has_no_record = False
|
||||
if has_no_record:
|
||||
no_record.append(ad)
|
||||
|
||||
info['old_ads'] = old_ads
|
||||
info['positions'] = latest_positions
|
||||
info['pos_yes'] = filter(lambda x: x.pos_id == "yes", latest_positions)
|
||||
info['pos_no'] = filter(lambda x: x.pos_id == "no", latest_positions)
|
||||
info['pos_block'] = filter(lambda x: x.pos_id == "block", latest_positions)
|
||||
info['pos_abstain'] = filter(lambda x: x.pos_id == "abstain", latest_positions)
|
||||
info['pos_no_record'] = no_record + [x.ad for x in latest_positions if x.pos_id == "norecord"]
|
||||
|
||||
# Get announcement texts
|
||||
review_ann = wg.charter.latest_event(WriteupDocEvent, type="changed_review_announcement")
|
||||
info['review_text'] = review_ann.text if review_ann else ""
|
||||
action_ann = wg.charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
|
||||
info['action_text'] = action_ann.text if action_ann else ""
|
||||
ballot_ann = wg.charter.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text")
|
||||
info['ballot_text'] = ballot_ann.text if ballot_ann else ""
|
||||
else:
|
||||
content = ""
|
||||
|
||||
versions = _get_versions(wg.charter) # Important: wg.charter not ch
|
||||
history = _get_history(wg, versions)
|
||||
|
||||
if history:
|
||||
info['last_update'] = history[0]['date']
|
||||
|
||||
template = "wgcharter/wg_tab_%s" % tab
|
||||
return render_to_response(template + ".html",
|
||||
{'content':content,
|
||||
'charter':ch,
|
||||
'info':info,
|
||||
'wg':wg,
|
||||
'tab':tab,
|
||||
'rev': rev if rev else ch.rev,
|
||||
'gh': gh,
|
||||
'snapshot': rev,
|
||||
'charter_text_url': settings.CHARTER_TXT_URL,
|
||||
'history': history, 'versions': versions,
|
||||
},
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
def _get_history(wg, versions=None):
|
||||
results = []
|
||||
for e in wg.groupevent_set.all().select_related('by').order_by('-time', 'id'):
|
||||
info = {}
|
||||
|
||||
charter_history = find_history_active_at(wg.charter, e.time)
|
||||
info['version'] = charter_history.rev if charter_history else wg.charter.rev
|
||||
info['text'] = e.desc
|
||||
info['by'] = e.by.name
|
||||
info['textSnippet'] = truncatewords_html(format_textarea(fill(info['text'], 80)), 25)
|
||||
info['snipped'] = info['textSnippet'][-3:] == "..."
|
||||
results.append({'comment':e, 'info':info, 'date':e.time, 'group': wg, 'is_com':True})
|
||||
|
||||
for e in wg.charter.docevent_set.all().order_by('-time'):
|
||||
info = {}
|
||||
charter_history = find_history_active_at(wg.charter, e.time)
|
||||
info['version'] = charter_history.rev if charter_history else wg.charter.rev
|
||||
info['text'] = e.desc
|
||||
info['by'] = e.by.name
|
||||
info['textSnippet'] = truncatewords_html(format_textarea(fill(info['text'], 80)), 25)
|
||||
info['snipped'] = info['textSnippet'][-3:] == "..."
|
||||
if e.type == "new_revision":
|
||||
if charter_history:
|
||||
charter = get_charter_for_revision(wg.charter, charter_history.rev)
|
||||
group = get_group_for_revision(wg, charter_history.rev)
|
||||
else:
|
||||
charter = get_charter_for_revision(wg.charter, wg.charter.rev)
|
||||
group = get_group_for_revision(wg, wg.charter.rev)
|
||||
|
||||
if versions:
|
||||
vl = [x['rev'] for x in versions]
|
||||
if vl:
|
||||
prev_charter = get_charter_for_revision(wg.charter, vl[vl.index(charter.rev) - 1])
|
||||
else:
|
||||
prev_charter = get_charter_for_revision(wg.charter, prev_revision(charter.rev))
|
||||
prev_group = get_group_for_revision(wg, prev_revision(charter.rev))
|
||||
results.append({'comment':e, 'info':info, 'date':e.time, 'group': group,
|
||||
'charter': charter, 'prev_charter': prev_charter,
|
||||
'prev_group': prev_group,
|
||||
'txt_url': settings.CHARTER_TXT_URL,
|
||||
'is_rev':True})
|
||||
else:
|
||||
results.append({'comment':e, 'info':info, 'date':e.time, 'group': wg, 'is_com':True})
|
||||
|
||||
# convert plain dates to datetimes (required for sorting)
|
||||
for x in results:
|
||||
if not isinstance(x['date'], datetime):
|
||||
if x['date']:
|
||||
x['date'] = datetime.combine(x['date'], time(0,0,0))
|
||||
else:
|
||||
x['date'] = datetime(1970,1,1)
|
||||
|
||||
results.sort(key=lambda x: x['date'])
|
||||
results.reverse()
|
||||
return results
|
||||
|
||||
def _get_versions(charter, include_replaced=True):
|
||||
ov = []
|
||||
prev = ""
|
||||
for r in charter.history_set.order_by('time'):
|
||||
if r.rev != prev:
|
||||
d = get_charter_for_revision(charter, r.rev)
|
||||
g = get_group_for_revision(charter.chartered_group, r.rev)
|
||||
ov.append({"name": "charter-ietf-%s" % g.acronym, "rev":d.rev, "date":d.time})
|
||||
prev = r.rev
|
||||
if charter.rev != "" and (not ov or ov[-1]['rev'] != charter.rev):
|
||||
d = get_charter_for_revision(charter, charter.rev)
|
||||
g = get_group_for_revision(charter.chartered_group, charter.rev)
|
||||
ov.append({"name": "charter-ietf-%s" % g.acronym, "rev": d.rev, "date":d.time})
|
||||
return ov
|
||||
|
||||
def wg_ballot(request, name):
|
||||
try:
|
||||
wg = Group.objects.get(acronym=name)
|
||||
except ObjectDoesNotExist:
|
||||
wglist = GroupHistory.objects.filter(acronym=name)
|
||||
if wglist:
|
||||
return redirect('wg_view', name=wglist[0].group.acronym)
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
doc = set_or_create_charter(wg)
|
||||
|
||||
if not doc:
|
||||
raise Http404
|
||||
|
||||
active_ads = list(Person.objects.filter(email__role__name="ad",
|
||||
email__role__group__type="area",
|
||||
email__role__group__state="active").distinct())
|
||||
started_process = doc.latest_event(type="started_iesg_process")
|
||||
latest_positions = []
|
||||
no_record = []
|
||||
for p in active_ads:
|
||||
p_pos = list(GroupBallotPositionDocEvent.objects.filter(doc=wg.charter, ad=p).order_by("-time"))
|
||||
if p_pos != []:
|
||||
latest_positions.append(p_pos[0])
|
||||
else:
|
||||
no_record.append(p)
|
||||
info = {}
|
||||
info['positions'] = latest_positions
|
||||
info['pos_yes'] = filter(lambda x: x.pos_id == "yes", latest_positions)
|
||||
info['pos_no'] = filter(lambda x: x.pos_id == "no", latest_positions)
|
||||
info['pos_block'] = filter(lambda x: x.pos_id == "block", latest_positions)
|
||||
info['pos_abstain'] = filter(lambda x: x.pos_id == "abstain", latest_positions)
|
||||
info['pos_no_record'] = no_record
|
||||
return render_to_response('wgcharter/wg_ballot.html', {'info':info, 'wg':wg, 'doc': doc}, context_instance=RequestContext(request))
|
||||
|
576
ietf/wgcharter/views_ballot.py
Normal file
576
ietf/wgcharter/views_ballot.py
Normal file
|
@ -0,0 +1,576 @@
|
|||
# ballot management (voting, commenting, writeups, ...) for Area
|
||||
# Directors and Secretariat
|
||||
|
||||
import re, os
|
||||
from datetime import datetime, date, time, timedelta
|
||||
from django.http import HttpResponse, HttpResponseRedirect, Http404
|
||||
from django.shortcuts import render_to_response, get_object_or_404, redirect
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
from django.template.loader import render_to_string
|
||||
from django.template import RequestContext
|
||||
from django import forms
|
||||
from django.utils.html import strip_tags
|
||||
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 redesign.group.models import Group, GroupHistory, GroupEvent
|
||||
from redesign.group.utils import save_group_in_history
|
||||
from redesign.name.models import GroupBallotPositionName, GroupStateName
|
||||
from redesign.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"
|
||||
|
||||
info = {}
|
||||
info['chairs'] = [{ 'name': x.email.person.name, 'email': x.email.address} for x in wg.role_set.filter(name="Chair")]
|
||||
info['secr'] = [{ 'name': x.email.person.name, 'email': x.email.address} for x in wg.role_set.filter(name="Secr")]
|
||||
info['techadv'] = [{ 'name': x.email.person.name, 'email': x.email.address} for x in wg.role_set.filter(name="Techadv")]
|
||||
info['ad'] = {'name': wg.ad.name, 'email': wg.ad.email_address().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"
|
||||
|
||||
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
|
||||
|
||||
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.name, 'email': x.email.address} for x in wg.role_set.filter(name="Chair")]
|
||||
info['secr'] = [{ 'name': x.person.name, 'email': x.email.address} for x in wg.role_set.filter(name="Secr")]
|
||||
info['techadv'] = [{ 'name': x.person.name, 'email': x.email.address} for x in wg.role_set.filter(name="Techadv")]
|
||||
info['ad'] = {'name': wg.ad.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()
|
||||
|
||||
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
|
||||
|
||||
BALLOT_CHOICES = (("yes", "Yes"),
|
||||
("no", "No"),
|
||||
("block", "Block"),
|
||||
("abstain", "Abstain"),
|
||||
("", "No Record"),
|
||||
)
|
||||
|
||||
def position_to_ballot_choice(position):
|
||||
for v, label in BALLOT_CHOICES:
|
||||
if v and getattr(position, v):
|
||||
return v
|
||||
return ""
|
||||
|
||||
def position_label(position_value):
|
||||
return dict(BALLOT_CHOICES).get(position_value, "")
|
||||
|
||||
class EditPositionForm(forms.Form):
|
||||
position = forms.ModelChoiceField(queryset=GroupBallotPositionName.objects.all(), widget=forms.RadioSelect, initial="norecord", required=True)
|
||||
block_comment = forms.CharField(required=False, label="Blocking comment", widget=forms.Textarea)
|
||||
comment = forms.CharField(required=False, widget=forms.Textarea)
|
||||
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
|
||||
|
||||
@group_required('Area_Director','Secretariat')
|
||||
def edit_position(request, name):
|
||||
"""Vote and edit comments on Charter as Area Director."""
|
||||
try:
|
||||
wg = Group.objects.get(acronym=name)
|
||||
except ObjectDoesNotExist:
|
||||
wglist = GroupHistory.objects.filter(acronym=name)
|
||||
if wglist:
|
||||
return redirect('wg_edit_position', name=wglist[0].group.acronym)
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
charter = set_or_create_charter(wg)
|
||||
started_process = charter.latest_event(type="started_iesg_process")
|
||||
if not started_process:
|
||||
raise Http404
|
||||
|
||||
ad = login = request.user.get_profile()
|
||||
|
||||
if 'HTTP_REFERER' in request.META:
|
||||
return_to_url = request.META['HTTP_REFERER']
|
||||
else:
|
||||
return_to_url = charter.get_absolute_url()
|
||||
|
||||
# if we're in the Secretariat, we can select an AD to act as stand-in for
|
||||
if not has_role(request.user, "Area Director"):
|
||||
ad_id = request.GET.get('ad')
|
||||
if not ad_id:
|
||||
raise Http404()
|
||||
from person.models import Person
|
||||
ad = get_object_or_404(Person, pk=ad_id)
|
||||
|
||||
old_pos = charter.latest_event(GroupBallotPositionDocEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time)
|
||||
|
||||
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']
|
||||
|
||||
pos = GroupBallotPositionDocEvent(doc=charter, by=login)
|
||||
pos.type = "changed_ballot_position"
|
||||
pos.ad = ad
|
||||
pos.pos = clean["position"]
|
||||
pos.comment = clean["comment"].strip()
|
||||
pos.comment_time = old_pos.comment_time if old_pos else None
|
||||
pos.block_comment = clean["block_comment"].strip() if pos.pos_id == "block" else ""
|
||||
pos.block_comment_time = old_pos.block_comment_time if old_pos else None
|
||||
|
||||
changes = []
|
||||
added_events = []
|
||||
# possibly add discuss/comment comments to history trail
|
||||
# so it's easy to see
|
||||
old_comment = old_pos.comment if old_pos else ""
|
||||
if pos.comment != old_comment:
|
||||
pos.comment_time = pos.time
|
||||
changes.append("comment")
|
||||
|
||||
if pos.comment:
|
||||
e = DocEvent(doc=charter)
|
||||
e.by = ad # otherwise we can't see who's saying it
|
||||
e.type = "added_comment"
|
||||
e.desc = "[Ballot comment]\n" + pos.comment
|
||||
added_events.append(e)
|
||||
|
||||
old_block_comment = old_pos.block_comment if old_pos else ""
|
||||
if pos.block_comment != old_block_comment:
|
||||
pos.block_comment_time = pos.time
|
||||
changes.append("block_comment")
|
||||
|
||||
if pos.block_comment:
|
||||
e = DocEvent(doc=charter, by=login)
|
||||
e.by = ad # otherwise we can't see who's saying it
|
||||
e.type = "added_comment"
|
||||
e.desc = "[Ballot blocking comment]\n" + pos.block_comment
|
||||
added_events.append(e)
|
||||
|
||||
# figure out a description
|
||||
if not old_pos and pos.pos.slug != "norecord":
|
||||
pos.desc = u"[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.name)
|
||||
elif old_pos and pos.pos != old_pos.pos:
|
||||
pos.desc = "[Ballot Position Update] Position for %s has been changed to %s from %s" % (pos.ad.name, pos.pos.name, old_pos.pos.name)
|
||||
|
||||
if not pos.desc and changes:
|
||||
pos.desc = u"Ballot %s text updated for %s" % (u" and ".join(changes), ad.name)
|
||||
|
||||
# only add new event if we actually got a change
|
||||
if pos.desc:
|
||||
if login != ad:
|
||||
pos.desc += u" by %s" % login.name
|
||||
|
||||
pos.save()
|
||||
|
||||
for e in added_events:
|
||||
e.save() # save them after the position is saved to get later id
|
||||
|
||||
charter.time = pos.time
|
||||
charter.save()
|
||||
|
||||
if request.POST.get("send_mail"):
|
||||
qstr = "?return_to_url=%s" % return_to_url
|
||||
if request.GET.get('ad'):
|
||||
qstr += "&ad=%s" % request.GET.get('ad')
|
||||
return HttpResponseRedirect(urlreverse("wg_send_ballot_comment", kwargs=dict(name=wg.acronym)) + qstr)
|
||||
else:
|
||||
return HttpResponseRedirect(return_to_url)
|
||||
else:
|
||||
initial = {}
|
||||
if old_pos:
|
||||
initial['position'] = old_pos.pos.slug
|
||||
initial['block_comment'] = old_pos.block_comment
|
||||
initial['comment'] = old_pos.comment
|
||||
|
||||
if return_to_url:
|
||||
initial['return_to_url'] = return_to_url
|
||||
|
||||
form = EditPositionForm(initial=initial)
|
||||
|
||||
return render_to_response('wgcharter/edit_position.html',
|
||||
dict(charter=charter,
|
||||
wg=wg,
|
||||
form=form,
|
||||
ad=ad,
|
||||
return_to_url=return_to_url,
|
||||
old_pos=old_pos,
|
||||
),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
@group_required('Area_Director','Secretariat')
|
||||
def send_ballot_comment(request, name):
|
||||
"""Email Charter ballot comment for area director."""
|
||||
try:
|
||||
wg = Group.objects.get(acronym=name)
|
||||
except ObjectDoesNotExist:
|
||||
wglist = GroupHistory.objects.filter(acronym=name)
|
||||
if wglist:
|
||||
return redirect('wg_send_ballot_comment', name=wglist[0].group.acronym)
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
charter = set_or_create_charter(wg)
|
||||
started_process = charter.latest_event(type="started_iesg_process")
|
||||
if not started_process:
|
||||
raise Http404()
|
||||
|
||||
ad = login = request.user.get_profile()
|
||||
|
||||
return_to_url = request.GET.get('return_to_url')
|
||||
if not return_to_url:
|
||||
return_to_url = charter.get_absolute_url()
|
||||
|
||||
if 'HTTP_REFERER' in request.META:
|
||||
back_url = request.META['HTTP_REFERER']
|
||||
else:
|
||||
back_url = charter.get_absolute_url()
|
||||
|
||||
# if we're in the Secretariat, we can select an AD to act as stand-in for
|
||||
if not has_role(request.user, "Area Director"):
|
||||
ad_id = request.GET.get('ad')
|
||||
if not ad_id:
|
||||
raise Http404()
|
||||
from person.models import Person
|
||||
ad = get_object_or_404(Person, pk=ad_id)
|
||||
|
||||
pos = charter.latest_event(GroupBallotPositionDocEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time)
|
||||
if not pos:
|
||||
raise Http404()
|
||||
|
||||
subj = []
|
||||
d = ""
|
||||
if pos.pos_id == "block" and pos.block_comment:
|
||||
d = pos.block_comment
|
||||
subj.append("BLOCKING COMMENT")
|
||||
c = ""
|
||||
if pos.comment:
|
||||
c = pos.comment
|
||||
subj.append("COMMENT")
|
||||
|
||||
ad_name_genitive = ad.name + "'" if ad.name.endswith('s') else ad.name + "'s"
|
||||
subject = "%s %s on %s" % (ad_name_genitive, pos.pos.name if pos.pos else "No Position", charter.name + "-" + charter.rev)
|
||||
if subj:
|
||||
subject += ": (with %s)" % " and ".join(subj)
|
||||
|
||||
body = render_to_string("wgcharter/ballot_comment_mail.txt",
|
||||
dict(block_comment=d, comment=c, ad=ad.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,
|
||||
body=body,
|
||||
frm=frm,
|
||||
to=to,
|
||||
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')
|
||||
def announcement_text(request, name, ann):
|
||||
"""Editing of announcement text"""
|
||||
try:
|
||||
wg = Group.objects.get(acronym=name)
|
||||
except ObjectDoesNotExist:
|
||||
wglist = GroupHistory.objects.filter(acronym=name)
|
||||
if wglist:
|
||||
return redirect('wg_announcement_text', name=wglist[0].group.acronym)
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
charter = set_or_create_charter(wg)
|
||||
|
||||
login = request.user.get_profile()
|
||||
|
||||
if ann == "action":
|
||||
existing = charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
|
||||
elif ann == "review":
|
||||
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")
|
||||
elif ann == "review":
|
||||
existing = default_review_text(wg, charter, login)
|
||||
|
||||
form = AnnouncementTextForm(initial=dict(announcement_text=existing.text))
|
||||
|
||||
if request.method == 'POST':
|
||||
form = AnnouncementTextForm(request.POST)
|
||||
if "save_text" in request.POST and form.is_valid():
|
||||
t = form.cleaned_data['announcement_text']
|
||||
if t != existing.text:
|
||||
e = WriteupDocEvent(doc=charter, by=login)
|
||||
e.by = login
|
||||
e.type = "changed_%s_announcement" % ann
|
||||
e.desc = "WG %s text was changed" % ann
|
||||
e.text = t
|
||||
e.save()
|
||||
|
||||
charter.time = e.time
|
||||
charter.save()
|
||||
return redirect('wg_view', name=wg.acronym)
|
||||
|
||||
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 "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"))
|
||||
|
||||
send_mail_text(request, parsed_msg["To"],
|
||||
parsed_msg["From"], parsed_msg["Subject"],
|
||||
parsed_msg.get_payload())
|
||||
return redirect('wg_view', name=wg.acronym)
|
||||
|
||||
return render_to_response('wgcharter/announcement_text.html',
|
||||
dict(charter=charter,
|
||||
announcement=ann,
|
||||
back_url=charter.get_absolute_url(),
|
||||
announcement_text_form=form,
|
||||
),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
class BallotWriteupForm(forms.Form):
|
||||
ballot_writeup = forms.CharField(widget=forms.Textarea, required=True)
|
||||
|
||||
def clean_ballot_writeup(self):
|
||||
return self.cleaned_data["ballot_writeup"].replace("\r", "")
|
||||
|
||||
@group_required('Area_Director','Secretariat')
|
||||
def ballot_writeupnotes(request, name):
|
||||
"""Editing of ballot write-up and notes"""
|
||||
try:
|
||||
wg = Group.objects.get(acronym=name)
|
||||
except ObjectDoesNotExist:
|
||||
wglist = GroupHistory.objects.filter(acronym=name)
|
||||
if wglist:
|
||||
return redirect('wg_ballot_writeupnotes', name=wglist[0].group.acronym)
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
charter = set_or_create_charter(wg)
|
||||
|
||||
started_process = charter.latest_event(type="started_iesg_process")
|
||||
if not started_process:
|
||||
raise Http404()
|
||||
|
||||
login = request.user.get_profile()
|
||||
|
||||
approval = charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
|
||||
|
||||
existing = charter.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text")
|
||||
if not existing:
|
||||
existing = generate_ballot_writeup(request, charter)
|
||||
|
||||
reissue = charter.latest_event(DocEvent, type="sent_ballot_announcement")
|
||||
|
||||
form = BallotWriteupForm(initial=dict(ballot_writeup=existing.text))
|
||||
|
||||
if request.method == 'POST' and "save_ballot_writeup" in request.POST or "issue_ballot" in request.POST:
|
||||
form = BallotWriteupForm(request.POST)
|
||||
if form.is_valid():
|
||||
t = form.cleaned_data["ballot_writeup"]
|
||||
if t != existing.text:
|
||||
e = WriteupDocEvent(doc=charter, by=login)
|
||||
e.by = login
|
||||
e.type = "changed_ballot_writeup_text"
|
||||
e.desc = "Ballot writeup was changed"
|
||||
e.text = t
|
||||
e.save()
|
||||
|
||||
if "issue_ballot" in request.POST and approval:
|
||||
if has_role(request.user, "Area Director") and not charter.latest_event(GroupBallotPositionDocEvent, ad=login, time__gte=started_process.time):
|
||||
# sending the ballot counts as a yes
|
||||
pos = GroupBallotPositionDocEvent(doc=charter, by=login)
|
||||
pos.type = "changed_ballot_position"
|
||||
pos.ad = login
|
||||
pos.pos_id = "yes"
|
||||
pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.name)
|
||||
pos.save()
|
||||
|
||||
msg = generate_issue_ballot_mail(request, charter)
|
||||
send_mail_preformatted(request, msg)
|
||||
|
||||
e = DocEvent(doc=charter, by=login)
|
||||
e.by = login
|
||||
e.type = "sent_ballot_announcement"
|
||||
e.desc = "Ballot has been issued by %s" % login.name
|
||||
e.save()
|
||||
|
||||
return render_to_response('wgcharter/ballot_issued.html',
|
||||
dict(charter=charter,
|
||||
back_url=charter.get_absolute_url()),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
|
||||
return render_to_response('wgcharter/ballot_writeupnotes.html',
|
||||
dict(charter=charter,
|
||||
back_url=charter.get_absolute_url(),
|
||||
ballot_issued=bool(charter.latest_event(type="sent_ballot_announcement")),
|
||||
ballot_writeup_form=form,
|
||||
reissue=reissue,
|
||||
approval=approval,
|
||||
),
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
@group_required('Secretariat')
|
||||
def approve_ballot(request, name):
|
||||
"""Approve ballot, changing state, copying charter"""
|
||||
try:
|
||||
wg = Group.objects.get(acronym=name)
|
||||
except ObjectDoesNotExist:
|
||||
wglist = GroupHistory.objects.filter(acronym=name)
|
||||
if wglist:
|
||||
return redirect('wg_approve_ballot', name=wglist[0].group.acronym)
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
charter = set_or_create_charter(wg)
|
||||
|
||||
login = request.user.get_profile()
|
||||
|
||||
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
|
||||
else:
|
||||
announcement = e.text
|
||||
|
||||
if request.method == 'POST':
|
||||
new_state = GroupStateName.objects.get(slug="active")
|
||||
new_charter_state = State.objects.get(type="charter", slug="approved")
|
||||
|
||||
save_charter_in_history(charter)
|
||||
save_group_in_history(wg)
|
||||
|
||||
prev_state = wg.state
|
||||
prev_charter_state = charter.get_state()
|
||||
wg.state = new_state
|
||||
charter.set_state(new_charter_state)
|
||||
|
||||
e = DocEvent(doc=charter, by=login)
|
||||
e.type = "iesg_approved"
|
||||
e.desc = "IESG has approved the charter"
|
||||
e.save()
|
||||
|
||||
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()
|
||||
|
||||
ch = get_charter_for_revision(wg.charter, wg.charter.rev)
|
||||
|
||||
filename = os.path.join(charter.get_file_path(), ch.name+"-"+ch.rev+".txt")
|
||||
try:
|
||||
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()
|
||||
except IOError:
|
||||
raise Http404("Charter text %s" % filename)
|
||||
|
||||
charter.rev = next_approved_revision(charter.rev)
|
||||
charter.save()
|
||||
|
||||
email_secretariat(request, wg, "state-%s" % new_charter_state.slug, change_description)
|
||||
|
||||
# send announcement
|
||||
send_mail_preformatted(request, announcement)
|
||||
|
||||
return HttpResponseRedirect(charter.get_absolute_url())
|
||||
|
||||
return render_to_response('wgcharter/approve_ballot.html',
|
||||
dict(charter=charter,
|
||||
announcement=announcement,
|
||||
wg=wg),
|
||||
context_instance=RequestContext(request))
|
||||
|
498
ietf/wgcharter/views_edit.py
Normal file
498
ietf/wgcharter/views_edit.py
Normal file
|
@ -0,0 +1,498 @@
|
|||
# Copyright The IETF Trust 2011, All Rights Reserved
|
||||
|
||||
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.forms.util import ErrorList
|
||||
|
||||
from utils import log_state_changed, log_group_state_changed, log_info_changed, update_telechat, add_wg_comment, set_or_create_charter, save_charter_in_history, approved_revision
|
||||
from mails import email_secretariat
|
||||
from ietf.ietfauth.decorators import group_required
|
||||
from ietf.iesg.models import TelechatDate
|
||||
|
||||
from redesign.doc.models import *
|
||||
from redesign.name.models import *
|
||||
from redesign.person.models import *
|
||||
from redesign.group.models import *
|
||||
from redesign.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)
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'queryset' in kwargs:
|
||||
qs = kwargs.pop('queryset')
|
||||
else:
|
||||
qs = None
|
||||
if 'hide' in kwargs:
|
||||
self.hide = kwargs.pop('hide')
|
||||
else:
|
||||
self.hide = None
|
||||
super(ChangeStateForm, self).__init__(*args, **kwargs)
|
||||
if qs:
|
||||
self.fields['charter_state'].queryset = qs
|
||||
# hide requested fields
|
||||
if self.hide:
|
||||
for f in self.hide:
|
||||
self.fields[f].widget = forms.HiddenInput
|
||||
|
||||
@group_required('Area_Director','Secretariat')
|
||||
def change_state(request, name, option=None):
|
||||
"""Change state of WG and charter, notifying parties as necessary
|
||||
and logging the change as a comment."""
|
||||
# Get WG by acronym, redirecting if there's a newer acronym
|
||||
try:
|
||||
wg = Group.objects.get(acronym=name)
|
||||
charter = set_or_create_charter(wg)
|
||||
except Group.DoesNotExist:
|
||||
wglist = GroupHistory.objects.filter(acronym=name)
|
||||
if wglist:
|
||||
return redirect('wg_change_state', name=wglist[0].group.acronym)
|
||||
else:
|
||||
raise Http404()
|
||||
|
||||
initial_review = charter.latest_event(InitialReviewDocEvent, type="initial_review")
|
||||
|
||||
login = request.user.get_profile()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ChangeStateForm(request.POST)
|
||||
if form.is_valid():
|
||||
if initial_review and form.cleaned_data['charter_state'] and form.cleaned_data['charter_state'].slug != "infrev" and initial_review.expires > datetime.datetime.now() and not form.cleaned_data['confirm_state']:
|
||||
form._errors['charter_state'] = "warning"
|
||||
else:
|
||||
if option == "initcharter" or option == "recharter":
|
||||
charter_state = State.objects.get(type="charter", slug="infrev")
|
||||
charter_rev = charter.rev
|
||||
elif option == "abandon":
|
||||
if wg.state_id == "proposed":
|
||||
charter_state = State.objects.get(type="charter", slug="notrev")
|
||||
else:
|
||||
charter_state = State.objects.get(type="charter", slug="approved")
|
||||
charter_rev = approved_revision(charter.rev)
|
||||
else:
|
||||
charter_state = form.cleaned_data['charter_state']
|
||||
charter_rev = charter.rev
|
||||
|
||||
comment = form.cleaned_data['comment']
|
||||
message = form.cleaned_data['message']
|
||||
|
||||
change = False
|
||||
if charter:
|
||||
# The WG has a charter
|
||||
if charter_state != charter.get_state():
|
||||
# Charter state changed
|
||||
change = True
|
||||
save_charter_in_history(charter)
|
||||
|
||||
prev = charter.get_state()
|
||||
charter.set_state(charter_state)
|
||||
charter.rev = charter_rev
|
||||
|
||||
if option != "abandon":
|
||||
e = log_state_changed(request, charter, login, prev, comment)
|
||||
else:
|
||||
# Special log for abandoned efforts
|
||||
e = DocEvent(doc=charter, by=login)
|
||||
e.type = "changed_document"
|
||||
e.desc = "IESG has abandoned the chartering effort"
|
||||
|
||||
if comment:
|
||||
e.desc += "<br>%s" % comment
|
||||
|
||||
e.save()
|
||||
|
||||
charter.time = datetime.datetime.now()
|
||||
charter.save()
|
||||
else:
|
||||
# WG does not yet have a charter
|
||||
if charter_state != "infrev":
|
||||
# This is an error
|
||||
raise Http404()
|
||||
|
||||
if change and charter:
|
||||
messages = {}
|
||||
messages['extrev'] = "The WG has been set to External review by %s. Please schedule discussion for the next IESG telechat." % login.name
|
||||
|
||||
if message:
|
||||
email_secretariat(request, wg, "state-%s" % charter_state.slug, message)
|
||||
if charter_state.slug == "extrev":
|
||||
email_secretariat(request, wg, "state-%s" % charter_state.slug, messages['extrev'])
|
||||
|
||||
if charter_state.slug == "infrev":
|
||||
e = DocEvent()
|
||||
e.type = "started_iesg_process"
|
||||
e.by = login
|
||||
e.doc = charter
|
||||
e.desc = "IESG process started in state <b>%s</b>" % charter_state.name
|
||||
e.save()
|
||||
|
||||
if charter_state.slug == "infrev" and form.cleaned_data["initial_time"] and form.cleaned_data["initial_time"] != 0:
|
||||
e = InitialReviewDocEvent()
|
||||
e.type = "initial_review"
|
||||
e.by = login
|
||||
e.doc = charter
|
||||
e.expires = datetime.datetime.now() + datetime.timedelta(weeks=form.cleaned_data["initial_time"])
|
||||
e.desc = "Initial review time expires %s" % e.expires.strftime("%Y-%m-%d")
|
||||
e.save()
|
||||
|
||||
return redirect('wg_view', name=wg.acronym)
|
||||
else:
|
||||
if option == "recharter":
|
||||
hide = ['charter_state']
|
||||
init = dict(initial_time=1, message="%s has initiated a recharter effort on the WG %s (%s)" % (login.name, wg.name, wg.acronym))
|
||||
elif option == "initcharter":
|
||||
hide = ['charter_state']
|
||||
init = dict(initial_time=1, message="%s has initiated chartering of the proposed WG %s (%s)" % (login.name, wg.name, wg.acronym))
|
||||
elif option == "abandon":
|
||||
hide = ['initial_time', 'charter_state']
|
||||
init = dict(message="%s has abandoned the chartering effort on the WG %s (%s)" % (login.name, wg.name, wg.acronym))
|
||||
else:
|
||||
hide = ['initial_time']
|
||||
init = dict(charter_state=wg.charter.get_state_slug(), state=wg.state_id)
|
||||
states = State.objects.filter(type="charter", slug__in=["infrev", "intrev", "extrev", "iesgrev"])
|
||||
form = ChangeStateForm(queryset=states, hide=hide, initial=init)
|
||||
|
||||
group_hists = GroupHistory.objects.filter(group=wg).exclude(state=wg.state).order_by("-time")[:1]
|
||||
prev_charter_state = None
|
||||
if charter:
|
||||
charter_hists = DocHistory.objects.filter(doc=charter).exclude(states__type="charter", states__slug=charter.get_state_slug()).order_by("-time")[:1]
|
||||
if charter_hists:
|
||||
prev_charter_state = charter_hists[0].get_state()
|
||||
|
||||
title = {
|
||||
"initcharter": "Initiate chartering of WG %s" % wg.acronym,
|
||||
"recharter": "Recharter WG %s" % wg.acronym,
|
||||
"abandon": "Abandon effort on WG %s" % wg.acronym,
|
||||
}.get(option)
|
||||
if not title:
|
||||
title = "Change state of WG %s" % wg.acronym
|
||||
|
||||
return render_to_response('wgcharter/change_state.html',
|
||||
dict(form=form,
|
||||
wg=wg,
|
||||
login=login,
|
||||
option=option,
|
||||
prev_charter_state=prev_charter_state,
|
||||
title=title),
|
||||
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)
|
||||
telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.cur_acronym = kwargs.pop('cur_acronym')
|
||||
if 'hide' in kwargs:
|
||||
self.hide = kwargs.pop('hide')
|
||||
else:
|
||||
self.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).name)]
|
||||
# telechat choices
|
||||
dates = [d.date for d in TelechatDate.objects.active().order_by('date')]
|
||||
if 'telechat_date' in kwargs['initial']:
|
||||
init = kwargs['initial']['telechat_date']
|
||||
if init and init not in dates:
|
||||
dates.insert(0, init)
|
||||
|
||||
self.fields['telechat_date'].choices = [("", "(not on agenda)")] + [(d, d.strftime("%Y-%m-%d")) for d in dates]
|
||||
|
||||
def clean_acronym(self):
|
||||
acronym = self.cleaned_data['acronym']
|
||||
if self.cur_acronym and acronym != self.cur_acronym:
|
||||
if Group.objects.filter(acronym=acronym):
|
||||
raise forms.ValidationError("Acronym used in a previous WG. Please pick another.")
|
||||
if GroupHistory.objects.filter(acronym=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(set, fs="\n"):
|
||||
ostr = ""
|
||||
for i,x in enumerate(set):
|
||||
if i != 0:
|
||||
ostr += fs
|
||||
if x.name:
|
||||
ostr += x.url + " (" + x.name + ")"
|
||||
else:
|
||||
ostr += x.url
|
||||
|
||||
return ostr
|
||||
|
||||
@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."""
|
||||
import sys
|
||||
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 not new_wg:
|
||||
e = charter.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
|
||||
initial_telechat_date = e.telechat_date if e else None
|
||||
else:
|
||||
initial_telechat_date = None
|
||||
|
||||
if request.method == 'POST':
|
||||
form = EditInfoForm(request.POST, initial=dict(telechat_date=initial_telechat_date), 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 = GroupEvent(group=wg, type="proposed")
|
||||
e.time = datetime.datetime.now()
|
||||
e.by = login
|
||||
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 has been changed to <b>%(new)s</b> from <b>%(old)s</b>"
|
||||
if new_wg:
|
||||
entry = "%(attr)s has been 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)
|
||||
|
||||
update_telechat(request, wg.charter, login, r['telechat_date'])
|
||||
|
||||
wg.save()
|
||||
if new_wg:
|
||||
return redirect('wg_startstop_process', name=wg.acronym, option="initcharter")
|
||||
else:
|
||||
return redirect('wg_view', name=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()),
|
||||
telechat_date=initial_telechat_date,
|
||||
)
|
||||
hide = None
|
||||
else:
|
||||
init = dict(ad=login.id,
|
||||
)
|
||||
hide = ['chairs', 'techadv', 'list_email', 'list_subscribe', 'list_archive', 'urls', 'telechat_date']
|
||||
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))
|
||||
|
||||
class AddCommentForm(forms.Form):
|
||||
comment = forms.CharField(required=True, widget=forms.Textarea)
|
||||
|
||||
@group_required('Area_Director','Secretariat')
|
||||
def add_comment(request, name):
|
||||
"""Add comment to WG Record."""
|
||||
wg = get_object_or_404(Group, acronym=name)
|
||||
|
||||
login = request.user.get_profile()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = AddCommentForm(request.POST)
|
||||
if form.is_valid():
|
||||
c = form.cleaned_data['comment']
|
||||
|
||||
add_wg_comment(request, wg, c)
|
||||
|
||||
#email_owner(request, doc, doc.ad, login,
|
||||
# "A new comment added by %s" % login.name)
|
||||
return redirect('wg_view', name=wg.acronym)
|
||||
else:
|
||||
form = AddCommentForm()
|
||||
|
||||
return render_to_response('wgcharter/add_comment.html',
|
||||
dict(wg=wg,
|
||||
form=form),
|
||||
context_instance=RequestContext(request))
|
||||
|
296
ietf/wgcharter/views_search.py
Normal file
296
ietf/wgcharter/views_search.py
Normal file
|
@ -0,0 +1,296 @@
|
|||
# Copyright The IETF Trust 2011, All Rights Reserved
|
||||
|
||||
import re, os
|
||||
import datetime
|
||||
from django import forms
|
||||
from django.shortcuts import render_to_response, redirect
|
||||
from django.db.models import Q
|
||||
from django.template import RequestContext
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponsePermanentRedirect
|
||||
from django.conf import settings
|
||||
from django.utils import simplejson
|
||||
|
||||
from redesign.doc.models import Document, State
|
||||
from redesign.name.models import GroupStateName
|
||||
from redesign.group.models import Group
|
||||
from redesign.person.models import Person, Email
|
||||
from redesign.doc.utils import augment_with_telechat_date
|
||||
|
||||
class SearchForm(forms.Form):
|
||||
nameacronym = forms.CharField(required=False)
|
||||
|
||||
inprocess = forms.BooleanField(required=False,initial=True)
|
||||
active = forms.BooleanField(required=False,initial=False)
|
||||
concluded = forms.BooleanField(required=False, initial=False)
|
||||
|
||||
by = forms.ChoiceField(choices=[(x,x) for x in ('acronym','state','ad','area','anyfield', 'eacronym')], required=False, initial='wg', label='Foobar')
|
||||
state = forms.ModelChoiceField(GroupStateName.objects.all(), label="WG state", empty_label="any state", required=False)
|
||||
charter_state = forms.ModelChoiceField(State.objects.filter(type="charter"), label="Charter state", empty_label="any state", required=False)
|
||||
ad = forms.ChoiceField(choices=(), required=False)
|
||||
area = forms.ModelChoiceField(Group.objects.filter(type="area", state="active").order_by('name'), empty_label="any area", required=False)
|
||||
anyfield= forms.CharField(required=False)
|
||||
eacronym = forms.CharField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SearchForm, self).__init__(*args, **kwargs)
|
||||
responsible = Document.objects.values_list('ad', flat=True).distinct()
|
||||
active_ads = list(Person.objects.filter(email__role__name="ad",
|
||||
email__role__group__type="area",
|
||||
email__role__group__state="active").distinct())
|
||||
inactive_ads = list(Person.objects.filter(pk__in=responsible)
|
||||
.exclude(pk__in=[x.pk for x in active_ads]))
|
||||
extract_last_name = lambda x: x.name_parts()[3]
|
||||
active_ads.sort(key=extract_last_name)
|
||||
inactive_ads.sort(key=extract_last_name)
|
||||
|
||||
self.fields['ad'].choices = c = [('', 'any AD')] + [(ad.pk, ad.name) for ad in active_ads] + [('', '------------------')] + [(ad.pk, ad.name) for ad in inactive_ads]
|
||||
|
||||
def clean_nameacronym(self):
|
||||
value = self.cleaned_data.get('nameacronym','')
|
||||
return value
|
||||
|
||||
def clean(self):
|
||||
q = self.cleaned_data
|
||||
# Reset query['by'] if needed
|
||||
for k in ('ad', 'area', 'anyfield', 'eacronym'):
|
||||
if (q['by'] == k) and not q[k]:
|
||||
q['by'] = None
|
||||
if (q['by'] == 'state') and not (q.get('state') or q.get('charter_state')):
|
||||
q['by'] = None
|
||||
# Reset other fields
|
||||
for k in ('ad', 'area', 'anyfield', 'eacronym'):
|
||||
if q['by'] != k:
|
||||
self.data[k] = ""
|
||||
q[k] = ""
|
||||
if q['by'] != 'state':
|
||||
self.data['state'] = ""
|
||||
self.data['charter_state'] = ""
|
||||
q['state'] = ""
|
||||
q['charter_state'] = ""
|
||||
return q
|
||||
|
||||
def search_query(query_original, sort_by=None):
|
||||
query = dict(query_original.items())
|
||||
|
||||
# Non-ASCII strings don't match anything; this check
|
||||
# is currently needed to avoid complaints from MySQL.
|
||||
for k in ['nameacronym','anyfield','eacronym']:
|
||||
try:
|
||||
tmp = str(query.get(k, ''))
|
||||
except:
|
||||
query[k] = '*NOSUCH*'
|
||||
|
||||
# Search
|
||||
MAX = 500
|
||||
maxReached = False
|
||||
|
||||
q_obj = Q(type__slug="wg", state__slug__in=("proposed", "active", "conclude"))
|
||||
|
||||
s_obj = Q()
|
||||
if query["inprocess"]:
|
||||
s_obj |= Q(charter__states__type="charter", charter__states__slug__in=("infrev", "intrev", "extrev", "iesgrev")) & Q(state__slug__in=("proposed", "active"))
|
||||
if query["active"]:
|
||||
s_obj |= Q(charter__states__type="charter", charter__states__slug="approved") & Q(state__slug="active")
|
||||
if query["concluded"]:
|
||||
s_obj |= Q(state__slug="conclude") | Q(charter__states__type="charter", charter__states__slug="notrev")
|
||||
|
||||
results = Group.objects.filter(q_obj & s_obj).select_related("charter")
|
||||
|
||||
prefix = ""
|
||||
q_objs = []
|
||||
# name
|
||||
if query["nameacronym"]:
|
||||
results = results.filter(Q(name__icontains=query["nameacronym"]) | Q(acronym__icontains=query["nameacronym"]))
|
||||
# radio choices
|
||||
by = query["by"]
|
||||
if by == "state":
|
||||
q_objs = []
|
||||
if query['state']:
|
||||
q_objs.append(Q(state=query['state']))
|
||||
if query['charter_state']:
|
||||
q_objs.append(Q(charter__states__type="charter", charter__states__slug=query['charter_state']))
|
||||
results = results.filter(*q_objs)
|
||||
elif by == "ad":
|
||||
results = results.filter(ad=query["ad"])
|
||||
elif by == "area":
|
||||
results = results.filter(parent=query["area"])
|
||||
elif by == "anyfield":
|
||||
q_obj = Q()
|
||||
q_obj |= Q(state__name__icontains=query['anyfield'])
|
||||
q_obj |= Q(charter__states__type="charter", charter__states__name__icontains=query['anyfield'])
|
||||
q_obj |= Q(ad__name__icontains=query['anyfield'])
|
||||
q_obj |= Q(parent__name__icontains=query['anyfield'])
|
||||
q_obj |= Q(history_set__acronym__icontains=query['anyfield'])
|
||||
results = list(results.filter(q_obj))
|
||||
# Search charter texts
|
||||
m = re.compile(query['anyfield'], re.IGNORECASE)
|
||||
if query['nameacronym']:
|
||||
file_set = Group.objects.filter(type="wg", name__icontains=query["nameacronym"])
|
||||
else:
|
||||
file_set = Group.objects.filter(type="wg")
|
||||
for g in file_set:
|
||||
charter = g.charter
|
||||
if charter:
|
||||
try:
|
||||
file = open(os.path.join(charter.get_file_path(), charter.name+"-"+charter.rev+".txt"))
|
||||
for line in file:
|
||||
if m.search(line):
|
||||
results.append(g)
|
||||
break
|
||||
except IOError:
|
||||
pass # Pass silently for files not found
|
||||
elif by == "eacronym":
|
||||
results = results.filter(history_set__acronym__icontains=query["eacronym"]).distinct()
|
||||
|
||||
results = list(results[:MAX])
|
||||
if len(results) == MAX:
|
||||
maxReached = True
|
||||
|
||||
# sort
|
||||
def sort_key(g):
|
||||
res = []
|
||||
|
||||
if sort_by == "acronym":
|
||||
res.append(g.acronym)
|
||||
elif sort_by == "name":
|
||||
res.append(g.name)
|
||||
elif sort_by == "date":
|
||||
res.append(str(g.time or datetime.date(1990, 1, 1)))
|
||||
elif sort_by == "status":
|
||||
if g.charter:
|
||||
s = g.charter.get_state()
|
||||
if s:
|
||||
res.append(s.order)
|
||||
# Sort secondary by group state
|
||||
res.append(g.state.name)
|
||||
|
||||
return res
|
||||
|
||||
results.sort(key=sort_key)
|
||||
|
||||
meta = {}
|
||||
if maxReached:
|
||||
meta['max'] = MAX
|
||||
if query['by']:
|
||||
meta['advanced'] = True
|
||||
|
||||
augment_with_telechat_date([r.charter for r in results])
|
||||
|
||||
return (results,meta)
|
||||
|
||||
def generate_query_string(request, ignore_list):
|
||||
"""Recreates the parameter string from the given request, and
|
||||
returns it as a string.
|
||||
Any parameter names present in ignore_list shall not be put
|
||||
in the result string.
|
||||
"""
|
||||
params = []
|
||||
for i in request.GET:
|
||||
if not i in ignore_list:
|
||||
params.append(i + "=" + request.GET[i])
|
||||
return "?" + "&".join(params)
|
||||
|
||||
def search_results(request):
|
||||
if len(request.REQUEST.items()) == 0:
|
||||
return search_main(request)
|
||||
form = SearchForm(dict(request.REQUEST.items()))
|
||||
if not form.is_valid():
|
||||
return HttpResponseBadRequest("form not valid?", mimetype="text/plain")
|
||||
|
||||
sort_by = None
|
||||
if "sortBy" in request.GET:
|
||||
sort_by = request.GET["sortBy"]
|
||||
|
||||
(results,meta) = search_query(form.cleaned_data, sort_by)
|
||||
|
||||
meta['searching'] = True
|
||||
meta['by'] = form.cleaned_data['by']
|
||||
meta['rqps'] = generate_query_string(request, ['sortBy'])
|
||||
# With a later Django we can do this from the template (incude with tag)
|
||||
# Pass the headers and their sort key names
|
||||
meta['hdrs'] = [{'htitle': 'Acronym', 'htype':'acronym'},
|
||||
{'htitle': 'Name', 'htype':'name'},
|
||||
{'htitle': 'Date', 'htype':'date'},
|
||||
{'htitle': 'Status', 'htype':'status', 'colspan':'2'},
|
||||
]
|
||||
if 'ajax' in request.REQUEST and request.REQUEST['ajax']:
|
||||
return render_to_response('wgcharter/search_results.html', {'recs':results, 'meta':meta}, context_instance=RequestContext(request))
|
||||
elif len(results) == 1:
|
||||
wg = results[0]
|
||||
return redirect('wg_view', name=wg.acronym)
|
||||
else:
|
||||
return render_to_response('wgcharter/search_main.html', {'form':form, 'recs':results,'meta':meta}, context_instance=RequestContext(request))
|
||||
|
||||
|
||||
def search_main(request):
|
||||
form = SearchForm()
|
||||
return render_to_response('wgcharter/search_main.html', {'form':form}, context_instance=RequestContext(request))
|
||||
|
||||
def by_area(request, name):
|
||||
area_id = None
|
||||
area_name = None
|
||||
for a in Group.objects.filter(type="area"):
|
||||
if name == a.acronym:
|
||||
area_id = a.id
|
||||
area_name = a.name
|
||||
break
|
||||
if not area_id:
|
||||
raise Http404
|
||||
form = SearchForm({'inprocess': True, 'active': True, 'concluded': True, 'by':'area','area':area_id})
|
||||
if not form.is_valid():
|
||||
raise ValueError("form did not validate")
|
||||
(results,meta) = search_query(form.cleaned_data)
|
||||
meta['searching'] = True
|
||||
meta['by'] = form.cleaned_data['by']
|
||||
meta['rqps'] = generate_query_string(request, ['sortBy'])
|
||||
# With a later Django we can do this from the template (incude with tag)
|
||||
# Pass the headers and their sort key names
|
||||
meta['hdrs'] = [{'htitle': 'Acronym', 'htype':'acronym'},
|
||||
{'htitle': 'Name', 'htype':'name'},
|
||||
{'htitle': 'Date', 'htype':'date'},
|
||||
{'htitle': 'Status', 'htype':'status', 'colspan':'2'},
|
||||
]
|
||||
fresults = []
|
||||
for r in results:
|
||||
if r.state_id == "proposed" and r.charter.get_state_slug() == "notrev":
|
||||
if r.charter.latest_event(desc__icontains="abandoned"):
|
||||
if r.charter.latest_event(desc__icontains="abandoned").time > datetime.datetime.now() - datetime.timedelta(days=31):
|
||||
# Abandoned recently
|
||||
fresults.append(r)
|
||||
else:
|
||||
if not r.state_id == "conclude":
|
||||
fresults.append(r)
|
||||
|
||||
fresults.sort(key=lambda g: str(g.time or datetime.date(1990, 1, 1)), reverse=True)
|
||||
return render_to_response('wgcharter/by_area.html', {'form':form, 'recs':fresults,'meta':meta, 'area_name':area_name}, context_instance=RequestContext(request))
|
||||
|
||||
def in_process(request):
|
||||
results = Group.objects.filter(type="wg",
|
||||
charter__states__slug__in=['infrev', 'intrev', 'extrev', 'iesgrev'],
|
||||
charter__states__type="charter").order_by('-time').select_related("charter")
|
||||
augment_with_telechat_date([r.charter for r in results])
|
||||
|
||||
meta = {}
|
||||
meta['searching'] = True
|
||||
meta['by'] = 'state'
|
||||
meta['rqps'] = generate_query_string(request, ['sortBy'])
|
||||
# With a later Django we can do this from the template (incude with tag)
|
||||
# Pass the headers and their sort key names
|
||||
meta['hdrs'] = [{'htitle': 'Acronym', 'htype':'acronym'},
|
||||
{'htitle': 'Name', 'htype':'name'},
|
||||
{'htitle': 'Date', 'htype':'date'},
|
||||
{'htitle': 'Status', 'htype':'status', 'colspan':'2'},
|
||||
]
|
||||
return render_to_response('wgcharter/in_process.html', {'recs':results,'meta':meta}, context_instance=RequestContext(request))
|
||||
|
||||
def json_emails(l):
|
||||
result = []
|
||||
for p in l:
|
||||
result.append({"id": p.address + "", "name": p.person.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')
|
98
ietf/wgcharter/views_submit.py
Normal file
98
ietf/wgcharter/views_submit.py
Normal file
|
@ -0,0 +1,98 @@
|
|||
# Copyright The IETF Trust 2011, All Rights Reserved
|
||||
import os, datetime
|
||||
|
||||
from django.http import HttpResponseRedirect, Http404
|
||||
from django import forms
|
||||
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.conf import settings
|
||||
|
||||
from ietf.ietfauth.decorators import group_required
|
||||
from redesign.group.models import Group
|
||||
from redesign.doc.models import Document, DocHistory, DocEvent
|
||||
from redesign.group.utils import save_group_in_history
|
||||
|
||||
from utils import next_revision, set_or_create_charter, save_charter_in_history
|
||||
|
||||
class UploadForm(forms.Form):
|
||||
content = forms.CharField(widget=forms.Textarea, label="Charter text", help_text="Edit the charter text", required=False)
|
||||
txt = forms.FileField(label=".txt format", help_text="Or upload a .txt file", required=False)
|
||||
|
||||
def clean_content(self):
|
||||
return self.cleaned_data["content"].replace("\r", "")
|
||||
|
||||
def save(self, wg, rev):
|
||||
fd = self.cleaned_data['txt']
|
||||
filename = os.path.join(settings.CHARTER_PATH, 'charter-ietf-%s-%s.txt' % (wg.acronym, rev))
|
||||
if fd:
|
||||
# A file was specified. Save it.
|
||||
destination = open(filename, 'wb+')
|
||||
for chunk in fd.chunks():
|
||||
destination.write(chunk)
|
||||
destination.close()
|
||||
else:
|
||||
# No file, save content
|
||||
destination = open(filename, 'wb+')
|
||||
content = self.cleaned_data['content']
|
||||
destination.write(content)
|
||||
destination.close()
|
||||
|
||||
@group_required('Area_Director','Secretariat')
|
||||
def submit(request, name):
|
||||
# Get WG by acronym, redirecting if there's a newer acronym
|
||||
try:
|
||||
wg = Group.objects.get(acronym=name)
|
||||
except Group.DoesNotExist:
|
||||
wglist = GroupHistory.objects.filter(acronym=name)
|
||||
if wglist:
|
||||
return redirect('charter_submit', name=wglist[0].group.acronym)
|
||||
else:
|
||||
raise Http404()
|
||||
# Get charter
|
||||
charter = set_or_create_charter(wg)
|
||||
|
||||
login = request.user.get_profile()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = UploadForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
save_charter_in_history(charter)
|
||||
# Also save group history so we can search for it
|
||||
save_group_in_history(wg)
|
||||
|
||||
# Search history for possible collisions with abandoned efforts
|
||||
rev_list = list(charter.history_set.order_by('-time').values_list('rev', flat=True))
|
||||
next_rev = next_revision(charter.rev)
|
||||
while next_rev in rev_list:
|
||||
next_rev = next_revision(next_rev)
|
||||
|
||||
charter.rev = next_rev
|
||||
|
||||
e = DocEvent()
|
||||
e.type = "new_revision"
|
||||
e.by = login
|
||||
e.doc = charter
|
||||
e.desc = "New version available: <b>charter-ietf-%s-%s.txt</b>" % (wg.acronym, charter.rev)
|
||||
e.save()
|
||||
|
||||
# Save file on disk
|
||||
form.save(wg, charter.rev)
|
||||
|
||||
charter.time = datetime.datetime.now()
|
||||
charter.save()
|
||||
|
||||
return HttpResponseRedirect(reverse('wg_view', kwargs={'name': wg.acronym}))
|
||||
else:
|
||||
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())
|
||||
except IOError:
|
||||
init = {}
|
||||
form = UploadForm(initial = init)
|
||||
return render_to_response('wgcharter/submit.html',
|
||||
{'form': form,
|
||||
'next_rev': next_revision(wg.charter.rev),
|
||||
'wg': wg},
|
||||
context_instance=RequestContext(request))
|
|
@ -13,8 +13,8 @@ urlpatterns = patterns('',
|
|||
(r'^1wg-summary-by-acronym.txt', views.wg_summary_acronym),
|
||||
(r'^1wg-charters.txt', views.wg_charters),
|
||||
(r'^1wg-charters-by-acronym.txt', views.wg_charters_by_acronym),
|
||||
(r'^(?P<acronym>[a-z0-9-]+)/documents/txt/$', views.wg_documents_txt),
|
||||
(r'^(?P<acronym>[a-z0-9-]+)/$', views.wg_documents_html),
|
||||
(r'^(?P<acronym>[a-z0-9-]+)/charter/$', views.wg_charter),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/documents/txt/$', views.wg_documents_txt),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/$', views.wg_documents_html),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-]+)/charter/$', views.wg_charter),
|
||||
(r'^(?P<acronym>[^/]+)/management/', include('ietf.wgchairs.urls')),
|
||||
)
|
||||
|
|
|
@ -111,7 +111,8 @@ if settings.USE_DB_REDESIGN_PROXY_CLASSES:
|
|||
|
||||
def wg_documents(request, acronym):
|
||||
wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1)
|
||||
concluded = (wg.status_id != 1)
|
||||
concluded = (wg.status_id != 1 and wg.status_id != 4)
|
||||
proposed = (wg.status_id == 4)
|
||||
form = SearchForm({'by':'group', 'group':str(wg.group_acronym.acronym),
|
||||
'rfcs':'on', 'activeDrafts':'on'})
|
||||
if not form.is_valid():
|
||||
|
@ -130,26 +131,28 @@ def wg_documents(request, acronym):
|
|||
if ( len(parts) >= 3):
|
||||
if parts[1] != "ietf" and parts[2].startswith(wg.group_acronym.acronym+"-"):
|
||||
docs_related_pruned.append(d)
|
||||
return wg, concluded, docs, meta, docs_related_pruned, meta_related
|
||||
return wg, concluded, proposed, docs, meta, docs_related_pruned, meta_related
|
||||
|
||||
def wg_documents_txt(request, acronym):
|
||||
wg, concluded, docs, meta, docs_related, meta_related = wg_documents(request, acronym)
|
||||
return HttpResponse(loader.render_to_string('wginfo/wg_documents.txt', {'wg': wg, 'concluded':concluded, 'selected':'documents', 'docs':docs, 'meta':meta, 'docs_related':docs_related, 'meta_related':meta_related}),mimetype='text/plain; charset=UTF-8')
|
||||
wg, concluded, proposed, docs, meta, docs_related, meta_related = wg_documents(request, acronym)
|
||||
return HttpResponse(loader.render_to_string('wginfo/wg_documents.txt', {'wg': wg, 'concluded':concluded, 'proposed':proposed, 'selected':'documents', 'docs':docs, 'meta':meta, 'docs_related':docs_related, 'meta_related':meta_related}),mimetype='text/plain; charset=UTF-8')
|
||||
|
||||
def wg_documents_html(request, acronym):
|
||||
wg, concluded, docs, meta, docs_related, meta_related = wg_documents(request, acronym)
|
||||
return render_to_response('wginfo/wg_documents.html', {'wg': wg, 'concluded':concluded, 'selected':'documents', 'docs':docs, 'meta':meta, 'docs_related':docs_related, 'meta_related':meta_related}, RequestContext(request))
|
||||
wg, concluded, proposed, docs, meta, docs_related, meta_related = wg_documents(request, acronym)
|
||||
return render_to_response('wginfo/wg_documents.html', {'wg': wg, 'concluded':concluded, 'proposed':proposed, 'selected':'documents', 'docs':docs, 'meta':meta, 'docs_related':docs_related, 'meta_related':meta_related}, RequestContext(request))
|
||||
|
||||
def wg_charter(request, acronym):
|
||||
wg = get_object_or_404(IETFWG, group_acronym__acronym=acronym, group_type=1)
|
||||
concluded = (wg.status_id != 1)
|
||||
concluded = (wg.status_id != 1 and wg.status_id != 4)
|
||||
proposed = (wg.status_id == 4)
|
||||
|
||||
if settings.USE_DB_REDESIGN_PROXY_CLASSES:
|
||||
fill_in_charter_info(wg)
|
||||
return render_to_response('wginfo/wg_charterREDESIGN.html',
|
||||
dict(wg=wg,
|
||||
concluded=concluded,
|
||||
proposed=proposed,
|
||||
selected='charter'),
|
||||
RequestContext(request))
|
||||
|
||||
return render_to_response('wginfo/wg_charter.html', {'wg': wg, 'concluded':concluded, 'selected':'charter'}, RequestContext(request))
|
||||
return render_to_response('wginfo/wg_charter.html', {'wg': wg, 'concluded':concluded, 'proposed': proposed, 'selected':'charter'}, RequestContext(request))
|
||||
|
|
|
@ -65,6 +65,8 @@ class DocumentInfo(models.Model):
|
|||
elif self.type_id in ("agenda", "minutes", "slides"):
|
||||
meeting = self.name.split("-")[1]
|
||||
return os.path.join(settings.AGENDA_PATH, meeting, self.type_id) + "/"
|
||||
elif self.type_id == "charter":
|
||||
return settings.CHARTER_PATH
|
||||
else:
|
||||
raise NotImplemented
|
||||
|
||||
|
@ -142,11 +144,14 @@ class Document(DocumentInfo):
|
|||
|
||||
def get_absolute_url(self):
|
||||
name = self.name
|
||||
if self.get_state_slug() == "rfc":
|
||||
aliases = self.docalias_set.filter(name__startswith="rfc")
|
||||
if aliases:
|
||||
name = aliases[0].name
|
||||
return urlreverse('doc_view', kwargs={ 'name': name })
|
||||
if self.type_id == "charter":
|
||||
return urlreverse('wg_view', kwargs={ 'name': self.group.acronym })
|
||||
elif self.type_id == "draft":
|
||||
if self.get_state_slug() == "rfc":
|
||||
aliases = self.docalias_set.filter(name__startswith="rfc")
|
||||
if aliases:
|
||||
name = aliases[0].name
|
||||
return urlreverse('doc_view', kwargs={ 'name': name })
|
||||
|
||||
def file_tag(self):
|
||||
return u"<%s>" % self.filename_with_rev()
|
||||
|
@ -192,8 +197,7 @@ class DocHistoryAuthor(models.Model):
|
|||
|
||||
class DocHistory(DocumentInfo):
|
||||
doc = models.ForeignKey(Document, related_name="history_set")
|
||||
# Django 1.2 won't let us define these in the base class, so we have
|
||||
# to repeat them
|
||||
name = models.CharField(max_length=255) # WG charter names can change if the group acronym changes
|
||||
related = models.ManyToManyField('DocAlias', through=RelatedDocHistory, blank=True)
|
||||
authors = models.ManyToManyField(Email, through=DocHistoryAuthor, blank=True)
|
||||
def __unicode__(self):
|
||||
|
@ -211,6 +215,7 @@ def save_document_in_history(doc):
|
|||
# copy fields
|
||||
fields = get_model_fields_as_dict(doc)
|
||||
fields["doc"] = doc
|
||||
fields["name"] = doc.name
|
||||
|
||||
dochist = DocHistory(**fields)
|
||||
dochist.save()
|
||||
|
@ -278,7 +283,12 @@ EVENT_TYPES = [
|
|||
# WG events
|
||||
("changed_group", "Changed group"),
|
||||
("changed_protocol_writeup", "Changed protocol writeup"),
|
||||
|
||||
|
||||
# charter events
|
||||
("initial_review", "Set initial review time"),
|
||||
("changed_review_announcement", "Changed WG Review text"),
|
||||
("changed_action_announcement", "Changed WG Action text"),
|
||||
|
||||
# IESG events
|
||||
("started_iesg_process", "Started IESG process on document"),
|
||||
|
||||
|
@ -336,3 +346,14 @@ class TelechatDocEvent(DocEvent):
|
|||
telechat_date = models.DateField(blank=True, null=True)
|
||||
returning_item = models.BooleanField(default=False)
|
||||
|
||||
# Charter ballot events
|
||||
class GroupBallotPositionDocEvent(DocEvent):
|
||||
ad = models.ForeignKey(Person)
|
||||
pos = models.ForeignKey(GroupBallotPositionName, verbose_name="position", default="norecord")
|
||||
block_comment = models.TextField(help_text="Blocking comment if position is comment", blank=True)
|
||||
block_comment_time = models.DateTimeField(help_text="Blocking comment was written", blank=True, null=True)
|
||||
comment = models.TextField(help_text="Non-blocking comment", blank=True)
|
||||
comment_time = models.DateTimeField(help_text="Time non-blocking comment was written", blank=True, null=True)
|
||||
|
||||
class InitialReviewDocEvent(DocEvent):
|
||||
expires = models.DateTimeField(blank=True, null=True)
|
||||
|
|
|
@ -52,3 +52,25 @@ def active_ballot_positions(doc):
|
|||
def get_rfc_number(doc):
|
||||
qs = doc.docalias_set.filter(name__startswith='rfc')
|
||||
return qs[0].name[3:] if qs else None
|
||||
|
||||
def augment_with_telechat_date(docs):
|
||||
"""Add a telechat_date attribute to each document with the
|
||||
scheduled telechat or None if it's not scheduled."""
|
||||
docs_dict = {}
|
||||
for d in docs:
|
||||
docs_dict[d.pk] = d
|
||||
d.telechat_date = None
|
||||
|
||||
seen = set()
|
||||
|
||||
for e in TelechatDocEvent.objects.filter(type="scheduled_for_telechat", doc__in=docs).order_by('-time'):
|
||||
if e.doc_id in seen:
|
||||
continue
|
||||
|
||||
docs_dict[e.doc_id].telechat_date = e.telechat_date
|
||||
seen.add(e.doc_id)
|
||||
|
||||
return docs
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@ class GroupAdmin(admin.ModelAdmin):
|
|||
|
||||
admin.site.register(Group, GroupAdmin)
|
||||
admin.site.register(GroupHistory)
|
||||
admin.site.register(GroupURL)
|
||||
|
||||
class RoleAdmin(admin.ModelAdmin):
|
||||
list_display = ["name", "person", "email", "group"]
|
||||
|
@ -104,6 +105,5 @@ class RoleAdmin(admin.ModelAdmin):
|
|||
list_filter = ["name"]
|
||||
ordering = ["id"]
|
||||
raw_id_fields = ["email", "person", "group"]
|
||||
|
||||
admin.site.register(Role, RoleAdmin)
|
||||
admin.site.register(RoleHistory, RoleAdmin)
|
||||
|
|
|
@ -12,7 +12,6 @@ class GroupInfo(models.Model):
|
|||
state = models.ForeignKey(GroupStateName, null=True)
|
||||
type = models.ForeignKey(GroupTypeName, null=True)
|
||||
parent = models.ForeignKey('Group', blank=True, null=True)
|
||||
iesg_state = models.ForeignKey(IesgGroupStateName, verbose_name="IESG state", blank=True, null=True)
|
||||
ad = models.ForeignKey(Person, verbose_name="AD", blank=True, null=True)
|
||||
list_email = models.CharField(max_length=64, blank=True)
|
||||
list_subscribe = models.CharField(max_length=255, blank=True)
|
||||
|
@ -44,14 +43,10 @@ class Group(GroupInfo):
|
|||
e = GroupEvent.objects.filter(group=self).filter(**filter_args).order_by('-time', '-id')[:1]
|
||||
return e[0] if e else None
|
||||
|
||||
# This will record the new state and the date it occurred for any changes
|
||||
# to a group. The group acronym must be unique and is the invariant used
|
||||
# to select group history from this table.
|
||||
class GroupHistory(GroupInfo):
|
||||
group = models.ForeignKey(Group, related_name='history_set')
|
||||
acronym = models.CharField(max_length=40)
|
||||
charter = models.ForeignKey('doc.Document', related_name='chartered_group_history_set', blank=True, null=True)
|
||||
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural="group histories"
|
||||
|
||||
|
@ -79,11 +74,17 @@ class GroupStateTransitions(models.Model):
|
|||
state = models.ForeignKey('doc.State', help_text="State for which the next states should be overridden")
|
||||
next_states = models.ManyToManyField('doc.State', related_name='previous_groupstatetransitions_states')
|
||||
|
||||
GROUP_EVENT_CHOICES = [("proposed", "Proposed group"),
|
||||
("started", "Started group"),
|
||||
("concluded", "Concluded group"),
|
||||
]
|
||||
|
||||
GROUP_EVENT_CHOICES = [
|
||||
# core events
|
||||
("proposed", "Proposed group"),
|
||||
("started", "Started group"),
|
||||
("concluded", "Concluded group"),
|
||||
|
||||
# misc group events
|
||||
("added_comment", "Added comment"),
|
||||
("info_changed", "Changed metadata"),
|
||||
]
|
||||
|
||||
class GroupEvent(models.Model):
|
||||
"""An occurrence for a group, used for tracking who, when and what."""
|
||||
group = models.ForeignKey(Group)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from redesign.proxy_utils import TranslatingManager, proxy_role_email
|
||||
|
||||
from models import *
|
||||
from doc.models import Document # for charter text
|
||||
from ietf.wgcharter.utils import get_charter_for_revision, approved_revision
|
||||
|
||||
class Acronym(Group):
|
||||
class LazyIndividualSubmitter(object):
|
||||
|
@ -60,7 +62,7 @@ class Area(Group):
|
|||
#status = models.ForeignKey(AreaStatus)
|
||||
@property
|
||||
def status_id(self):
|
||||
return { "active": 1, "dormant": 2, "conclude": 3 }[self.state_id]
|
||||
return { "active": 1, "dormant": 2, "conclude": 3, "proposed": 4 }[self.state_id]
|
||||
#comments = models.TextField(blank=True)
|
||||
#last_modified_date = models.DateField(auto_now=True)
|
||||
@property
|
||||
|
@ -131,7 +133,7 @@ class IETFWG(Group):
|
|||
#status = models.ForeignKey(WGStatus)
|
||||
@property
|
||||
def status_id(self):
|
||||
return { "active": 1, "dormant": 2, "conclude": 3 }[self.state_id]
|
||||
return { "active": 1, "dormant": 2, "conclude": 3, "proposed": 4 }[self.state_id]
|
||||
#area_director = models.ForeignKey(AreaDirector, null=True)
|
||||
#meeting_scheduled = models.CharField(blank=True, max_length=3)
|
||||
@property
|
||||
|
@ -200,12 +202,23 @@ class IETFWG(Group):
|
|||
from django.conf import settings
|
||||
# get file path from settings. Syntesize file name from path, acronym, and suffix
|
||||
try:
|
||||
filename = os.path.join(settings.IETFWG_DESCRIPTIONS_PATH, self.acronym) + ".desc.txt"
|
||||
# Try getting charter from new charter tool
|
||||
charter = Document.objects.get(docalias__name="charter-ietf-%s" % self.acronym)
|
||||
ch = get_charter_for_revision(charter, charter.rev)
|
||||
name = ch.name
|
||||
rev = approved_revision(ch.rev)
|
||||
filename = os.path.join(charter.get_file_path(), "%s-%s.txt" % (name, rev))
|
||||
desc_file = open(filename)
|
||||
desc = desc_file.read()
|
||||
except BaseException:
|
||||
desc = 'Error Loading Work Group Description'
|
||||
return desc
|
||||
return desc
|
||||
except:
|
||||
try:
|
||||
filename = os.path.join(settings.IETFWG_DESCRIPTIONS_PATH, self.acronym) + ".desc.txt"
|
||||
desc_file = open(filename)
|
||||
desc = desc_file.read()
|
||||
except BaseException:
|
||||
desc = 'Error Loading Work Group Description'
|
||||
return desc
|
||||
|
||||
def additional_urls(self):
|
||||
return self.groupurl_set.all().order_by("name")
|
||||
|
|
28
redesign/group/utils.py
Normal file
28
redesign/group/utils.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from redesign.group.models import *
|
||||
|
||||
def save_group_in_history(group):
|
||||
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(group)
|
||||
del fields["charter"] # Charter is saved canonically on Group
|
||||
fields["group"] = group
|
||||
|
||||
grouphist = GroupHistory(**fields)
|
||||
grouphist.save()
|
||||
|
||||
# save RoleHistory
|
||||
for role in group.role_set.all():
|
||||
rh = RoleHistory(name=role.name, group=grouphist, email=role.email, person=role.person)
|
||||
rh.save()
|
||||
|
||||
# copy many to many
|
||||
for field in group._meta.many_to_many:
|
||||
if field.rel.through and field.rel.through._meta.auto_created:
|
||||
setattr(grouphist, field.name, getattr(group, field.name).all())
|
||||
|
||||
return grouphist
|
||||
|
|
@ -17,11 +17,13 @@ from redesign.group.models import *
|
|||
from redesign.name.models import *
|
||||
from redesign.doc.models import State, StateType
|
||||
from redesign.doc.utils import get_tags_for_stream_id
|
||||
from redesign.doc.models import Document
|
||||
from redesign.name.utils import name
|
||||
from redesign.importing.utils import old_person_to_person
|
||||
from ietf.idtracker.models import AreaGroup, IETFWG, Area, AreaGroup, Acronym, AreaWGURL, IRTF, ChairsHistory, Role, AreaDirector
|
||||
from ietf.liaisons.models import SDOs
|
||||
from ietf.iesg.models import TelechatDates, Telechat, TelechatDate
|
||||
from ietf.wgcharter.utils import set_or_create_charter
|
||||
import workflows.utils
|
||||
|
||||
# imports IETFWG, Area, AreaGroup, Acronym, IRTF, AreaWGURL, SDOs, TelechatDates, dates from Telechat
|
||||
|
@ -30,6 +32,8 @@ import workflows.utils
|
|||
|
||||
# assumptions: persons and states have been imported
|
||||
|
||||
doc_type_charter = name(DocTypeName, "charter", "Charter")
|
||||
|
||||
state_names = dict(
|
||||
bof=name(GroupStateName, slug="bof", name="BOF"),
|
||||
proposed=name(GroupStateName, slug="proposed", name="Proposed"),
|
||||
|
@ -50,6 +54,15 @@ type_names = dict(
|
|||
sdo=name(GroupTypeName, slug="sdo", name="SDO", desc="Standards organization"),
|
||||
)
|
||||
|
||||
group_ballot_names = {
|
||||
'No': name(GroupBallotPositionName, 'no', 'No'),
|
||||
'Yes': name(GroupBallotPositionName, 'yes', 'Yes'),
|
||||
'Abstain': name(GroupBallotPositionName, 'abstain', 'Abstain'),
|
||||
'Block': name(GroupBallotPositionName, 'block', 'Block'),
|
||||
'No Record': name(GroupBallotPositionName, 'norecord', 'No record'),
|
||||
}
|
||||
|
||||
|
||||
# make sure we got the IETF as high-level parent
|
||||
ietf_group, _ = Group.objects.get_or_create(acronym="ietf")
|
||||
ietf_group.name = "IETF"
|
||||
|
@ -322,6 +335,16 @@ for o in IETFWG.objects.all().order_by("pk"):
|
|||
if l in ("none", "not available"):
|
||||
l = ""
|
||||
group.list_archive = l
|
||||
|
||||
charter = set_or_create_charter(group)
|
||||
if group.state_id in ("active", "conclude"):
|
||||
charter.rev = "01"
|
||||
charter.set_state(State.objects.get(type="charter", slug="approved"))
|
||||
else:
|
||||
charter.rev = "00"
|
||||
charter.set_state(State.objects.get(type="charter", slug="notrev"))
|
||||
charter.save()
|
||||
|
||||
group.comments = o.comments.strip() if o.comments else ""
|
||||
|
||||
group.save()
|
||||
|
|
|
@ -283,14 +283,12 @@ for o in IESGHistory.objects.all().order_by('meeting__start_date', 'pk'):
|
|||
existing = history if history else area
|
||||
|
||||
h = GroupHistory(group=area,
|
||||
charter=existing.charter,
|
||||
time=meeting_time,
|
||||
name=existing.name,
|
||||
acronym=existing.acronym,
|
||||
state=existing.state,
|
||||
type=existing.type,
|
||||
parent=existing.parent,
|
||||
iesg_state=existing.iesg_state,
|
||||
ad=existing.ad,
|
||||
list_email=existing.list_email,
|
||||
list_subscribe=existing.list_subscribe,
|
||||
|
|
|
@ -34,9 +34,10 @@ slides_type, _ = StateType.objects.get_or_create(slug="slides", label="State")
|
|||
minutes_type, _ = StateType.objects.get_or_create(slug="minutes", label="State")
|
||||
agenda_type, _ = StateType.objects.get_or_create(slug="agenda", label="State")
|
||||
liaison_att_type, _ = StateType.objects.get_or_create(slug="liai-att", label="State")
|
||||
charter_type, _ = StateType.objects.get_or_create(slug="charter", label="State")
|
||||
|
||||
# draft states
|
||||
print "Importing draft states"
|
||||
print "importing draft states"
|
||||
State.objects.get_or_create(type=draft_type, slug="active", name="Active", order=1)
|
||||
State.objects.get_or_create(type=draft_type, slug="expired", name="Expired", order=2)
|
||||
State.objects.get_or_create(type=draft_type, slug="rfc", name="RFC", order=3)
|
||||
|
@ -180,3 +181,13 @@ for t in (slides_type, minutes_type, agenda_type):
|
|||
print "importing states for", t.slug
|
||||
State.objects.get_or_create(type=t, slug="active", name="Active", order=1)
|
||||
State.objects.get_or_create(type=t, slug="deleted", name="Deleted", order=2)
|
||||
|
||||
# charter states
|
||||
print "importing states for charters"
|
||||
State.objects.get_or_create(type=charter_type, slug="notrev", name="Not currently under review", desc="The proposed charter is not being considered at this time. A proposed charter will remain in this state until an AD moves it to Informal IESG review.")
|
||||
State.objects.get_or_create(type=charter_type, slug="infrev", name="Informal IESG review", desc="This is the initial state when an AD proposes a new charter. The normal next state is Internal review if the idea is accepted, or Not currently under review if the idea is abandoned.")
|
||||
State.objects.get_or_create(type=charter_type, slug="intrev", name="Internal review", desc="The IESG and IAB are reviewing the early draft of the charter; this is the initial IESG and IAB review. The usual next state is External review if the idea is adopted, or Informal IESG review if the IESG decides the idea needs more work, or Not currently under review is the idea is abandoned")
|
||||
State.objects.get_or_create(type=charter_type, slug="extrev", name="External review", desc="The IETF community and possibly other standards development organizations (SDOs) are reviewing the proposed charter. The usual next state is IESG review, although it might move to Not currently under review is the idea is abandoned during the external review.")
|
||||
State.objects.get_or_create(type=charter_type, slug="iesgrev", name="IESG review", desc="The IESG is reviewing the discussion from the external review of the proposed charter. The usual next state is Approved, or Not currently under review if the idea is abandoned.")
|
||||
State.objects.get_or_create(type=charter_type, slug="approved", name="Approved", desc="The charter is approved by the IESG.")
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ class NameAdmin(admin.ModelAdmin):
|
|||
|
||||
admin.site.register(GroupTypeName, NameAdmin)
|
||||
admin.site.register(GroupStateName, NameAdmin)
|
||||
admin.site.register(IesgGroupStateName, NameAdmin)
|
||||
admin.site.register(RoleName, NameAdmin)
|
||||
admin.site.register(StreamName, NameAdmin)
|
||||
admin.site.register(DocRelationshipName, NameAdmin)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -20,11 +20,6 @@ class GroupStateName(NameModel):
|
|||
"""BOF, Proposed, Active, Dormant, Concluded"""
|
||||
class GroupTypeName(NameModel):
|
||||
"""IETF, Area, WG, RG, Team, etc."""
|
||||
class IesgGroupStateName(NameModel):
|
||||
"""Informal IESG review, Internal review, External review, IESG review,
|
||||
WG exists, Not currently under review, Informal IESG recharter review,
|
||||
Internal recharter review, External recharter review, IESG recharter
|
||||
review """
|
||||
class RoleName(NameModel):
|
||||
"""AD, Chair"""
|
||||
class StreamName(NameModel):
|
||||
|
@ -48,6 +43,8 @@ class DocReminderTypeName(NameModel):
|
|||
"Stream state"
|
||||
class BallotPositionName(NameModel):
|
||||
""" Yes, No Objection, Abstain, Discuss, Recuse """
|
||||
class GroupBallotPositionName(NameModel):
|
||||
""" Yes, No, Block, Abstain """
|
||||
class MeetingTypeName(NameModel):
|
||||
"""IETF, Interim"""
|
||||
class SessionStatusName(NameModel):
|
||||
|
|
113
static/css/token-input.css
Normal file
113
static/css/token-input.css
Normal file
|
@ -0,0 +1,113 @@
|
|||
/* Example tokeninput style #1: Token vertical list*/
|
||||
ul.token-input-list {
|
||||
overflow: hidden;
|
||||
height: auto !important;
|
||||
height: 1%;
|
||||
width: 400px;
|
||||
border: 1px solid #999;
|
||||
cursor: text;
|
||||
font-size: 12px;
|
||||
font-family: Verdana;
|
||||
z-index: 999;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #fff;
|
||||
list-style-type: none;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
ul.token-input-list li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
ul.token-input-list li input {
|
||||
border: 0;
|
||||
width: 350px;
|
||||
padding: 3px 8px;
|
||||
background-color: white;
|
||||
-webkit-appearance: caret;
|
||||
}
|
||||
|
||||
li.token-input-token {
|
||||
overflow: hidden;
|
||||
height: auto !important;
|
||||
height: 1%;
|
||||
margin: 3px;
|
||||
padding: 3px 5px;
|
||||
background-color: #d0efa0;
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
cursor: default;
|
||||
display: block;
|
||||
}
|
||||
|
||||
li.token-input-token p {
|
||||
float: left;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li.token-input-token span {
|
||||
float: right;
|
||||
color: #777;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
li.token-input-selected-token {
|
||||
background-color: #08844e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
li.token-input-selected-token span {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
div.token-input-dropdown {
|
||||
position: absolute;
|
||||
width: 400px;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
border-left: 1px solid #ccc;
|
||||
border-right: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
cursor: default;
|
||||
font-size: 12px;
|
||||
font-family: Verdana;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
div.token-input-dropdown p {
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul li {
|
||||
background-color: #fff;
|
||||
padding: 3px;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul li.token-input-dropdown-item {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul li.token-input-dropdown-item2 {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul li em {
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
div.token-input-dropdown ul li.token-input-selected-dropdown-item {
|
||||
background-color: #d0efa0;
|
||||
}
|
||||
|
|
@ -74,6 +74,52 @@ function showBallot(draftName, editPositionUrl) {
|
|||
function editBallot(editPositionUrl) {
|
||||
window.open(editPositionUrl);
|
||||
}
|
||||
|
||||
function showWGBallot(acronym, editPositionUrl) {
|
||||
var handleEditPosition = function() {
|
||||
IETF.ballotDialog.hide();
|
||||
window.location = IETF.editPositionUrl;
|
||||
};
|
||||
var handleClose = function() {
|
||||
IETF.ballotDialog.hide();
|
||||
};
|
||||
var el;
|
||||
|
||||
if (!IETF.ballotDialog) {
|
||||
el = document.createElement("div");
|
||||
el.innerHTML = '<div id="ballot_dialog" style="visibility:hidden;"><div class="hd">Positions for <span id="ballot_dialog_name">wgacronym</span></span></div><div class="bd"> <div id="ballot_dialog_body" style="overflow-y:scroll; height:400px;"></div> </div></div>';
|
||||
document.getElementById("ietf-extras").appendChild(el);
|
||||
|
||||
var buttons = [{text:"Close", handler:handleClose, isDefault:true}];
|
||||
if (("Area_Director" in IETF.user_groups) ||
|
||||
("Secretariat" in IETF.user_groups)) {
|
||||
buttons.unshift({text:"Edit Position", handler:handleEditPosition});
|
||||
}
|
||||
var kl = [new YAHOO.util.KeyListener(document, {keys:27}, handleClose)]
|
||||
IETF.ballotDialog = new YAHOO.widget.Dialog("ballot_dialog", {
|
||||
visible:false, draggable:false, close:true, modal:true,
|
||||
width:"860px", fixedcenter:true, constraintoviewport:true,
|
||||
buttons: buttons, keylisteners:kl});
|
||||
IETF.ballotDialog.render();
|
||||
}
|
||||
document.getElementById("ballot_dialog_name").innerHTML = acronym;
|
||||
IETF.editPositionUrl = editPositionUrl;
|
||||
|
||||
IETF.ballotDialog.show();
|
||||
|
||||
el = document.getElementById("ballot_dialog_body");
|
||||
el.innerHTML = "Loading...";
|
||||
YAHOO.util.Connect.asyncRequest('GET',
|
||||
"/wgcharter/"+acronym+"/_ballot.data",
|
||||
{ success: function(o) { el.innerHTML = (o.responseText !== undefined) ? o.responseText : "?"; },
|
||||
failure: function(o) { el.innerHTML = "Error: "+o.status+" "+o.statusText; },
|
||||
argument: null
|
||||
}, null);
|
||||
}
|
||||
function editWGBallot(editPositionUrl) {
|
||||
window.open(editPositionUrl);
|
||||
}
|
||||
|
||||
function showStream(dialogTitle, infoStreamUrl) {
|
||||
var handleClose = function() {
|
||||
IETF.streamDialog.hide();
|
||||
|
|
793
static/js/lib/jquery.tokeninput.js
Normal file
793
static/js/lib/jquery.tokeninput.js
Normal file
|
@ -0,0 +1,793 @@
|
|||
/*
|
||||
* jQuery Plugin: Tokenizing Autocomplete Text Entry
|
||||
* Version 1.5.0
|
||||
*
|
||||
* Copyright (c) 2009 James Smith (http://loopj.com)
|
||||
* Licensed jointly under the GPL and MIT licenses,
|
||||
* choose which one suits your project best!
|
||||
*
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
// Default settings
|
||||
var DEFAULT_SETTINGS = {
|
||||
hintText: "Type in a search term",
|
||||
noResultsText: "No results",
|
||||
searchingText: "Searching...",
|
||||
deleteText: "×",
|
||||
searchDelay: 300,
|
||||
minChars: 1,
|
||||
tokenLimit: null,
|
||||
jsonContainer: null,
|
||||
method: "GET",
|
||||
contentType: "json",
|
||||
queryParam: "q",
|
||||
tokenDelimiter: ",",
|
||||
preventDuplicates: false,
|
||||
prePopulate: null,
|
||||
processPrePopulate: false,
|
||||
animateDropdown: true,
|
||||
onResult: null,
|
||||
onAdd: null,
|
||||
onDelete: null,
|
||||
idPrefix: "token-input-"
|
||||
};
|
||||
|
||||
// Default classes to use when theming
|
||||
var DEFAULT_CLASSES = {
|
||||
tokenList: "token-input-list",
|
||||
token: "token-input-token",
|
||||
tokenDelete: "token-input-delete-token",
|
||||
selectedToken: "token-input-selected-token",
|
||||
highlightedToken: "token-input-highlighted-token",
|
||||
dropdown: "token-input-dropdown",
|
||||
dropdownItem: "token-input-dropdown-item",
|
||||
dropdownItem2: "token-input-dropdown-item2",
|
||||
selectedDropdownItem: "token-input-selected-dropdown-item",
|
||||
inputToken: "token-input-input-token"
|
||||
};
|
||||
|
||||
// Input box position "enum"
|
||||
var POSITION = {
|
||||
BEFORE: 0,
|
||||
AFTER: 1,
|
||||
END: 2
|
||||
};
|
||||
|
||||
// Keys "enum"
|
||||
var KEY = {
|
||||
BACKSPACE: 8,
|
||||
TAB: 9,
|
||||
ENTER: 13,
|
||||
ESCAPE: 27,
|
||||
SPACE: 32,
|
||||
PAGE_UP: 33,
|
||||
PAGE_DOWN: 34,
|
||||
END: 35,
|
||||
HOME: 36,
|
||||
LEFT: 37,
|
||||
UP: 38,
|
||||
RIGHT: 39,
|
||||
DOWN: 40,
|
||||
NUMPAD_ENTER: 108,
|
||||
COMMA: 188
|
||||
};
|
||||
|
||||
// Additional public (exposed) methods
|
||||
var methods = {
|
||||
init: function(url_or_data_or_function, options) {
|
||||
var settings = $.extend({}, DEFAULT_SETTINGS, options || {});
|
||||
|
||||
return this.each(function () {
|
||||
$(this).data("tokenInputObject", new $.TokenList(this, url_or_data_or_function, settings));
|
||||
});
|
||||
},
|
||||
clear: function() {
|
||||
this.data("tokenInputObject").clear();
|
||||
return this;
|
||||
},
|
||||
add: function(item) {
|
||||
this.data("tokenInputObject").add(item);
|
||||
return this;
|
||||
},
|
||||
remove: function(item) {
|
||||
this.data("tokenInputObject").remove(item);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// Expose the .tokenInput function to jQuery as a plugin
|
||||
$.fn.tokenInput = function (method) {
|
||||
// Method calling and initialization logic
|
||||
if(methods[method]) {
|
||||
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
|
||||
} else {
|
||||
return methods.init.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
// TokenList class for each input
|
||||
$.TokenList = function (input, url_or_data, settings) {
|
||||
//
|
||||
// Initialization
|
||||
//
|
||||
|
||||
// Configure the data source
|
||||
if(typeof(url_or_data) === "string") {
|
||||
// Set the url to query against
|
||||
settings.url = url_or_data;
|
||||
|
||||
// Make a smart guess about cross-domain if it wasn't explicitly specified
|
||||
if(settings.crossDomain === undefined) {
|
||||
if(settings.url.indexOf("://") === -1) {
|
||||
settings.crossDomain = false;
|
||||
} else {
|
||||
settings.crossDomain = (location.href.split(/\/+/g)[1] !== settings.url.split(/\/+/g)[1]);
|
||||
}
|
||||
}
|
||||
} else if(typeof(url_or_data) === "object") {
|
||||
// Set the local data to search through
|
||||
settings.local_data = url_or_data;
|
||||
}
|
||||
|
||||
// Build class names
|
||||
if(settings.classes) {
|
||||
// Use custom class names
|
||||
settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes);
|
||||
} else if(settings.theme) {
|
||||
// Use theme-suffixed default class names
|
||||
settings.classes = {};
|
||||
$.each(DEFAULT_CLASSES, function(key, value) {
|
||||
settings.classes[key] = value + "-" + settings.theme;
|
||||
});
|
||||
} else {
|
||||
settings.classes = DEFAULT_CLASSES;
|
||||
}
|
||||
|
||||
|
||||
// Save the tokens
|
||||
var saved_tokens = [];
|
||||
|
||||
// Keep track of the number of tokens in the list
|
||||
var token_count = 0;
|
||||
|
||||
// Basic cache to save on db hits
|
||||
var cache = new $.TokenList.Cache();
|
||||
|
||||
// Keep track of the timeout, old vals
|
||||
var timeout;
|
||||
var input_val;
|
||||
|
||||
// Create a new text input an attach keyup events
|
||||
var input_box = $("<input type=\"text\" autocomplete=\"off\">")
|
||||
.css({
|
||||
outline: "none"
|
||||
})
|
||||
.attr("id", settings.idPrefix + input.id)
|
||||
.focus(function () {
|
||||
if (settings.tokenLimit === null || settings.tokenLimit !== token_count) {
|
||||
show_dropdown_hint();
|
||||
}
|
||||
})
|
||||
.blur(function () {
|
||||
hide_dropdown();
|
||||
$(this).val("");
|
||||
})
|
||||
.bind("keyup keydown blur update", resize_input)
|
||||
.keydown(function (event) {
|
||||
var previous_token;
|
||||
var next_token;
|
||||
|
||||
switch(event.keyCode) {
|
||||
case KEY.LEFT:
|
||||
case KEY.RIGHT:
|
||||
case KEY.UP:
|
||||
case KEY.DOWN:
|
||||
if(!$(this).val()) {
|
||||
previous_token = input_token.prev();
|
||||
next_token = input_token.next();
|
||||
|
||||
if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) {
|
||||
// Check if there is a previous/next token and it is selected
|
||||
if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) {
|
||||
deselect_token($(selected_token), POSITION.BEFORE);
|
||||
} else {
|
||||
deselect_token($(selected_token), POSITION.AFTER);
|
||||
}
|
||||
} else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) {
|
||||
// We are moving left, select the previous token if it exists
|
||||
select_token($(previous_token.get(0)));
|
||||
} else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) {
|
||||
// We are moving right, select the next token if it exists
|
||||
select_token($(next_token.get(0)));
|
||||
}
|
||||
} else {
|
||||
var dropdown_item = null;
|
||||
|
||||
if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
|
||||
dropdown_item = $(selected_dropdown_item).next();
|
||||
} else {
|
||||
dropdown_item = $(selected_dropdown_item).prev();
|
||||
}
|
||||
|
||||
if(dropdown_item.length) {
|
||||
select_dropdown_item(dropdown_item);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.BACKSPACE:
|
||||
previous_token = input_token.prev();
|
||||
|
||||
if(!$(this).val().length) {
|
||||
if(selected_token) {
|
||||
delete_token($(selected_token));
|
||||
} else if(previous_token.length) {
|
||||
select_token($(previous_token.get(0)));
|
||||
}
|
||||
|
||||
return false;
|
||||
} else if($(this).val().length === 1) {
|
||||
hide_dropdown();
|
||||
} else {
|
||||
// set a timeout just long enough to let this function finish.
|
||||
setTimeout(function(){do_search();}, 5);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.TAB:
|
||||
case KEY.ENTER:
|
||||
case KEY.NUMPAD_ENTER:
|
||||
case KEY.COMMA:
|
||||
if(selected_dropdown_item) {
|
||||
add_token($(selected_dropdown_item).data("tokeninput"));
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.ESCAPE:
|
||||
hide_dropdown();
|
||||
return true;
|
||||
|
||||
default:
|
||||
if(String.fromCharCode(event.which)) {
|
||||
// set a timeout just long enough to let this function finish.
|
||||
setTimeout(function(){do_search();}, 5);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Keep a reference to the original input box
|
||||
var hidden_input = $(input)
|
||||
.hide()
|
||||
.val("")
|
||||
.focus(function () {
|
||||
input_box.focus();
|
||||
})
|
||||
.blur(function () {
|
||||
input_box.blur();
|
||||
});
|
||||
|
||||
// Keep a reference to the selected token and dropdown item
|
||||
var selected_token = null;
|
||||
var selected_token_index = 0;
|
||||
var selected_dropdown_item = null;
|
||||
|
||||
// The list to store the token items in
|
||||
var token_list = $("<ul />")
|
||||
.addClass(settings.classes.tokenList)
|
||||
.click(function (event) {
|
||||
var li = $(event.target).closest("li");
|
||||
if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
|
||||
toggle_select_token(li);
|
||||
} else {
|
||||
// Deselect selected token
|
||||
if(selected_token) {
|
||||
deselect_token($(selected_token), POSITION.END);
|
||||
}
|
||||
|
||||
// Focus input box
|
||||
input_box.focus();
|
||||
}
|
||||
})
|
||||
.mouseover(function (event) {
|
||||
var li = $(event.target).closest("li");
|
||||
if(li && selected_token !== this) {
|
||||
li.addClass(settings.classes.highlightedToken);
|
||||
}
|
||||
})
|
||||
.mouseout(function (event) {
|
||||
var li = $(event.target).closest("li");
|
||||
if(li && selected_token !== this) {
|
||||
li.removeClass(settings.classes.highlightedToken);
|
||||
}
|
||||
})
|
||||
.insertBefore(hidden_input);
|
||||
|
||||
// The token holding the input box
|
||||
var input_token = $("<li />")
|
||||
.addClass(settings.classes.inputToken)
|
||||
.appendTo(token_list)
|
||||
.append(input_box);
|
||||
|
||||
// The list to store the dropdown items in
|
||||
var dropdown = $("<div>")
|
||||
.addClass(settings.classes.dropdown)
|
||||
.appendTo("body")
|
||||
.hide();
|
||||
|
||||
// Magic element to help us resize the text input
|
||||
var input_resizer = $("<tester/>")
|
||||
.insertAfter(input_box)
|
||||
.css({
|
||||
position: "absolute",
|
||||
top: -9999,
|
||||
left: -9999,
|
||||
width: "auto",
|
||||
fontSize: input_box.css("fontSize"),
|
||||
fontFamily: input_box.css("fontFamily"),
|
||||
fontWeight: input_box.css("fontWeight"),
|
||||
letterSpacing: input_box.css("letterSpacing"),
|
||||
whiteSpace: "nowrap"
|
||||
});
|
||||
|
||||
// Pre-populate list if items exist
|
||||
hidden_input.val("");
|
||||
var li_data = settings.prePopulate || hidden_input.data("pre");
|
||||
if(settings.processPrePopulate && $.isFunction(settings.onResult)) {
|
||||
li_data = settings.onResult.call(hidden_input, li_data);
|
||||
}
|
||||
if(li_data && li_data.length) {
|
||||
$.each(li_data, function (index, value) {
|
||||
insert_token(value);
|
||||
checkTokenLimit();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Public functions
|
||||
//
|
||||
|
||||
this.clear = function() {
|
||||
token_list.children("li").each(function() {
|
||||
if ($(this).children("input").length === 0) {
|
||||
delete_token($(this));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.add = function(item) {
|
||||
add_token(item);
|
||||
}
|
||||
|
||||
this.remove = function(item) {
|
||||
token_list.children("li").each(function() {
|
||||
if ($(this).children("input").length === 0) {
|
||||
var currToken = $(this).data("tokeninput");
|
||||
var match = true;
|
||||
for (var prop in item) {
|
||||
if (item[prop] !== currToken[prop]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
delete_token($(this));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Private functions
|
||||
//
|
||||
|
||||
function checkTokenLimit() {
|
||||
if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
|
||||
input_box.hide();
|
||||
hide_dropdown();
|
||||
return;
|
||||
} else {
|
||||
input_box.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function resize_input() {
|
||||
if(input_val === (input_val = input_box.val())) {return;}
|
||||
|
||||
// Enter new content into resizer and resize input accordingly
|
||||
var escaped = input_val.replace(/&/g, '&').replace(/\s/g,' ').replace(/</g, '<').replace(/>/g, '>');
|
||||
input_resizer.html(escaped);
|
||||
input_box.width(input_resizer.width() + 30);
|
||||
}
|
||||
|
||||
function is_printable_character(keycode) {
|
||||
return ((keycode >= 48 && keycode <= 90) || // 0-1a-z
|
||||
(keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * .
|
||||
(keycode >= 186 && keycode <= 192) || // ; = , - . / ^
|
||||
(keycode >= 219 && keycode <= 222)); // ( \ ) '
|
||||
}
|
||||
|
||||
// Inner function to a token to the list
|
||||
function insert_token(item) {
|
||||
var this_token = $("<li><p>"+ item.name +"</p></li>")
|
||||
.addClass(settings.classes.token)
|
||||
.insertBefore(input_token);
|
||||
|
||||
// The 'delete token' button
|
||||
$("<span>" + settings.deleteText + "</span>")
|
||||
.addClass(settings.classes.tokenDelete)
|
||||
.appendTo(this_token)
|
||||
.click(function () {
|
||||
delete_token($(this).parent());
|
||||
return false;
|
||||
});
|
||||
|
||||
// Store data on the token
|
||||
var token_data = {"id": item.id, "name": item.name};
|
||||
$.data(this_token.get(0), "tokeninput", item);
|
||||
|
||||
// Save this token for duplicate checking
|
||||
saved_tokens = saved_tokens.slice(0,selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index));
|
||||
selected_token_index++;
|
||||
|
||||
// Update the hidden input
|
||||
var token_ids = $.map(saved_tokens, function (el) {
|
||||
return el.id;
|
||||
});
|
||||
hidden_input.val(token_ids.join(settings.tokenDelimiter));
|
||||
|
||||
token_count += 1;
|
||||
|
||||
return this_token;
|
||||
}
|
||||
|
||||
// Add a token to the token list based on user input
|
||||
function add_token (item) {
|
||||
var callback = settings.onAdd;
|
||||
|
||||
// See if the token already exists and select it if we don't want duplicates
|
||||
if(token_count > 0 && settings.preventDuplicates) {
|
||||
var found_existing_token = null;
|
||||
token_list.children().each(function () {
|
||||
var existing_token = $(this);
|
||||
var existing_data = $.data(existing_token.get(0), "tokeninput");
|
||||
if(existing_data && existing_data.id === item.id) {
|
||||
found_existing_token = existing_token;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if(found_existing_token) {
|
||||
select_token(found_existing_token);
|
||||
input_token.insertAfter(found_existing_token);
|
||||
input_box.focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the new tokens
|
||||
insert_token(item);
|
||||
checkTokenLimit();
|
||||
|
||||
// Clear input box
|
||||
input_box.val("");
|
||||
|
||||
// Don't show the help dropdown, they've got the idea
|
||||
hide_dropdown();
|
||||
|
||||
// Execute the onAdd callback if defined
|
||||
if($.isFunction(callback)) {
|
||||
callback.call(hidden_input,item);
|
||||
}
|
||||
}
|
||||
|
||||
// Select a token in the token list
|
||||
function select_token (token) {
|
||||
token.addClass(settings.classes.selectedToken);
|
||||
selected_token = token.get(0);
|
||||
|
||||
// Hide input box
|
||||
input_box.val("");
|
||||
|
||||
// Hide dropdown if it is visible (eg if we clicked to select token)
|
||||
hide_dropdown();
|
||||
}
|
||||
|
||||
// Deselect a token in the token list
|
||||
function deselect_token (token, position) {
|
||||
token.removeClass(settings.classes.selectedToken);
|
||||
selected_token = null;
|
||||
|
||||
if(position === POSITION.BEFORE) {
|
||||
input_token.insertBefore(token);
|
||||
selected_token_index--;
|
||||
} else if(position === POSITION.AFTER) {
|
||||
input_token.insertAfter(token);
|
||||
selected_token_index++;
|
||||
} else {
|
||||
input_token.appendTo(token_list);
|
||||
selected_token_index = token_count;
|
||||
}
|
||||
|
||||
// Show the input box and give it focus again
|
||||
input_box.focus();
|
||||
}
|
||||
|
||||
// Toggle selection of a token in the token list
|
||||
function toggle_select_token(token) {
|
||||
var previous_selected_token = selected_token;
|
||||
|
||||
if(selected_token) {
|
||||
deselect_token($(selected_token), POSITION.END);
|
||||
}
|
||||
|
||||
if(previous_selected_token === token.get(0)) {
|
||||
deselect_token(token, POSITION.END);
|
||||
} else {
|
||||
select_token(token);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete a token from the token list
|
||||
function delete_token (token) {
|
||||
// Remove the id from the saved list
|
||||
var token_data = $.data(token.get(0), "tokeninput");
|
||||
var callback = settings.onDelete;
|
||||
|
||||
var index = token.prevAll().length;
|
||||
if(index > selected_token_index) index--;
|
||||
|
||||
// Delete the token
|
||||
token.remove();
|
||||
selected_token = null;
|
||||
|
||||
// Show the input box and give it focus again
|
||||
input_box.focus();
|
||||
|
||||
// Remove this token from the saved list
|
||||
saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1));
|
||||
if(index < selected_token_index) selected_token_index--;
|
||||
|
||||
// Update the hidden input
|
||||
var token_ids = $.map(saved_tokens, function (el) {
|
||||
return el.id;
|
||||
});
|
||||
hidden_input.val(token_ids.join(settings.tokenDelimiter));
|
||||
|
||||
token_count -= 1;
|
||||
|
||||
if(settings.tokenLimit !== null) {
|
||||
input_box
|
||||
.show()
|
||||
.val("")
|
||||
.focus();
|
||||
}
|
||||
|
||||
// Execute the onDelete callback if defined
|
||||
if($.isFunction(callback)) {
|
||||
callback.call(hidden_input,token_data);
|
||||
}
|
||||
}
|
||||
|
||||
// Hide and clear the results dropdown
|
||||
function hide_dropdown () {
|
||||
dropdown.hide().empty();
|
||||
selected_dropdown_item = null;
|
||||
}
|
||||
|
||||
function show_dropdown() {
|
||||
dropdown
|
||||
.css({
|
||||
position: "absolute",
|
||||
top: $(token_list).offset().top + $(token_list).outerHeight(),
|
||||
left: $(token_list).offset().left,
|
||||
zindex: 999
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
function show_dropdown_searching () {
|
||||
if(settings.searchingText) {
|
||||
dropdown.html("<p>"+settings.searchingText+"</p>");
|
||||
show_dropdown();
|
||||
}
|
||||
}
|
||||
|
||||
function show_dropdown_hint () {
|
||||
if(settings.hintText) {
|
||||
dropdown.html("<p>"+settings.hintText+"</p>");
|
||||
show_dropdown();
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight the query part of the search term
|
||||
function highlight_term(value, term) {
|
||||
return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>");
|
||||
}
|
||||
|
||||
// Populate the results dropdown with some results
|
||||
function populate_dropdown (query, results) {
|
||||
if(results && results.length) {
|
||||
dropdown.empty();
|
||||
var dropdown_ul = $("<ul>")
|
||||
.appendTo(dropdown)
|
||||
.mouseover(function (event) {
|
||||
select_dropdown_item($(event.target).closest("li"));
|
||||
})
|
||||
.mousedown(function (event) {
|
||||
add_token($(event.target).closest("li").data("tokeninput"));
|
||||
return false;
|
||||
})
|
||||
.hide();
|
||||
|
||||
$.each(results, function(index, value) {
|
||||
var this_li = $("<li>" + highlight_term(value.name, query) + "</li>")
|
||||
.appendTo(dropdown_ul);
|
||||
|
||||
if(index % 2) {
|
||||
this_li.addClass(settings.classes.dropdownItem);
|
||||
} else {
|
||||
this_li.addClass(settings.classes.dropdownItem2);
|
||||
}
|
||||
|
||||
if(index === 0) {
|
||||
select_dropdown_item(this_li);
|
||||
}
|
||||
|
||||
$.data(this_li.get(0), "tokeninput", value);
|
||||
});
|
||||
|
||||
show_dropdown();
|
||||
|
||||
if(settings.animateDropdown) {
|
||||
dropdown_ul.slideDown("fast");
|
||||
} else {
|
||||
dropdown_ul.show();
|
||||
}
|
||||
} else {
|
||||
if(settings.noResultsText) {
|
||||
dropdown.html("<p>"+settings.noResultsText+"</p>");
|
||||
show_dropdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight an item in the results dropdown
|
||||
function select_dropdown_item (item) {
|
||||
if(item) {
|
||||
if(selected_dropdown_item) {
|
||||
deselect_dropdown_item($(selected_dropdown_item));
|
||||
}
|
||||
|
||||
item.addClass(settings.classes.selectedDropdownItem);
|
||||
selected_dropdown_item = item.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove highlighting from an item in the results dropdown
|
||||
function deselect_dropdown_item (item) {
|
||||
item.removeClass(settings.classes.selectedDropdownItem);
|
||||
selected_dropdown_item = null;
|
||||
}
|
||||
|
||||
// Do a search and show the "searching" dropdown if the input is longer
|
||||
// than settings.minChars
|
||||
function do_search() {
|
||||
var query = input_box.val().toLowerCase();
|
||||
|
||||
if(query && query.length) {
|
||||
if(selected_token) {
|
||||
deselect_token($(selected_token), POSITION.AFTER);
|
||||
}
|
||||
|
||||
if(query.length >= settings.minChars) {
|
||||
show_dropdown_searching();
|
||||
clearTimeout(timeout);
|
||||
|
||||
timeout = setTimeout(function(){
|
||||
run_search(query);
|
||||
}, settings.searchDelay);
|
||||
} else {
|
||||
hide_dropdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do the actual search
|
||||
function run_search(query) {
|
||||
var cached_results = cache.get(query);
|
||||
if(cached_results) {
|
||||
populate_dropdown(query, cached_results);
|
||||
} else {
|
||||
// Are we doing an ajax search or local data search?
|
||||
if(settings.url) {
|
||||
// Extract exisiting get params
|
||||
var ajax_params = {};
|
||||
ajax_params.data = {};
|
||||
if(settings.url.indexOf("?") > -1) {
|
||||
var parts = settings.url.split("?");
|
||||
ajax_params.url = parts[0];
|
||||
|
||||
var param_array = parts[1].split("&");
|
||||
$.each(param_array, function (index, value) {
|
||||
var kv = value.split("=");
|
||||
ajax_params.data[kv[0]] = kv[1];
|
||||
});
|
||||
} else {
|
||||
ajax_params.url = settings.url;
|
||||
}
|
||||
|
||||
// Prepare the request
|
||||
ajax_params.data[settings.queryParam] = query;
|
||||
ajax_params.type = settings.method;
|
||||
ajax_params.dataType = settings.contentType;
|
||||
if(settings.crossDomain) {
|
||||
ajax_params.dataType = "jsonp";
|
||||
}
|
||||
|
||||
// Attach the success callback
|
||||
ajax_params.success = function(results) {
|
||||
if($.isFunction(settings.onResult)) {
|
||||
results = settings.onResult.call(hidden_input, results);
|
||||
}
|
||||
cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
|
||||
|
||||
// only populate the dropdown if the results are associated with the active search query
|
||||
if(input_box.val().toLowerCase() === query) {
|
||||
populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
|
||||
}
|
||||
};
|
||||
|
||||
// Make the request
|
||||
$.ajax(ajax_params);
|
||||
} else if(settings.local_data) {
|
||||
// Do the search through local data
|
||||
var results = $.grep(settings.local_data, function (row) {
|
||||
return row.name.toLowerCase().indexOf(query.toLowerCase()) > -1;
|
||||
});
|
||||
|
||||
if($.isFunction(settings.onResult)) {
|
||||
results = settings.onResult.call(hidden_input, results);
|
||||
}
|
||||
cache.add(query, results);
|
||||
populate_dropdown(query, results);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Really basic cache for the results
|
||||
$.TokenList.Cache = function (options) {
|
||||
var settings = $.extend({
|
||||
max_size: 500
|
||||
}, options);
|
||||
|
||||
var data = {};
|
||||
var size = 0;
|
||||
|
||||
var flush = function () {
|
||||
data = {};
|
||||
size = 0;
|
||||
};
|
||||
|
||||
this.add = function (query, results) {
|
||||
if(size > settings.max_size) {
|
||||
flush();
|
||||
}
|
||||
|
||||
if(!data[query]) {
|
||||
size += 1;
|
||||
}
|
||||
|
||||
data[query] = results;
|
||||
};
|
||||
|
||||
this.get = function (query) {
|
||||
return data[query];
|
||||
};
|
||||
};
|
||||
}(jQuery));
|
29
static/js/wg-change-state.js
Normal file
29
static/js/wg-change-state.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
jQuery(document).ready(function () {
|
||||
|
||||
function setMessageDraft(state) {
|
||||
if (jQuery("#id_state").val() == "conclude") {
|
||||
jQuery("#id_message").val("");
|
||||
} else {
|
||||
if (message[state]) {
|
||||
jQuery("#id_message").val(message[state]);
|
||||
} else {
|
||||
jQuery("#id_message").val("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jQuery("#id_charter_state").click(function (e) {
|
||||
setMessageDraft(jQuery(this).val());
|
||||
});
|
||||
|
||||
jQuery("#id_state").click(function (e) {
|
||||
setMessageDraft(jQuery("#id_charter_state").val());
|
||||
});
|
||||
|
||||
var prev_mesg = jQuery("#id_message").val();
|
||||
|
||||
if (prev_mesg == "") {
|
||||
jQuery("#id_charter_state").click();
|
||||
}
|
||||
|
||||
});
|
14
static/js/wg-edit-position.js
Normal file
14
static/js/wg-edit-position.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
jQuery(document).ready(function () {
|
||||
function setDiscussWidgetVisibility(block) {
|
||||
if (block)
|
||||
jQuery("form.position-form .block_comment-widgets").show();
|
||||
else
|
||||
jQuery("form.position-form .block_comment-widgets").hide();
|
||||
}
|
||||
|
||||
jQuery("form.position-form input[name=position]").click(function (e) {
|
||||
setDiscussWidgetVisibility(jQuery(this).val() == "block");
|
||||
});
|
||||
|
||||
setDiscussWidgetVisibility(jQuery("form.position-form input[name=position][value=block]").is(':checked'));
|
||||
});
|
Loading…
Reference in a new issue