Merge facelift-r8510 branch with trunk. The IPR tool still has some non-facelifted parts and ideosyncracies. Fix a couple of minor bugs (including infamous empty licensing choice) in the IPR code, and port the IPR views to use the render shortcut.

- Legacy-Id: 8896
This commit is contained in:
Ole Laursen 2015-01-22 17:38:05 +00:00
commit d0999c8b5e
341 changed files with 16114 additions and 9595 deletions

25
README Normal file
View file

@ -0,0 +1,25 @@
This is the "facelift" datatracker branch that uses Twitter Bootstrap for
the UI.
You need to install a few new django extensions:
https://pypi.python.org/pypi/django-widget-tweaks
https://pypi.python.org/pypi/django-bootstrap3
https://pypi.python.org/pypi/django-typogrify
The meta goal of this effort is: *** NO CHANGES TO THE PYTHON CODE ***
Whenever changes to the python code are made, they can only fix HTML bugs,
or add comments (tagged with "FACELIFT") about functionality that can be
removed once the facelift templates become default. Or they need to add
functionality that is only called from the new facelift templates.
Javascript that is only used on one template goes into that template.
Javascript that is used by more than one template goes into ietf.js.
CSS that is only used on one template goes into that template.
CSS that is used by more than one template goes into ietf.css. No CSS in the
templates or - god forbid - style tags! (And no CSS or HTML styling in
python code!!)
Templates that use jquery or bootstrap plugins include the css in the pagehead
block, and the js in the js block.

11
TODO Normal file
View file

@ -0,0 +1,11 @@
Major pieces not facelifted: milestone editing, liaison editing, WG workflow customization
Use affix for navigation on active_wgs.html
Figure out why {% if debug %} does not work in the text templates under ietf/templates_facelift/community/public.
Make django generate HTML5 date inputs or use a js-based datepicker.
Deferring ballots does not work. (Seems to be an upstream bug.)
Make tables that are too wide to usefully work on small screens responsive. See http://getbootstrap.com/css/#tables-responsive

View file

@ -1,5 +1,6 @@
from django import template
from django.template.loader import render_to_string
from django.conf import settings
from ietf.community.models import CommunityList
from ietf.group.models import Role
@ -9,7 +10,7 @@ register = template.Library()
class CommunityListNode(template.Node):
def __init__(self, user, var_name):
self.user = user
self.var_name = var_name
@ -57,13 +58,13 @@ def show_field(field, doc):
class CommunityListViewNode(template.Node):
def __init__(self, clist):
self.clist = clist
def render(self, context):
clist = self.clist.resolve(context)
if not clist.cached:
if settings.DEBUG or not clist.cached:
clist.cached = render_to_string('community/raw_view.html',
{'cl': clist,
'dc': clist.get_display_config()})

88
ietf/doc/fields.py Normal file
View file

@ -0,0 +1,88 @@
import json
from django.utils.html import escape
from django import forms
from django.core.urlresolvers import reverse as urlreverse
import debug # pyflakes:ignore
from ietf.doc.models import Document, DocAlias
def select2_id_doc_name_json(objs):
return json.dumps([{ "id": o.pk, "text": escape(o.name) } for o in objs])
class SearchableDocumentsField(forms.CharField):
"""Server-based multi-select field for choosing documents using
select2.js.
The field uses a comma-separated list of primary keys in a
CharField element as its API with some extra attributes used by
the Javascript part."""
def __init__(self,
max_entries=None, # max number of selected objs
model=Document,
hint_text="Type in name to search for document",
doc_type="draft",
*args, **kwargs):
kwargs["max_length"] = 10000
self.max_entries = max_entries
self.doc_type = doc_type
self.model = model
super(SearchableDocumentsField, self).__init__(*args, **kwargs)
self.widget.attrs["class"] = "select2-field"
self.widget.attrs["data-placeholder"] = hint_text
if self.max_entries != None:
self.widget.attrs["data-max-entries"] = self.max_entries
def parse_select2_value(self, value):
return [x.strip() for x in value.split(",") if x.strip()]
def prepare_value(self, value):
if not value:
value = ""
if isinstance(value, basestring):
pks = self.parse_select2_value(value)
value = self.model.objects.filter(pk__in=pks)
filter_args = {}
if self.model == DocAlias:
filter_args["document__type"] = self.doc_type
else:
filter_args["type"] = self.doc_type
value = value.filter(**filter_args)
if isinstance(value, self.model):
value = [value]
self.widget.attrs["data-pre"] = select2_id_doc_name_json(value)
# doing this in the constructor is difficult because the URL
# patterns may not have been fully constructed there yet
self.widget.attrs["data-ajax-url"] = urlreverse("ajax_select2_search_docs", kwargs={
"doc_type": self.doc_type,
"model_name": self.model.__name__.lower()
})
return u",".join(unicode(o.pk) for o in value)
def clean(self, value):
value = super(SearchableDocumentsField, self).clean(value)
pks = self.parse_select2_value(value)
objs = self.model.objects.filter(pk__in=pks)
found_pks = [str(o.pk) for o in objs]
failed_pks = [x for x in pks if x not in found_pks]
if failed_pks:
raise forms.ValidationError(u"Could not recognize the following documents: {pks}. You can only input documents already registered in the Datatracker.".format(pks=", ".join(failed_pks)))
if self.max_entries != None and len(objs) > self.max_entries:
raise forms.ValidationError(u"You can select at most %s entries only." % self.max_entries)
return objs
class SearchableDocAliasesField(SearchableDocumentsField):
def __init__(self, model=DocAlias, *args, **kwargs):
super(SearchableDocAliasesField, self).__init__(model=model, *args, **kwargs)

View file

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
from south.v2 import DataMigration
class Migration(DataMigration):

View file

@ -87,35 +87,32 @@ def ballot_icon(context, doc):
positions = list(doc.active_ballot().active_ad_positions().items())
positions.sort(key=sort_key)
edit_position_url = ""
if has_role(user, "Area Director"):
edit_position_url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=doc.name, ballot_id=ballot.pk))
title = "IESG positions (click to show more%s)" % (", right-click to edit position" if edit_position_url else "")
res = ['<a href="%s" data-popup="%s" data-edit="%s" title="%s" class="ballot-icon"><table>' % (
urlreverse("doc_ballot", kwargs=dict(name=doc.name, ballot_id=ballot.pk)),
res = ['<a href="%s" data-toggle="modal" data-target="#modal-%d" title="IESG positions (click to show more)" class="ballot-icon"><table>' % (
urlreverse("ietf.doc.views_doc.ballot_popup", kwargs=dict(name=doc.name, ballot_id=ballot.pk)),
edit_position_url,
title
)]
ballot.pk)]
res.append("<tr>")
for i, (ad, pos) in enumerate(positions):
if i > 0 and i % 5 == 0:
res.append("</tr>")
res.append("<tr>")
res.append("</tr><tr>")
c = "position-%s" % (pos.pos.slug if pos else "norecord")
if user_is_person(user, ad):
c += " my"
res.append('<td class="%s" />' % c)
res.append('<td class="%s"></td>' % c)
res.append("</tr>")
res.append("</table></a>")
# add sufficient table calls to last row to avoid HTML validation warning
while (i + 1) % 5 != 0:
res.append('<td class="empty"></td>')
i = i + 1
res.append("</tr></table></a>")
# XXX FACELIFT: Loading via href will go away in bootstrap 4.
# See http://getbootstrap.com/javascript/#modals-usage
res.append('<div id="modal-%d" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true"><div class="modal-dialog modal-lg"><div class="modal-content"></div></div></div>' % ballot.pk)
return "".join(res)
@ -137,7 +134,8 @@ def ballotposition(doc, user):
@register.filter
def state_age_colored(doc):
# FACELIFT: added flavor argument for styling
def state_age_colored(doc, flavor=""):
if doc.type_id == 'draft':
if not doc.get_state_slug() in ["active", "rfc"]:
# Don't show anything for expired/withdrawn/replaced drafts
@ -156,7 +154,7 @@ def state_age_colored(doc):
except IndexError:
state_date = datetime.date(1990,1,1)
days = (datetime.date.today() - state_date).days
# loosely based on
# loosely based on
# http://trac.tools.ietf.org/group/iesg/trac/wiki/PublishPath
if iesg_state == "lc":
goal1 = 30
@ -180,16 +178,26 @@ def state_age_colored(doc):
goal1 = 14
goal2 = 28
if days > goal2:
class_name = "ietf-small ietf-highlight-r"
if flavor == "facelift":
class_name = "label label-danger"
else:
class_name = "ietf-small ietf-highlight-r"
elif days > goal1:
class_name = "ietf-small ietf-highlight-y"
if flavor == "facelift":
class_name = "label label-warning"
else:
class_name = "ietf-small ietf-highlight-y"
else:
class_name = "ietf-small"
if days > goal1:
title = ' title="Goal is &lt;%d days"' % (goal1,)
else:
title = ''
return mark_safe('<span class="%s"%s>(for&nbsp;%d&nbsp;day%s)</span>' % (
class_name, title, days, 's' if days != 1 else ''))
# It's too bad that this function returns HTML; this makes it hard to
# style. For the facelift, I therefore needed to add a new "flavor"
# parameter, which is ugly.
return mark_safe('<span class="%s"%s>%sfor %d day%s%s</span>' % (
class_name, title, '(' if flavor != "facelift" else "", days,
's' if days != 1 else '', '(' if flavor != "facelift" else "" ))
else:
return ""

View file

@ -8,9 +8,10 @@ from email.utils import parseaddr
from ietf.doc.models import ConsensusDocEvent
from django import template
from django.conf import settings
from django.utils.html import escape, fix_ampersands
from django.utils.text import wrap
from django.template.defaultfilters import truncatewords_html, linebreaksbr, stringfilter, urlize
from django.template.defaultfilters import truncatewords_html, linebreaksbr, stringfilter, striptags, urlize
from django.template import resolve_variable
from django.utils.safestring import mark_safe, SafeData
from django.utils.html import strip_tags
@ -52,12 +53,12 @@ def parse_email_list(value):
u'<a href="mailto:joe@example.org">joe@example.org</a>, <a href="mailto:fred@example.com">fred@example.com</a>'
Parsing a non-string should return the input value, rather than fail:
>>> parse_email_list(['joe@example.org', 'fred@example.com'])
['joe@example.org', 'fred@example.com']
Null input values should pass through silently:
>>> parse_email_list('')
''
@ -91,7 +92,7 @@ def fix_angle_quotes(value):
if "<" in value:
value = re.sub("<([\w\-\.]+@[\w\-\.]+)>", "&lt;\1&gt;", value)
return value
# there's an "ahref -> a href" in GEN_UTIL
# but let's wait until we understand what that's for.
@register.filter(name='make_one_per_line')
@ -103,7 +104,7 @@ def make_one_per_line(value):
'a\\nb\\nc'
Pass through non-strings:
>>> make_one_per_line([1, 2])
[1, 2]
@ -114,7 +115,7 @@ def make_one_per_line(value):
return re.sub(", ?", "\n", value)
else:
return value
@register.filter(name='timesum')
def timesum(value):
"""
@ -203,7 +204,7 @@ def rfcspace(string):
"""
string = str(string)
if string[:3].lower() == "rfc" and string[3] != " ":
return string[:3] + " " + string[3:]
return string[:3].upper() + " " + string[3:]
else:
return string
@ -226,7 +227,7 @@ def rfclink(string):
URL for that RFC.
"""
string = str(string);
return "http://tools.ietf.org/html/rfc" + string;
return "//tools.ietf.org/html/rfc" + string;
@register.filter(name='urlize_ietf_docs', is_safe=True, needs_autoescape=True)
def urlize_ietf_docs(string, autoescape=None):
@ -278,7 +279,7 @@ def truncate_ellipsis(text, arg):
return escape(text[:num-1])+"&hellip;"
else:
return escape(text)
@register.filter
def split(text, splitter=None):
return text.split(splitter)
@ -379,7 +380,7 @@ def linebreaks_lf(text):
@register.filter(name='clean_whitespace')
def clean_whitespace(text):
"""
Map all ASCII control characters (0x00-0x1F) to spaces, and
Map all ASCII control characters (0x00-0x1F) to spaces, and
remove unnecessary spaces.
"""
text = re.sub("[\000-\040]+", " ", text)
@ -388,7 +389,7 @@ def clean_whitespace(text):
@register.filter(name='unescape')
def unescape(text):
"""
Unescape &nbsp;/&gt;/&lt;
Unescape &nbsp;/&gt;/&lt;
"""
text = text.replace("&gt;", ">")
text = text.replace("&lt;", "<")
@ -427,7 +428,7 @@ def has_role(user, role_names):
@register.filter
def stable_dictsort(value, arg):
"""
Like dictsort, except it's stable (preserves the order of items
Like dictsort, except it's stable (preserves the order of items
whose sort key is the same). See also bug report
http://code.djangoproject.com/ticket/12110
"""
@ -459,7 +460,7 @@ def format_snippet(text, trunc_words=25):
full = mark_safe(keep_spacing(collapsebr(linebreaksbr(urlize(sanitize_html(text))))))
snippet = truncatewords_html(full, trunc_words)
if snippet != full:
return mark_safe(u'<div class="snippet">%s<span class="show-all">[show all]</span></div><div style="display:none" class="full">%s</div>' % (snippet, full))
return mark_safe(u'<div class="snippet">%s<button class="btn btn-xs btn-default show-all"><span class="fa fa-caret-down"></span></button></div><div class="hidden full">%s</div>' % (snippet, full))
return full
@register.filter
@ -563,3 +564,62 @@ class WordWrapNode(template.Node):
def render(self, context):
return wrap(str(self.nodelist.render(context)), int(self.len))
# FACELIFT: The following filters are only used by the facelift UI:
@register.filter
def pos_to_label(text):
"""Return a valid Bootstrap3 label type for a ballot position."""
return {
'Yes': 'success',
'No Objection': 'info',
'Abstain': 'warning',
'Discuss': 'danger',
'Block': 'danger',
'Recuse': 'default',
}.get(str(text), 'blank')
@register.filter
def capfirst_allcaps(text):
"""Like capfirst, except it doesn't lowercase words in ALL CAPS."""
result = text
i = False
for token in re.split("(\W+)", striptags(text)):
if not re.match("^[A-Z]+$", token):
if not i:
result = result.replace(token, token.capitalize())
i = True
else:
result = result.replace(token, token.lower())
return result
@register.filter
def lower_allcaps(text):
"""Like lower, except it doesn't lowercase words in ALL CAPS."""
result = text
for token in re.split("(\W+)", striptags(text)):
if not re.match("^[A-Z]+$", token):
result = result.replace(token, token.lower())
return result
# See https://djangosnippets.org/snippets/2072/ and
# https://stackoverflow.com/questions/9939248/how-to-prevent-django-basic-inlines-from-autoescaping
@register.filter
def urlize_html(html, autoescape=False):
"""
Returns urls found in an (X)HTML text node element as urls via Django urlize filter.
"""
try:
from BeautifulSoup import BeautifulSoup
except ImportError:
if settings.DEBUG:
raise template.TemplateSyntaxError, "Error in urlize_html The Python BeautifulSoup libraries aren't installed."
return html
else:
soup = BeautifulSoup(html)
textNodes = soup.findAll(text=True)
for textNode in textNodes:
urlizedText = urlize(textNode, autoescape=autoescape)
textNode.replaceWith(BeautifulSoup(urlizedText))
return str(soup)

View file

@ -43,24 +43,32 @@ area_short_names = {
'rai':'RAI'
}
# FACELIFT: Function is called with "facelift" flavor from the new UI code.
# The old code (and flavoring) can be remove eventually.
@register.simple_tag
def wg_menu():
res = cache.get('base_left_wgmenu')
def wg_menu(flavor=""):
res = cache.get('wgmenu' + flavor)
if res:
return res
areas = Group.objects.filter(type="area", state="active").order_by('acronym')
groups = Group.objects.filter(type="wg", state="active", parent__in=areas).order_by("acronym")
wgs = Group.objects.filter(type="wg", state="active", parent__in=areas).order_by("acronym")
rgs = Group.objects.filter(type="rg", state="active").order_by("acronym")
for a in areas:
a.short_area_name = area_short_names.get(a.acronym) or a.name
if a.short_area_name.endswith(" Area"):
a.short_area_name = a.short_area_name[:-len(" Area")]
a.active_groups = [g for g in groups if g.parent_id == a.id]
a.active_groups = [g for g in wgs if g.parent_id == a.id]
areas = [a for a in areas if a.active_groups]
res = render_to_string('base/wg_menu.html', {'areas':areas})
cache.set('base_left_wgmenu', res, 30*60)
if flavor == "facelift":
res = render_to_string('base/menu_wg.html', {'areas':areas, 'rgs':rgs})
elif flavor == "modal":
res = render_to_string('base/menu_wg_modal.html', {'areas':areas, 'rgs':rgs})
else:
res = render_to_string('base/wg_menu.html', {'areas':areas, 'rgs':rgs})
cache.set('wgmenu' + flavor, res, 30*60)
return res

View file

@ -1,4 +1,5 @@
import datetime
import json
import sys
if sys.version_info[0] == 2 and sys.version_info[1] < 7:
import unittest2 as unittest
@ -97,7 +98,7 @@ class SearchTestCase(TestCase):
make_test_data()
r = self.client.get("/")
self.assertEqual(r.status_code, 200)
self.assertTrue("Search Internet-Drafts" in r.content)
self.assertTrue("Search Documents" in r.content)
def test_drafts_pages(self):
draft = make_test_data()
@ -121,6 +122,32 @@ class SearchTestCase(TestCase):
r = self.client.get(urlreverse("index_active_drafts"))
self.assertEqual(r.status_code, 200)
self.assertTrue(draft.title in r.content)
def test_ajax_search_docs(self):
draft = make_test_data()
# Document
url = urlreverse("ajax_select2_search_docs", kwargs={
"model_name": "document",
"doc_type": "draft",
})
r = self.client.get(url, dict(q=draft.name))
self.assertEqual(r.status_code, 200)
data = json.loads(r.content)
self.assertEqual(data[0]["id"], draft.pk)
# DocAlias
doc_alias = draft.docalias_set.get()
url = urlreverse("ajax_select2_search_docs", kwargs={
"model_name": "docalias",
"doc_type": "draft",
})
r = self.client.get(url, dict(q=doc_alias.name))
self.assertEqual(r.status_code, 200)
data = json.loads(r.content)
self.assertEqual(data[0]["id"], doc_alias.pk)
class DocTestCase(TestCase):
@ -373,14 +400,12 @@ class DocTestCase(TestCase):
self.client.login(username='iab-chair', password='iab-chair+password')
r = self.client.get(urlreverse("doc_view", kwargs=dict(name=doc.name)))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertFalse(q('.actions'))
self.assertTrue("Request publication" not in r.content)
Document.objects.filter(pk=doc.pk).update(stream='iab')
r = self.client.get(urlreverse("doc_view", kwargs=dict(name=doc.name)))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue('IESG state' in q('.actions').html())
self.assertTrue("Request publication" in r.content)
class AddCommentTestCase(TestCase):

View file

@ -174,9 +174,9 @@ class BallotWriteupsTests(TestCase):
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('textarea[name=last_call_text]')), 1)
self.assertEqual(len(q('input[type=submit][value*="Save Last Call"]')), 1)
# we're secretariat, so we got The Link
self.assertEqual(len(q('a:contains("Make Last Call")')), 1)
self.assertTrue(q('[type=submit]:contains("Save")'))
# we're Secretariat, so we got The Link
self.assertEqual(len(q('a:contains("Issue last call")')), 1)
# subject error
r = self.client.post(url, dict(
@ -184,7 +184,7 @@ class BallotWriteupsTests(TestCase):
save_last_call_text="1"))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
# save
r = self.client.post(url, dict(
@ -243,7 +243,7 @@ class BallotWriteupsTests(TestCase):
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('textarea[name=ballot_writeup]')), 1)
self.assertEqual(len(q('input[type=submit][value*="Save Ballot Writeup"]')), 1)
self.assertTrue(q('[type=submit]:contains("Save")'))
self.assertTrue("IANA does not" in r.content)
# save
@ -317,7 +317,7 @@ class BallotWriteupsTests(TestCase):
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('textarea[name=approval_text]')), 1)
self.assertEqual(len(q('input[type=submit][value*="Save Approval"]')), 1)
self.assertTrue(q('[type=submit]:contains("Save")'))
# save
r = self.client.post(url, dict(
@ -365,8 +365,8 @@ class ApproveBallotTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue("send out the announcement" in q('.actions input[type=submit]')[0].get('value').lower())
self.assertEqual(len(q('.announcement pre:contains("Subject: Protocol Action")')), 1)
self.assertTrue(q('[type=submit]:contains("send announcement")'))
self.assertEqual(len(q('form pre:contains("Subject: Protocol Action")')), 1)
# approve
mailbox_before = len(outbox)
@ -466,7 +466,7 @@ class DeferUndeferTestCase(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('form.defer')),1)
self.assertEqual(len(q('[type=submit][value="Defer ballot"]')),1)
# defer
mailbox_before = len(outbox)
@ -521,7 +521,7 @@ class DeferUndeferTestCase(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('form.undefer')),1)
self.assertEqual(len(q('[type=submit][value="Undefer ballot"]')),1)
# undefer
mailbox_before = len(outbox)

View file

@ -71,7 +71,7 @@ class EditCharterTests(TestCase):
r = self.client.post(url, dict(charter_state="-12345"))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
self.assertEqual(charter.get_state(), first_state)
# change state
@ -370,18 +370,16 @@ class EditCharterTests(TestCase):
desc="Has been copied",
due=due_date,
resolved="")
# m2 isn't used -- missing test?
m2 = GroupMilestone.objects.create(group=group, # pyflakes:ignore
state_id="active",
desc="To be deleted",
due=due_date,
resolved="")
# m3 isn't used -- missing test?
m3 = GroupMilestone.objects.create(group=group, # pyflakes:ignore
state_id="charter",
desc="Has been copied",
due=due_date,
resolved="")
GroupMilestone.objects.create(group=group,
state_id="active",
desc="To be deleted",
due=due_date,
resolved="")
GroupMilestone.objects.create(group=group,
state_id="charter",
desc="Has been copied",
due=due_date,
resolved="")
m4 = GroupMilestone.objects.create(group=group,
state_id="charter",
desc="New charter milestone",
@ -392,7 +390,7 @@ class EditCharterTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue("Send out the announcement" in q('input[type=submit]')[0].get('value'))
self.assertTrue(q('[type=submit]:contains("Send announcement")'))
self.assertEqual(len(q('pre')), 1)
# approve

View file

@ -45,13 +45,13 @@ class ConflictReviewTests(TestCase):
r = self.client.post(url,dict(create_in_state=""))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
self.assertEqual(Document.objects.filter(name='conflict-review-imaginary-independent-submission').count() , 0)
r = self.client.post(url,dict(ad=""))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
self.assertEqual(Document.objects.filter(name='conflict-review-imaginary-independent-submission').count() , 0)
# successful review start
@ -139,7 +139,7 @@ class ConflictReviewTests(TestCase):
r = self.client.post(url,dict(review_state=""))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
# successful change to AD Review
adrev_pk = str(State.objects.get(used=True, slug='adrev',type__slug='conflrev').pk)
@ -274,7 +274,7 @@ class ConflictReviewTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('form.approve')),1)
self.assertEqual(len(q('[type=submit]:contains("Send announcement")')), 1)
if approve_type == 'appr-noprob':
self.assertTrue( 'IESG has no problem' in ''.join(wrap(r.content,2**16)))
else:

View file

@ -33,7 +33,7 @@ class ChangeStateTests(TestCase):
login_testing_unauthorized(self, "secretary", url)
first_state = draft.get_state("draft-iesg")
next_states = first_state.next_states
next_states = first_state.next_states.all()
# normal get
r = self.client.get(url)
@ -42,14 +42,14 @@ class ChangeStateTests(TestCase):
self.assertEqual(len(q('form select[name=state]')), 1)
if next_states:
self.assertTrue(len(q('.next-states form input[type=hidden]')) > 0)
self.assertEqual(len(q('[type=submit][value="%s"]' % next_states[0].name)), 1)
# faulty post
r = self.client.post(url, dict(state=State.objects.get(used=True, type="draft", slug="active").pk))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
draft = Document.objects.get(name=draft.name)
self.assertEqual(draft.get_state("draft-iesg"), first_state)
@ -81,7 +81,7 @@ class ChangeStateTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('.prev-state form input[name="state"]')), 1)
self.assertEqual(len(q('form [type=submit][value="%s"]' % first_state.name)), 1)
def test_pull_from_rfc_queue(self):
draft = make_test_data()
@ -127,7 +127,7 @@ class ChangeStateTests(TestCase):
r = self.client.post(url, dict(state="foobarbaz"))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
draft = Document.objects.get(name=draft.name)
self.assertEqual(draft.get_state("draft-iana-review"), first_state)
@ -149,7 +149,7 @@ class ChangeStateTests(TestCase):
self.assertTrue(not draft.latest_event(type="changed_ballot_writeup_text"))
r = self.client.post(url, dict(state=State.objects.get(used=True, type="draft-iesg", slug="lc-req").pk))
self.assertContains(r, "Your request to issue the Last Call")
self.assertTrue("Your request to issue" in r.content)
# last call text
e = draft.latest_event(WriteupDocEvent, type="changed_last_call_text")
@ -195,7 +195,7 @@ class EditInfoTests(TestCase):
r = self.client.post(url, dict(ad="123456789"))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
draft = Document.objects.get(name=draft.name)
self.assertEqual(draft.ad, prev_ad)
@ -689,7 +689,7 @@ class IndividualInfoFormsTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code,200)
q = PyQuery(r.content)
self.assertEqual(len(q('form.change-stream')),1)
self.assertEqual(len(q('[type=submit]:contains("Save")')), 1)
# shift to ISE stream
messages_before = len(outbox)
@ -742,13 +742,13 @@ class IndividualInfoFormsTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code,200)
q = PyQuery(r.content)
self.assertEqual(len(q('form.change-intended-status')),1)
self.assertEqual(len(q('[type=submit]:contains("Save")')), 1)
# don't allow status level to be cleared
r = self.client.post(url,dict(intended_std_level=""))
self.assertEqual(r.status_code,200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
# change intended status level
messages_before = len(outbox)
@ -768,7 +768,7 @@ class IndividualInfoFormsTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code,200)
q = PyQuery(r.content)
self.assertEqual(len(q('form.telechat-date')),1)
self.assertEqual(len(q('[type=submit]:contains("Save")')), 1)
# set a date
self.assertFalse(self.doc.latest_event(TelechatDocEvent, "scheduled_for_telechat"))
@ -791,7 +791,7 @@ class IndividualInfoFormsTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code,200)
q = PyQuery(r.content)
self.assertEqual(len(q('form.edit-iesg-note')),1)
self.assertEqual(len(q('[type=submit]:contains("Save")')),1)
# post
r = self.client.post(url,dict(note='ZpyQFGmA\r\nZpyQFGmA'))
@ -868,7 +868,7 @@ class IndividualInfoFormsTests(TestCase):
r = self.client.post(url, dict(shepherd=two_answers))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
def test_doc_change_shepherd_email(self):
self.doc.shepherd = None
@ -914,15 +914,16 @@ class IndividualInfoFormsTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code,200)
q = PyQuery(r.content)
self.assertEqual(len(q('span[id=doc_edit_shepherd_writeup]')),1)
self.assertEqual(len(q('.content-wrapper a:contains("Edit")')), 1)
# Try again when no longer a shepherd.
self.doc.shepherd = None
self.doc.save()
r = self.client.get(url)
self.assertEqual(r.status_code,200)
q = PyQuery(r.content)
self.assertEqual(len(q('span[id=doc_edit_shepherd_writeup]')),1)
self.assertEqual(len(q('.content-wrapper a:contains("Edit")')), 0)
def test_doc_change_shepherd_writeup(self):
url = urlreverse('doc_edit_shepherd_writeup',kwargs=dict(name=self.docname))
@ -1037,7 +1038,7 @@ class RequestPublicationTests(TestCase):
q = PyQuery(r.content)
subject = q('input#id_subject')[0].get("value")
self.assertTrue("Document Action" in subject)
body = q('.request-publication #id_body').text()
body = q('#id_body').text()
self.assertTrue("Informational" in body)
self.assertTrue("IAB" in body)
@ -1244,38 +1245,34 @@ class ChangeReplacesTests(TestCase):
# normal get
r = self.client.get(url)
self.assertEquals(r.status_code, 200)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEquals(len(q('form[class=change-replaces]')), 1)
self.assertEqual(len(q('[type=submit]:contains("Save")')), 1)
# Post that says replacea replaces base a
self.assertEquals(self.basea.get_state().slug,'active')
repljson='{"%d":"%s"}'%(DocAlias.objects.get(name=self.basea.name).id,self.basea.name)
r = self.client.post(url, dict(replaces=repljson))
self.assertEquals(r.status_code, 302)
self.assertEqual(self.basea.get_state().slug,'active')
r = self.client.post(url, dict(replaces=str(DocAlias.objects.get(name=self.basea.name).id)))
self.assertEqual(r.status_code, 302)
self.assertEqual(RelatedDocument.objects.filter(relationship__slug='replaces',source=self.replacea).count(),1)
self.assertEquals(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
# Post that says replaceboth replaces both base a and base b
url = urlreverse('doc_change_replaces', kwargs=dict(name=self.replaceboth.name))
self.assertEquals(self.baseb.get_state().slug,'expired')
repljson='{"%d":"%s","%d":"%s"}'%(DocAlias.objects.get(name=self.basea.name).id,self.basea.name,
DocAlias.objects.get(name=self.baseb.name).id,self.baseb.name)
r = self.client.post(url, dict(replaces=repljson))
self.assertEquals(r.status_code, 302)
self.assertEquals(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
self.assertEquals(Document.objects.get(name='draft-test-base-b').get_state().slug,'repl')
self.assertEqual(self.baseb.get_state().slug,'expired')
r = self.client.post(url, dict(replaces=str(DocAlias.objects.get(name=self.basea.name).id) + "," + str(DocAlias.objects.get(name=self.baseb.name).id)))
self.assertEqual(r.status_code, 302)
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'repl')
# Post that undoes replaceboth
repljson='{}'
r = self.client.post(url, dict(replaces=repljson))
self.assertEquals(r.status_code, 302)
self.assertEquals(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl') # Because A is still also replaced by replacea
self.assertEquals(Document.objects.get(name='draft-test-base-b').get_state().slug,'expired')
r = self.client.post(url, dict(replaces=""))
self.assertEqual(r.status_code, 302)
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl') # Because A is still also replaced by replacea
self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'expired')
# Post that undoes replacea
url = urlreverse('doc_change_replaces', kwargs=dict(name=self.replacea.name))
r = self.client.post(url, dict(replaces=repljson))
self.assertEquals(r.status_code, 302)
self.assertEquals(Document.objects.get(name='draft-test-base-a').get_state().slug,'active')
r = self.client.post(url, dict(replaces=""))
self.assertEqual(r.status_code, 302)
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'active')

View file

@ -41,25 +41,25 @@ class StatusChangeTests(TestCase):
r = self.client.post(url,dict(document_name="bogus",title="Bogus Title",ad="",create_in_state=state_strpk,notify='ipu@ietf.org'))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
## Must set a name
r = self.client.post(url,dict(document_name="",title="Bogus Title",ad=ad_strpk,create_in_state=state_strpk,notify='ipu@ietf.org'))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
## Must not choose a document name that already exists
r = self.client.post(url,dict(document_name="imaginary-mid-review",title="Bogus Title",ad=ad_strpk,create_in_state=state_strpk,notify='ipu@ietf.org'))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
## Must set a title
r = self.client.post(url,dict(document_name="bogus",title="",ad=ad_strpk,create_in_state=state_strpk,notify='ipu@ietf.org'))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
# successful status change start
r = self.client.post(url,dict(document_name="imaginary-new",title="A new imaginary status change",ad=ad_strpk,
@ -90,7 +90,7 @@ class StatusChangeTests(TestCase):
r = self.client.post(url,dict(new_state=""))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
# successful change to AD Review
adrev_pk = str(State.objects.get(slug='adrev',type__slug='statchg').pk)
@ -283,7 +283,7 @@ class StatusChangeTests(TestCase):
messages_before = len(outbox)
r = self.client.post(url,dict(last_call_text='stuff',send_last_call_request='Save+and+Request+Last+Call'))
self.assertEqual(r.status_code,200)
self.assertTrue( 'Last Call Requested' in ''.join(wrap(r.content,2**16)))
self.assertTrue( 'Last call requested' in ''.join(wrap(r.content,2**16)))
self.assertEqual(len(outbox), messages_before + 1)
self.assertTrue('iesg-secretary' in outbox[-1]['To'])
self.assertTrue('Last Call:' in outbox[-1]['Subject'])
@ -307,7 +307,7 @@ class StatusChangeTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('form.approve')),1)
self.assertEqual(len(q('[type=submit]:contains("Send announcement")')), 1)
# There should be two messages to edit
self.assertEqual(q('input#id_form-TOTAL_FORMS').val(),'2')
self.assertTrue( '(rfc9999) to Internet Standard' in ''.join(wrap(r.content,2**16)))
@ -345,30 +345,27 @@ class StatusChangeTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('form.edit-status-change-rfcs')),1)
self.assertEqual(len(q('.content-wrapper [type=submit]:contains("Save")')),1)
# There should be three rows on the form
self.assertEqual(len(q('tr[id^=relation_row]')),3)
self.assertEqual(len(q('.content-wrapper .row')),3)
# Try to add a relation to an RFC that doesn't exist
r = self.client.post(url,dict(new_relation_row_blah="rfc9997",
statchg_relation_row_blah="tois",
Submit="Submit"))
statchg_relation_row_blah="tois"))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
# Try to add a relation leaving the relation type blank
r = self.client.post(url,dict(new_relation_row_blah="rfc9999",
statchg_relation_row_blah="",
Submit="Submit"))
statchg_relation_row_blah=""))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
# Try to add a relation with an unknown relationship type
r = self.client.post(url,dict(new_relation_row_blah="rfc9999",
statchg_relation_row_blah="badslug",
Submit="Submit"))
statchg_relation_row_blah="badslug"))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
@ -379,8 +376,7 @@ class StatusChangeTests(TestCase):
new_relation_row_foo="rfc9998",
statchg_relation_row_foo="tobcp",
new_relation_row_nob="rfc14",
statchg_relation_row_nob="tohist",
Submit="Submit"))
statchg_relation_row_nob="tohist"))
self.assertEqual(r.status_code, 302)
doc = Document.objects.get(name='status-change-imaginary-mid-review')
self.assertEqual(doc.relateddocument_set.count(),3)

View file

@ -49,6 +49,7 @@ urlpatterns = patterns('',
url(r'^all/$', views_search.index_all_drafts, name="index_all_drafts"),
url(r'^active/$', views_search.index_active_drafts, name="index_active_drafts"),
url(r'^select2search/(?P<model_name>(document|docalias))/(?P<doc_type>draft)/$', views_search.ajax_select2_search_docs, name="ajax_select2_search_docs"),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/(?:(?P<rev>[0-9-]+)/)?$', views_doc.document_main, name="doc_view"),
url(r'^(?P<name>[A-Za-z0-9._+-]+)/history/$', views_doc.document_history, name="doc_history"),

View file

@ -116,7 +116,7 @@ def edit_position(request, name, ballot_id):
if has_role(request.user, "Secretariat"):
ad_id = request.GET.get('ad')
if not ad_id:
raise Http404()
raise Http404
ad = get_object_or_404(Person, pk=ad_id)
old_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, ballot=ballot)
@ -251,12 +251,12 @@ def send_ballot_comment(request, name, ballot_id):
if not has_role(request.user, "Area Director"):
ad_id = request.GET.get('ad')
if not ad_id:
raise Http404()
raise Http404
ad = get_object_or_404(Person, pk=ad_id)
pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, ballot=ballot)
if not pos:
raise Http404()
raise Http404
subj = []
d = ""
@ -330,11 +330,11 @@ def defer_ballot(request, name):
"""Signal post-pone of ballot, notifying relevant parties."""
doc = get_object_or_404(Document, docalias__name=name)
if doc.type_id not in ('draft','conflrev','statchg'):
raise Http404()
raise Http404
interesting_state = dict(draft='draft-iesg',conflrev='conflrev',statchg='statchg')
state = doc.get_state(interesting_state[doc.type_id])
if not state or state.slug=='defer' or not doc.telechat_date():
raise Http404()
raise Http404
login = request.user.person
telechat_date = TelechatDate.objects.active().order_by("date")[1].date
@ -380,13 +380,13 @@ def undefer_ballot(request, name):
"""undo deferral of ballot ballot."""
doc = get_object_or_404(Document, docalias__name=name)
if doc.type_id not in ('draft','conflrev','statchg'):
raise Http404()
raise Http404
if doc.type_id == 'draft' and not doc.get_state("draft-iesg"):
raise Http404()
raise Http404
interesting_state = dict(draft='draft-iesg',conflrev='conflrev',statchg='statchg')
state = doc.get_state(interesting_state[doc.type_id])
if not state or state.slug!='defer':
raise Http404()
raise Http404
telechat_date = TelechatDate.objects.active().order_by("date")[0].date
@ -417,7 +417,7 @@ def lastcalltext(request, name):
"""Editing of the last call text"""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.get_state("draft-iesg"):
raise Http404()
raise Http404
login = request.user.person
@ -581,7 +581,7 @@ def ballot_approvaltext(request, name):
"""Editing of approval text"""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.get_state("draft-iesg"):
raise Http404()
raise Http404
login = request.user.person
@ -629,7 +629,7 @@ def approve_ballot(request, name):
"""Approve ballot, sending out announcement, changing state."""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.get_state("draft-iesg"):
raise Http404()
raise Http404
login = request.user.person

View file

@ -13,7 +13,7 @@ from django.contrib.auth.decorators import login_required
import debug # pyflakes:ignore
from ietf.doc.models import ( Document, DocHistory, State, DocEvent, BallotDocEvent,
BallotPositionDocEvent, InitialReviewDocEvent, NewRevisionDocEvent,
BallotPositionDocEvent, InitialReviewDocEvent, NewRevisionDocEvent,
WriteupDocEvent, save_document_in_history )
from ietf.doc.utils import ( add_state_change_event, close_open_ballots,
create_ballot_if_not_open, get_chartering_type )
@ -33,7 +33,7 @@ from ietf.group.mails import email_iesg_secretary_re_charter
class ChangeStateForm(forms.Form):
charter_state = forms.ModelChoiceField(State.objects.filter(used=True, type="charter"), label="Charter state", empty_label=None, required=False)
initial_time = forms.IntegerField(initial=0, label="Review time", help_text="(in weeks)", required=False)
message = forms.CharField(widget=forms.Textarea, help_text="Leave blank to change state without notifying the Secretariat", required=False, label=mark_safe("Message to<br> Secretariat"))
message = forms.CharField(widget=forms.Textarea, help_text="Leave blank to change state without notifying the Secretariat", required=False, label=mark_safe("Message to the Secretariat"))
comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the charter history", required=False)
def __init__(self, *args, **kwargs):
self.hide = kwargs.pop('hide', None)
@ -47,7 +47,7 @@ class ChangeStateForm(forms.Form):
# hide requested fields
if self.hide:
for f in self.hide:
self.fields[f].widget = forms.HiddenInput
self.fields[f].widget = forms.HiddenInput()
@login_required
def change_state(request, name, option=None):
@ -101,7 +101,7 @@ def change_state(request, name, option=None):
e.state_id = group.state.slug
e.desc = "Group state changed to %s from %s" % (group.state, oldstate)
e.save()
else:
charter_state = State.objects.get(used=True, type="charter", slug="approved")
charter_rev = approved_revision(charter.rev)
@ -382,12 +382,12 @@ def submit(request, name=None, option=None):
e.desc = "New version available: <b>%s-%s.txt</b>" % (charter.canonical_name(), charter.rev)
e.rev = charter.rev
e.save()
# Save file on disk
form.save(group, charter.rev)
if option in ['initcharter','recharter'] and charter.ad == None:
charter.ad = group.ad
charter.ad = group.ad
charter.time = datetime.datetime.now()
charter.save()
@ -460,7 +460,7 @@ def announcement_text(request, name, ann):
e.desc = "%s %s text was changed" % (group.type.name, ann)
e.text = t
e.save()
charter.time = e.time
charter.save()
@ -495,7 +495,7 @@ class BallotWriteupForm(forms.Form):
def clean_ballot_writeup(self):
return self.cleaned_data["ballot_writeup"].replace("\r", "")
@role_required('Area Director','Secretariat')
def ballot_writeupnotes(request, name):
"""Editing of ballot write-up and notes"""
@ -508,13 +508,13 @@ def ballot_writeupnotes(request, name):
login = request.user.person
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 "send_ballot" in request.POST):
@ -699,7 +699,7 @@ def approve(request, name):
send_mail_preformatted(request, announcement)
return HttpResponseRedirect(charter.get_absolute_url())
return render_to_response('doc/charter/approve.html',
dict(charter=charter,
announcement=announcement),

View file

@ -33,7 +33,7 @@
import os, datetime, urllib, json, glob
from django.http import HttpResponse, Http404
from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.shortcuts import render_to_response, get_object_or_404, redirect, render
from django.template import RequestContext
from django.template.loader import render_to_string
from django.core.exceptions import ObjectDoesNotExist
@ -869,20 +869,17 @@ def telechat_date(request, name):
e = doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
initial_returning_item = bool(e and e.returning_item)
prompts = []
warnings = []
if e and e.telechat_date and doc.type.slug != 'charter':
if e.telechat_date==datetime.date.today():
prompts.append( "This document is currently scheduled for today's telechat. "
+"Please set the returning item bit carefully.")
warnings.append( "This document is currently scheduled for today's telechat. "
+"Please set the returning item bit carefully.")
elif e.telechat_date<datetime.date.today() and has_same_ballot(doc,e.telechat_date):
initial_returning_item = True
prompts.append( "This document appears to have been on a previous telechat with the same ballot, "
warnings.append( "This document appears to have been on a previous telechat with the same ballot, "
+"so the returning item bit has been set. Clear it if that is not appropriate.")
else:
pass
initial = dict(telechat_date=e.telechat_date if e else None,
returning_item = initial_returning_item,
)
@ -901,13 +898,12 @@ def telechat_date(request, name):
if doc.type.slug=='charter':
del form.fields['returning_item']
return render_to_response('doc/edit_telechat_date.html',
return render(request, 'doc/edit_telechat_date.html',
dict(doc=doc,
form=form,
user=request.user,
prompts=prompts,
login=login),
context_instance=RequestContext(request))
warnings=warnings,
login=login))
@role_required('Area Director', 'Secretariat')
def edit_notify(request, name):

View file

@ -1,6 +1,6 @@
# changing state and metadata on Internet Drafts
import datetime, json
import datetime
from django import forms
from django.http import HttpResponseRedirect, HttpResponseForbidden, Http404
@ -24,13 +24,14 @@ from ietf.doc.utils import ( add_state_change_event, can_adopt_draft,
get_tags_for_stream_id, nice_consensus,
update_reminder, update_telechat, make_notify_changed_event, get_initial_notify )
from ietf.doc.lastcall import request_last_call
from ietf.doc.fields import SearchableDocAliasesField
from ietf.group.models import Group, Role
from ietf.iesg.models import TelechatDate
from ietf.ietfauth.utils import has_role, is_authorized_in_doc_stream, user_is_person
from ietf.ietfauth.utils import role_required
from ietf.message.models import Message
from ietf.name.models import IntendedStdLevelName, DocTagName, StreamName
from ietf.person.fields import AutocompletedEmailField
from ietf.person.fields import SearchableEmailField
from ietf.person.models import Person, Email
from ietf.secr.lib.template import jsonapi
from ietf.utils.mail import send_mail, send_mail_message
@ -64,7 +65,7 @@ def change_state(request, name):
and logging the change as a comment."""
doc = get_object_or_404(Document, docalias__name=name)
if (not doc.latest_event(type="started_iesg_process")) or doc.get_state_slug() == "expired":
raise Http404()
raise Http404
login = request.user.person
@ -214,7 +215,7 @@ def change_stream(request, name):
and logging the change as a comment."""
doc = get_object_or_404(Document, docalias__name=name)
if not doc.type_id=='draft':
raise Http404()
raise Http404
if not (has_role(request.user, ("Area Director", "Secretariat")) or
(request.user.is_authenticated() and
@ -307,35 +308,21 @@ def collect_email_addresses(emails, doc):
return emails
class ReplacesForm(forms.Form):
replaces = forms.CharField(max_length=512,widget=forms.HiddenInput)
replaces = SearchableDocAliasesField(required=False)
comment = forms.CharField(widget=forms.Textarea, required=False)
def __init__(self, *args, **kwargs):
self.doc = kwargs.pop('doc')
super(ReplacesForm, self).__init__(*args, **kwargs)
drafts = {}
for d in self.doc.related_that_doc("replaces"):
drafts[d.id] = d.document.name
self.initial['replaces'] = json.dumps(drafts)
self.initial['replaces'] = self.doc.related_that_doc("replaces")
def clean_replaces(self):
data = self.cleaned_data['replaces'].strip()
if data:
ids = [int(x) for x in json.loads(data)]
else:
return []
objects = []
for id in ids:
try:
d = DocAlias.objects.get(pk=id)
except DocAlias.DoesNotExist:
raise forms.ValidationError("ERROR: %s not found for id %d" % DocAlias._meta.verbos_name, id)
for d in self.cleaned_data['replaces']:
if d.document == self.doc:
raise forms.ValidationError("ERROR: A draft can't replace itself")
raise forms.ValidationError("A draft can't replace itself")
if d.document.type_id == "draft" and d.document.get_state_slug() == "rfc":
raise forms.ValidationError("ERROR: A draft can't replace an RFC")
objects.append(d)
return objects
raise forms.ValidationError("A draft can't replace an RFC")
return self.cleaned_data['replaces']
def replaces(request, name):
"""Change 'replaces' set of a Document of type 'draft' , notifying parties
@ -505,10 +492,10 @@ def to_iesg(request,name):
doc = get_object_or_404(Document, docalias__name=name, stream='ietf')
if doc.get_state_slug('draft') == "expired" or doc.get_state_slug('draft-iesg') == 'pub-req' :
raise Http404()
raise Http404
if not is_authorized_in_doc_stream(request.user, doc):
raise Http404()
raise Http404
target_state={
'iesg' : State.objects.get(type='draft-iesg',slug='pub-req'),
@ -614,7 +601,7 @@ def edit_info(request, name):
necessary and logging changes as document events."""
doc = get_object_or_404(Document, docalias__name=name)
if doc.get_state_slug() == "expired":
raise Http404()
raise Http404
login = request.user.person
@ -764,7 +751,7 @@ def request_resurrect(request, name):
"""Request resurrect of expired Internet Draft."""
doc = get_object_or_404(Document, docalias__name=name)
if doc.get_state_slug() != "expired":
raise Http404()
raise Http404
login = request.user.person
@ -788,7 +775,7 @@ def resurrect(request, name):
"""Resurrect expired Internet Draft."""
doc = get_object_or_404(Document, docalias__name=name)
if doc.get_state_slug() != "expired":
raise Http404()
raise Http404
login = request.user.person
@ -942,7 +929,7 @@ def edit_shepherd_writeup(request, name):
context_instance=RequestContext(request))
class ShepherdForm(forms.Form):
shepherd = AutocompletedEmailField(required=False, only_users=True)
shepherd = SearchableEmailField(required=False, only_users=True)
def edit_shepherd(request, name):
"""Change the shepherd for a Document"""

View file

@ -37,7 +37,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import render_to_response
from django.db.models import Q
from django.template import RequestContext
from django.http import Http404, HttpResponseBadRequest
from django.http import Http404, HttpResponseBadRequest, HttpResponse
import debug # pyflakes:ignore
@ -45,6 +45,7 @@ from ietf.community.models import CommunityList
from ietf.doc.models import ( Document, DocAlias, State, RelatedDocument, DocEvent,
LastCallDocEvent, TelechatDocEvent, IESG_SUBSTATE_TAGS )
from ietf.doc.expire import expirable_draft
from ietf.doc.fields import select2_id_doc_name_json
from ietf.group.models import Group
from ietf.idindex.index import active_drafts_index_by_group
from ietf.ipr.models import IprDocAlias
@ -52,6 +53,7 @@ from ietf.name.models import DocTagName, DocTypeName, StreamName
from ietf.person.models import Person
from ietf.utils.draft_search import normalize_draftname
class SearchForm(forms.Form):
name = forms.CharField(required=False)
rfcs = forms.BooleanField(required=False, initial=True)
@ -148,7 +150,7 @@ def fill_in_search_attributes(docs):
for d in docs:
if isinstance(d,DocAlias):
d = d.document
rel_this_doc = d.all_related_that_doc(['replaces','obs'])
rel_this_doc = d.all_related_that_doc(['replaces','obs'])
for rel in rel_this_doc:
rel_id_camefrom.setdefault(rel.document.pk,[]).append(d.pk)
rel_docs += [x.document for x in rel_this_doc]
@ -240,7 +242,7 @@ def retrieve_search_results(form, all_types=False):
"""Takes a validated SearchForm and return the results."""
if not form.is_valid():
raise ValueError("SearchForm doesn't validate: %s" % form.errors)
query = form.cleaned_data
types=[];
@ -282,7 +284,7 @@ def retrieve_search_results(form, all_types=False):
if query["olddrafts"]:
allowed_draft_states.extend(['repl', 'expired', 'auth-rm', 'ietf-rm'])
docs = docs.filter(Q(states__slug__in=allowed_draft_states) |
docs = docs.filter(Q(states__slug__in=allowed_draft_states) |
~Q(type__slug='draft')).distinct()
# radio choices
@ -366,7 +368,7 @@ def retrieve_search_results(form, all_types=False):
{'title': 'Document', 'key':'document'},
{'title': 'Title', 'key':'title'},
{'title': 'Date', 'key':'date'},
{'title': 'Status', 'key':'status', 'colspan':'2'},
{'title': 'Status', 'key':'status'},
{'title': 'IPR', 'key':'ipr'},
{'title': 'AD / Shepherd', 'key':'ad'}]
@ -441,14 +443,14 @@ def ad_dashboard_group(doc):
return '%s Internet-Draft' % doc.get_state('draft').name
elif doc.type.slug=='conflrev':
if doc.get_state_slug('conflrev') in ('appr-reqnopub-sent','appr-noprob-sent'):
return 'Approved Conflict Review'
return 'Approved Conflict Review'
elif doc.get_state_slug('conflrev') in ('appr-reqnopub-pend','appr-noprob-pend','appr-reqnopub-pr','appr-noprob-pr'):
return "%s Conflict Review" % State.objects.get(type__slug='draft-iesg',slug='approved')
else:
return '%s Conflict Review' % doc.get_state('conflrev')
elif doc.type.slug=='statchg':
if doc.get_state_slug('statchg') in ('appr-sent',):
return 'Approved Status Change'
return 'Approved Status Change'
if doc.get_state_slug('statchg') in ('appr-pend','appr-pr'):
return '%s Status Change' % State.objects.get(type__slug='draft-iesg',slug='approved')
else:
@ -462,7 +464,7 @@ def ad_dashboard_group(doc):
return "Document"
def ad_dashboard_sort_key(doc):
if doc.type.slug=='draft' and doc.get_state_slug('draft') == 'rfc':
return "21%04d" % int(doc.rfc_number())
if doc.type.slug=='statchg' and doc.get_state_slug('statchg') == 'appr-sent':
@ -475,26 +477,26 @@ def ad_dashboard_sort_key(doc):
seed = ad_dashboard_group(doc)
if doc.type.slug=='conflrev' and doc.get_state_slug('conflrev') == 'adrev':
state = State.objects.get(type__slug='draft-iesg',slug='ad-eval')
state = State.objects.get(type__slug='draft-iesg',slug='ad-eval')
return "1%d%s" % (state.order,seed)
if doc.type.slug=='charter':
if doc.get_state_slug('charter') in ('notrev','infrev'):
return "100%s" % seed
elif doc.get_state_slug('charter') == 'intrev':
state = State.objects.get(type__slug='draft-iesg',slug='ad-eval')
state = State.objects.get(type__slug='draft-iesg',slug='ad-eval')
return "1%d%s" % (state.order,seed)
elif doc.get_state_slug('charter') == 'extrev':
state = State.objects.get(type__slug='draft-iesg',slug='lc')
state = State.objects.get(type__slug='draft-iesg',slug='lc')
return "1%d%s" % (state.order,seed)
elif doc.get_state_slug('charter') == 'iesgrev':
state = State.objects.get(type__slug='draft-iesg',slug='iesg-eva')
state = State.objects.get(type__slug='draft-iesg',slug='iesg-eva')
return "1%d%s" % (state.order,seed)
if doc.type.slug=='statchg' and doc.get_state_slug('statchg') == 'adrev':
state = State.objects.get(type__slug='draft-iesg',slug='ad-eval')
state = State.objects.get(type__slug='draft-iesg',slug='ad-eval')
return "1%d%s" % (state.order,seed)
if seed.startswith('Needs Shepherd'):
return "100%s" % seed
if seed.endswith(' Document'):
@ -627,3 +629,28 @@ def index_active_drafts(request):
groups = active_drafts_index_by_group()
return render_to_response("doc/index_active_drafts.html", { 'groups': groups }, context_instance=RequestContext(request))
def ajax_select2_search_docs(request, model_name, doc_type):
if model_name == "docalias":
model = DocAlias
else:
model = Document
q = [w.strip() for w in request.GET.get('q', '').split() if w.strip()]
if not q:
objs = model.objects.none()
else:
qs = model.objects.all()
if model == Document:
qs = qs.filter(type=doc_type)
elif model == DocAlias:
qs = qs.filter(document__type=doc_type)
for t in q:
qs = qs.filter(name__icontains=t)
objs = qs.distinct().order_by("name")[:20]
return HttpResponse(select2_id_doc_name_json(objs), content_type='application/json')

View file

@ -399,11 +399,11 @@ def clean_helper(form, formtype):
elif k.startswith('statchg_relation_row'):
status_fields[k[21:]]=v
for key in rfc_fields:
if rfc_fields[key]!="":
if key in status_fields:
new_relations[rfc_fields[key]]=status_fields[key]
else:
new_relations[rfc_fields[key]]=None
if rfc_fields[key]!="":
if key in status_fields:
new_relations[rfc_fields[key]]=status_fields[key]
else:
new_relations[rfc_fields[key]]=None
form.relations = new_relations
@ -568,7 +568,7 @@ def edit_relations(request, name):
if request.method == 'POST':
form = EditStatusChangeForm(request.POST)
if 'Submit' in request.POST and form.is_valid():
if form.is_valid():
old_relations={}
for rel in status_change.relateddocument_set.filter(relationship__slug__in=STATUSCHANGE_RELATIONS):
@ -590,9 +590,6 @@ def edit_relations(request, name):
return HttpResponseRedirect(status_change.get_absolute_url())
elif 'Cancel' in request.POST:
return HttpResponseRedirect(status_change.get_absolute_url())
else:
relations={}
for rel in status_change.relateddocument_set.filter(relationship__slug__in=STATUSCHANGE_RELATIONS):

View file

@ -19,9 +19,10 @@ from ietf.group.models import ( Group, Role, GroupEvent, GroupHistory, GroupStat
from ietf.group.utils import save_group_in_history, can_manage_group_type
from ietf.group.utils import get_group_or_404
from ietf.ietfauth.utils import has_role
from ietf.person.fields import AutocompletedEmailsField
from ietf.person.fields import SearchableEmailsField
from ietf.person.models import Person, Email
from ietf.group.mails import email_iesg_secretary_re_charter, email_iesg_secretary_personnel_change
from ietf.utils.ordereddict import insert_after_in_ordered_dict
MAX_GROUP_DELEGATES = 3
@ -29,21 +30,20 @@ class GroupForm(forms.Form):
name = forms.CharField(max_length=255, label="Name", required=True)
acronym = forms.CharField(max_length=10, label="Acronym", required=True)
state = forms.ModelChoiceField(GroupStateName.objects.all(), label="State", required=True)
chairs = AutocompletedEmailsField(required=False, only_users=True)
secretaries = AutocompletedEmailsField(required=False, only_users=True)
techadv = AutocompletedEmailsField(label="Technical Advisors", required=False, only_users=True)
delegates = AutocompletedEmailsField(required=False, only_users=True, max_entries=MAX_GROUP_DELEGATES,
help_text=mark_safe("Chairs can delegate the authority to update the state of group documents - max %s persons at a given time" % MAX_GROUP_DELEGATES))
chairs = SearchableEmailsField(label="Chairs", required=False, only_users=True)
secretaries = SearchableEmailsField(label="Secretarias", required=False, only_users=True)
techadv = SearchableEmailsField(label="Technical Advisors", required=False, only_users=True)
delegates = SearchableEmailsField(label="Delegates", required=False, only_users=True, max_entries=MAX_GROUP_DELEGATES,
help_text=mark_safe("Chairs can delegate the authority to update the state of group documents - at most %s persons at a given time." % MAX_GROUP_DELEGATES))
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active").order_by('name'), label="Shepherding AD", empty_label="(None)", required=False)
parent = forms.ModelChoiceField(Group.objects.filter(state="active").order_by('name'), empty_label="(None)", required=False)
list_email = forms.CharField(max_length=64, required=False)
list_subscribe = forms.CharField(max_length=255, required=False)
list_archive = forms.CharField(max_length=255, required=False)
urls = forms.CharField(widget=forms.Textarea, label="Additional URLs", help_text="Format: http://site/path (Optional description). Separate multiple entries with newline.", required=False)
urls = forms.CharField(widget=forms.Textarea, label="Additional URLs", help_text="Format: https://site/path (Optional description). Separate multiple entries with newline. Prefer HTTPS URLs where possible.", required=False)
def __init__(self, *args, **kwargs):
self.group = kwargs.pop('group', None)
self.confirmed = kwargs.pop('confirmed', False)
self.group_type = kwargs.pop('group_type', False)
super(self.__class__, self).__init__(*args, **kwargs)
@ -57,10 +57,8 @@ class GroupForm(forms.Form):
if ad_pk and ad_pk not in [pk for pk, name in choices]:
self.fields['ad'].choices = list(choices) + [("", "-------"), (ad_pk, Person.objects.get(pk=ad_pk).plain_name())]
self.confirm_msg = ""
self.autoenable_confirm = False
if self.group:
self.fields['acronym'].widget.attrs['readonly'] = True
self.fields['acronym'].widget.attrs['readonly'] = ""
if self.group_type == "rg":
self.fields['ad'].widget = forms.HiddenInput()
@ -71,9 +69,6 @@ class GroupForm(forms.Form):
self.fields['parent'].label = "IETF Area"
def clean_acronym(self):
self.confirm_msg = ""
self.autoenable_confirm = False
# Changing the acronym of an already existing group will cause 404s all
# over the place, loose history, and generally muck up a lot of
# things, so we don't permit it
@ -90,27 +85,41 @@ class GroupForm(forms.Form):
if existing:
existing = existing[0]
if existing and existing.type_id == self.group_type:
if self.confirmed:
return acronym # take over confirmed
confirmed = self.data.get("confirm_acronym", False)
def insert_confirm_field(label, initial):
# set required to false, we don't need it since we do the
# validation of the field in here, and otherwise the
# browser and Django may barf
insert_after_in_ordered_dict(self.fields, "confirm_acronym", forms.BooleanField(label=label, required=False), after="acronym")
# we can't set initial, it's ignored since the form is bound, instead mutate the data
self.data = self.data.copy()
self.data["confirm_acronym"] = initial
if existing and existing.type_id == self.group_type:
if existing.state_id == "bof":
self.confirm_msg = "Turn BoF %s into proposed %s and start chartering it" % (existing.acronym, existing.type.name)
self.autoenable_confirm = True
raise forms.ValidationError("Warning: Acronym used for an existing BoF (%s)." % existing.name)
insert_confirm_field(label="Turn BoF %s into proposed %s and start chartering it" % (existing.acronym, existing.type.name), initial=True)
if confirmed:
return acronym
else:
raise forms.ValidationError("Warning: Acronym used for an existing BoF (%s)." % existing.name)
else:
self.confirm_msg = "Set state of %s %s to proposed and start chartering it" % (existing.acronym, existing.type.name)
self.autoenable_confirm = False
raise forms.ValidationError("Warning: Acronym used for an existing %s (%s, %s)." % (existing.type.name, existing.name, existing.state.name if existing.state else "unknown state"))
insert_confirm_field(label="Set state of %s %s to proposed and start chartering it" % (existing.acronym, existing.type.name), initial=False)
if confirmed:
return acronym
else:
raise forms.ValidationError("Warning: Acronym used for an existing %s (%s, %s)." % (existing.type.name, existing.name, existing.state.name if existing.state else "unknown state"))
if existing:
raise forms.ValidationError("Acronym used for an existing group (%s)." % existing.name)
old = GroupHistory.objects.filter(acronym__iexact=acronym, type__in=("wg", "rg"))
if old and not self.confirmed:
self.confirm_msg = "Confirm reusing acronym %s" % old[0].acronym
self.autoenable_confirm = False
raise forms.ValidationError("Warning: Acronym used for a historic group.")
if old:
insert_confirm_field(label="Confirm reusing acronym %s" % old[0].acronym, initial=False)
if confirmed:
return acronym
else:
raise forms.ValidationError("Warning: Acronym used for a historic group.")
return acronym
@ -149,7 +158,7 @@ def get_or_create_initial_charter(group, group_type):
)
charter.save()
charter.set_state(State.objects.get(used=True, type="charter", slug="notrev"))
# Create an alias as well
DocAlias.objects.create(name=charter.name, document=charter)
@ -190,7 +199,7 @@ def edit(request, group_type=None, acronym=None, action="edit"):
group_type = group.type_id
if request.method == 'POST':
form = GroupForm(request.POST, group=group, confirmed=request.POST.get("confirmed", False), group_type=group_type)
form = GroupForm(request.POST, group=group, group_type=group_type)
if form.is_valid():
clean = form.cleaned_data
if new_group:
@ -220,12 +229,12 @@ def edit(request, group_type=None, acronym=None, action="edit"):
group.charter = get_or_create_initial_charter(group, group_type)
changes = []
def desc(attr, new, old):
entry = "%(attr)s changed to <b>%(new)s</b> from %(old)s"
if new_group:
entry = "%(attr)s changed to <b>%(new)s</b>"
return entry % dict(attr=attr, new=new, old=old)
def diff(attr, name):

View file

@ -304,7 +304,7 @@ def construct_group_menu_context(request, group, selected, group_type, others):
if group.features.has_milestones:
if group.state_id != "proposed" and (is_chair or can_manage):
actions.append((u"Add or edit milestones", urlreverse("group_edit_milestones", kwargs=kwargs)))
actions.append((u"Edit milestones", urlreverse("group_edit_milestones", kwargs=kwargs)))
if group.features.has_materials and can_manage_materials(request.user, group):
actions.append((u"Upload material", urlreverse("ietf.doc.views_material.choose_material_type", kwargs=kwargs)))

View file

@ -2,102 +2,72 @@
import datetime
import calendar
import json
from django import forms
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest, HttpResponseRedirect, Http404
from django.http import HttpResponseForbidden, HttpResponseBadRequest, HttpResponseRedirect, Http404
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from ietf.doc.models import Document, DocEvent
from ietf.doc.models import DocEvent
from ietf.doc.utils import get_chartering_type
from ietf.doc.fields import SearchableDocumentsField
from ietf.group.models import GroupMilestone, MilestoneGroupEvent
from ietf.group.utils import (save_milestone_in_history, can_manage_group_type, milestone_reviewer_for_group_type,
get_group_or_404)
from ietf.name.models import GroupMilestoneStateName
from ietf.group.mails import email_milestones_changed
def json_doc_names(docs):
return json.dumps([{"id": doc.pk, "name": doc.name } for doc in docs])
def parse_doc_names(s):
return Document.objects.filter(pk__in=[x.strip() for x in s.split(",") if x.strip()], type="draft")
from ietf.utils.fields import DatepickerDateField
class MilestoneForm(forms.Form):
id = forms.IntegerField(required=True, widget=forms.HiddenInput)
desc = forms.CharField(max_length=500, label="Milestone:", required=True)
due_month = forms.TypedChoiceField(choices=(), required=True, coerce=int)
due_year = forms.TypedChoiceField(choices=(), required=True, coerce=int)
desc = forms.CharField(max_length=500, label="Milestone", required=True)
due = DatepickerDateField(date_format="MM yyyy", picker_settings={"min-view-mode": "months", "autoclose": "1", "view-mode": "years" }, required=True)
docs = SearchableDocumentsField(label="Drafts", required=False, help_text="Any drafts that the milestone concerns.")
resolved_checkbox = forms.BooleanField(required=False, label="Resolved")
resolved = forms.CharField(max_length=50, required=False)
resolved = forms.CharField(label="Resolved as", max_length=50, required=False)
delete = forms.BooleanField(required=False, initial=False)
docs = forms.CharField(max_length=10000, required=False)
accept = forms.ChoiceField(choices=(("accept", "Accept"), ("reject", "Reject and delete"), ("noaction", "No action")),
review = forms.ChoiceField(label="Review action", help_text="Choose whether to accept or reject the proposed changes.",
choices=(("accept", "Accept"), ("reject", "Reject and delete"), ("noaction", "No action")),
required=False, initial="noaction", widget=forms.RadioSelect)
def __init__(self, *args, **kwargs):
kwargs["label_suffix"] = ""
def __init__(self, needs_review, reviewer, *args, **kwargs):
m = self.milestone = kwargs.pop("instance", None)
self.needs_review = kwargs.pop("needs_review", False)
can_review = not self.needs_review
can_review = not needs_review
if m:
self.needs_review = m.state_id == "review"
needs_review = m.state_id == "review"
if not "initial" in kwargs:
kwargs["initial"] = {}
kwargs["initial"].update(dict(id=m.pk,
desc=m.desc,
due_month=m.due.month,
due_year=m.due.year,
due=m.due,
resolved_checkbox=bool(m.resolved),
resolved=m.resolved,
docs=",".join(m.docs.values_list("pk", flat=True)),
docs=m.docs.all(),
delete=False,
accept="noaction" if can_review and self.needs_review else None,
review="noaction" if can_review and needs_review else "",
))
kwargs["prefix"] = "m%s" % m.pk
super(MilestoneForm, self).__init__(*args, **kwargs)
# set choices for due date
this_year = datetime.date.today().year
self.fields["resolved"].widget.attrs["data-default"] = "Done"
self.fields["due_month"].choices = [(month, datetime.date(this_year, month, 1).strftime("%B")) for month in range(1, 13)]
if needs_review and self.milestone and self.milestone.state_id != "review":
self.fields["desc"].widget.attrs["readonly"] = True
years = [ y for y in range(this_year, this_year + 10)]
self.changed = False
initial = self.initial.get("due_year")
if initial and initial not in years:
years.insert(0, initial)
if not (needs_review and can_review):
self.fields["review"].widget = forms.HiddenInput()
self.fields["due_year"].choices = zip(years, map(str, years))
# figure out what to prepopulate many-to-many field with
pre = ""
if not self.is_bound:
pre = self.initial.get("docs", "")
else:
pre = self["docs"].data or ""
# this is ugly, but putting it on self["docs"] is buggy with a
# bound/unbound form in Django 1.2
self.docs_names = parse_doc_names(pre)
self.docs_prepopulate = json_doc_names(self.docs_names)
# calculate whether we've changed
self.changed = self.is_bound and (not self.milestone or any(unicode(self[f].data) != unicode(self.initial[f]) for f in self.fields.iterkeys()))
def clean_docs(self):
s = self.cleaned_data["docs"]
return Document.objects.filter(pk__in=[x.strip() for x in s.split(",") if x.strip()], type="draft")
self.needs_review = needs_review
def clean_resolved(self):
r = self.cleaned_data["resolved"].strip()
@ -137,14 +107,17 @@ def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
title = "Edit charter milestones for %s %s" % (group.acronym, group.type.name)
milestones = group.groupmilestone_set.filter(state="charter")
reviewer = milestone_reviewer_for_group_type(group_type)
forms = []
milestones_dict = dict((str(m.id), m) for m in milestones)
def due_month_year_to_date(c):
y = c["due_year"]
m = c["due_month"]
return datetime.date(y, m, calendar.monthrange(y, m)[1])
y = c["due"].year
m = c["due"].month
first_day, last_day = calendar.monthrange(y, m)
return datetime.date(y, m, last_day)
def set_attributes_from_form(f, m):
c = f.cleaned_data
@ -156,10 +129,24 @@ def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
m.state = GroupMilestoneStateName.objects.get(slug="active")
elif milestone_set == "charter":
m.state = GroupMilestoneStateName.objects.get(slug="charter")
m.desc = c["desc"]
m.due = due_month_year_to_date(c)
m.resolved = c["resolved"]
def milestone_changed(f, m):
# we assume that validation has run
if not m or not f.is_valid():
return True
c = f.cleaned_data
return (c["desc"] != m.desc or
due_month_year_to_date(c) != m.due or
c["resolved"] != m.resolved or
set(c["docs"]) != set(m.docs.all()) or
c.get("review") in ("accept", "reject")
)
def save_milestone_form(f):
c = f.cleaned_data
@ -183,14 +170,14 @@ def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
changes = ['Changed %s' % named_milestone]
if m.state_id == "review" and not needs_review and c["accept"] != "noaction":
if m.state_id == "review" and not needs_review and c["review"] != "noaction":
if not history:
history = save_milestone_in_history(m)
if c["accept"] == "accept":
if c["review"] == "accept":
m.state_id = "active"
changes.append("set state to active from review, accepting new milestone")
elif c["accept"] == "reject":
elif c["review"] == "reject":
m.state_id = "deleted"
changes.append("set state to deleted from review, rejecting new milestone")
@ -260,8 +247,6 @@ def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
elif m.state_id == "review":
return 'Added %s for review, due %s' % (named_milestone, m.due.strftime("%B %Y"))
finished_milestone_text = "Done"
form_errors = False
if request.method == 'POST':
@ -272,22 +257,23 @@ def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
# new milestones have non-existing ids so instance end up as None
instance = milestones_dict.get(request.POST.get(prefix + "-id", ""), None)
f = MilestoneForm(request.POST, prefix=prefix, instance=instance,
needs_review=needs_review)
f = MilestoneForm(needs_review, reviewer, request.POST, prefix=prefix, instance=instance)
forms.append(f)
form_errors = form_errors or not f.is_valid()
f.changed = milestone_changed(f, f.milestone)
if f.is_valid() and f.cleaned_data.get("review") in ("accept", "reject"):
f.needs_review = False
action = request.POST.get("action", "review")
if action == "review":
for f in forms:
if not f.is_valid():
continue
# let's fill in the form milestone so we can output it in the template
if not f.milestone:
f.milestone = GroupMilestone()
set_attributes_from_form(f, f.milestone)
if f.is_valid():
# let's fill in the form milestone so we can output it in the template
if not f.milestone:
f.milestone = GroupMilestone()
set_attributes_from_form(f, f.milestone)
elif action == "save" and not form_errors:
changes = []
for f in forms:
@ -314,11 +300,11 @@ def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
return HttpResponseRedirect(group.about_url())
else:
for m in milestones:
forms.append(MilestoneForm(instance=m, needs_review=needs_review))
forms.append(MilestoneForm(needs_review, reviewer, instance=m))
can_reset = milestone_set == "charter" and get_chartering_type(group.charter) == "rechartering"
empty_form = MilestoneForm(needs_review=needs_review)
empty_form = MilestoneForm(needs_review, reviewer)
forms.sort(key=lambda f: f.milestone.due if f.milestone else datetime.date.max)
@ -329,9 +315,8 @@ def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
form_errors=form_errors,
empty_form=empty_form,
milestone_set=milestone_set,
finished_milestone_text=finished_milestone_text,
needs_review=needs_review,
reviewer=milestone_reviewer_for_group_type(group_type),
reviewer=reviewer,
can_reset=can_reset))
@login_required
@ -391,8 +376,3 @@ def reset_charter_milestones(request, group_type, acronym):
charter_milestones=charter_milestones,
current_milestones=current_milestones,
))
def ajax_search_docs(request, group_type, acronym):
docs = Document.objects.filter(name__icontains=request.GET.get('q',''), type="draft").order_by('name').distinct()[:20]
return HttpResponse(json_doc_names(docs), content_type='application/json')

View file

@ -1,7 +1,6 @@
import os
import shutil
import calendar
import json
import datetime
from pyquery import PyQuery
@ -99,7 +98,7 @@ class GroupPagesTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('table.ietf-doctable td.acronym a:contains("%s")' % group.acronym)), 1)
self.assertEqual(len(q('.content-wrapper a:contains("%s")' % group.acronym)), 1)
def test_concluded_groups(self):
draft = make_test_data()
@ -111,7 +110,7 @@ class GroupPagesTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('table.concluded-groups a:contains("%s")' % group.acronym)), 1)
self.assertEqual(len(q('.content-wrapper a:contains("%s")' % group.acronym)), 1)
def test_bofs(self):
draft = make_test_data()
@ -123,7 +122,7 @@ class GroupPagesTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('table.ietf-doctable td.acronym a:contains("%s")' % group.acronym)), 1)
self.assertEqual(len(q('.content-wrapper a:contains("%s")' % group.acronym)), 1)
def test_group_documents(self):
draft = make_test_data()
@ -302,7 +301,7 @@ class GroupEditTests(TestCase):
r = self.client.post(url, dict(acronym="foobarbaz")) # No name
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
self.assertEqual(len(Group.objects.filter(type="wg")), num_wgs)
# acronym contains non-alphanumeric
@ -330,7 +329,7 @@ class GroupEditTests(TestCase):
self.assertEqual(group.charter.name, "charter-ietf-testwg")
self.assertEqual(group.charter.rev, "00-00")
def test_create_based_on_existing(self):
def test_create_based_on_existing_bof(self):
make_test_data()
url = urlreverse('group_create', kwargs=dict(group_type="wg"))
@ -342,8 +341,8 @@ class GroupEditTests(TestCase):
r = self.client.post(url, dict(name="Test", acronym=group.parent.acronym))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertEqual(len(q('form input[name="confirmed"]')), 0) # can't confirm us out of this
self.assertTrue(len(q('form .has-error')) > 0)
self.assertEqual(len(q('form input[name="confirm_acronym"]')), 0) # can't confirm us out of this
# try elevating BoF to WG
group.state_id = "bof"
@ -352,14 +351,14 @@ class GroupEditTests(TestCase):
r = self.client.post(url, dict(name="Test", acronym=group.acronym))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertEqual(len(q('form input[name="confirmed"]')), 1)
self.assertTrue(len(q('form .has-error')) > 0)
self.assertEqual(len(q('form input[name="confirm_acronym"]')), 1)
self.assertEqual(Group.objects.get(acronym=group.acronym).state_id, "bof")
# confirm elevation
state = GroupStateName.objects.get(slug="proposed")
r = self.client.post(url, dict(name="Test", acronym=group.acronym, confirmed="1",state=state.pk))
r = self.client.post(url, dict(name="Test", acronym=group.acronym, confirm_acronym="1", state=state.pk))
self.assertEqual(r.status_code, 302)
self.assertEqual(Group.objects.get(acronym=group.acronym).state_id, "proposed")
self.assertEqual(Group.objects.get(acronym=group.acronym).name, "Test")
@ -383,7 +382,7 @@ class GroupEditTests(TestCase):
r = self.client.post(url, dict(acronym="collide"))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
# create old acronym
group.acronym = "oldmars"
@ -396,7 +395,7 @@ class GroupEditTests(TestCase):
r = self.client.post(url, dict(acronym="oldmars"))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
# edit info
with open(os.path.join(self.charter_dir, "%s-%s.txt" % (group.charter.canonical_name(), group.charter.rev)), "w") as f:
@ -453,7 +452,7 @@ class GroupEditTests(TestCase):
r = self.client.post(url, dict(instructions="")) # No instructions
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
# request conclusion
mailbox_before = len(outbox)
@ -530,15 +529,14 @@ class MilestoneTests(TestCase):
r = self.client.post(url, { 'prefix': "m-1",
'm-1-id': "-1",
'm-1-desc': "", # no description
'm-1-due_month': str(due.month),
'm-1-due_year': str(due.year),
'm-1-due': due.strftime("%B %Y"),
'm-1-resolved': "",
'm-1-docs': ",".join(docs),
'action': "save",
})
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
self.assertEqual(GroupMilestone.objects.count(), milestones_before)
# add
@ -546,8 +544,7 @@ class MilestoneTests(TestCase):
r = self.client.post(url, { 'prefix': "m-1",
'm-1-id': "-1",
'm-1-desc': "Test 3",
'm-1-due_month': str(due.month),
'm-1-due_year': str(due.year),
'm-1-due': due.strftime("%B %Y"),
'm-1-resolved': "",
'm-1-docs': ",".join(docs),
'action': "save",
@ -584,8 +581,7 @@ class MilestoneTests(TestCase):
r = self.client.post(url, { 'prefix': "m-1",
'm-1-id': -1,
'm-1-desc': "Test 3",
'm-1-due_month': str(due.month),
'm-1-due_year': str(due.year),
'm-1-due': due.strftime("%B %Y"),
'm-1-resolved': "",
'm-1-docs': "",
'action': "save",
@ -619,11 +615,10 @@ class MilestoneTests(TestCase):
r = self.client.post(url, { 'prefix': "m1",
'm1-id': m1.id,
'm1-desc': m1.desc,
'm1-due_month': str(m1.due.month),
'm1-due_year': str(m1.due.year),
'm1-due': m1.due.strftime("%B %Y"),
'm1-resolved': m1.resolved,
'm1-docs': ",".join(m1.docs.values_list("name", flat=True)),
'm1-accept': "accept",
'm1-review': "accept",
'action': "save",
})
self.assertEqual(r.status_code, 302)
@ -646,8 +641,7 @@ class MilestoneTests(TestCase):
r = self.client.post(url, { 'prefix': "m1",
'm1-id': m1.id,
'm1-desc': m1.desc,
'm1-due_month': str(m1.due.month),
'm1-due_year': str(m1.due.year),
'm1-due': m1.due.strftime("%B %Y"),
'm1-resolved': "",
'm1-docs': ",".join(m1.docs.values_list("name", flat=True)),
'm1-delete': "checked",
@ -677,15 +671,14 @@ class MilestoneTests(TestCase):
r = self.client.post(url, { 'prefix': "m1",
'm1-id': m1.id,
'm1-desc': "", # no description
'm1-due_month': str(due.month),
'm1-due_year': str(due.year),
'm1-due': due.strftime("%B %Y"),
'm1-resolved': "",
'm1-docs': ",".join(docs),
'action': "save",
})
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q('form ul.errorlist')) > 0)
self.assertTrue(len(q('form .has-error')) > 0)
m = GroupMilestone.objects.get(pk=m1.pk)
self.assertEqual(GroupMilestone.objects.count(), milestones_before)
self.assertEqual(m.due, m1.due)
@ -695,8 +688,7 @@ class MilestoneTests(TestCase):
r = self.client.post(url, { 'prefix': "m1",
'm1-id': m1.id,
'm1-desc': "Test 2 - changed",
'm1-due_month': str(due.month),
'm1-due_year': str(due.year),
'm1-due': due.strftime("%B %Y"),
'm1-resolved': "Done",
'm1-resolved_checkbox': "checked",
'm1-docs': ",".join(docs),
@ -873,15 +865,6 @@ class MilestoneTests(TestCase):
self.assertTrue(m1.desc in unicode(outbox[-1]))
self.assertTrue(m2.desc in unicode(outbox[-1]))
def test_ajax_search_docs(self):
draft = make_test_data()
r = self.client.get(urlreverse("group_ajax_search_docs", kwargs=dict(group_type=draft.group.type_id, acronym=draft.group.acronym)),
dict(q=draft.name))
self.assertEqual(r.status_code, 200)
data = json.loads(r.content)
self.assertTrue(data[0]["id"], draft.name)
class CustomizeWorkflowTests(TestCase):
def test_customize_workflow(self):
make_test_data()

View file

@ -22,7 +22,6 @@ urlpatterns = patterns('',
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "current"}, "group_edit_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/charter/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "charter"}, "group_edit_charter_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/charter/reset/$', 'ietf.group.milestones.reset_charter_milestones', None, "group_reset_charter_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/ajax/searchdocs/$', 'ietf.group.milestones.ajax_search_docs', None, "group_ajax_search_docs"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/workflow/$', 'ietf.group.edit.customize_workflow'),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/about/(?P<group_type>.)?$', 'ietf.group.info.group_about', None, 'group_about'),

View file

@ -31,6 +31,5 @@ urlpatterns = patterns('',
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/$', milestones.edit_milestones, {'milestone_set': "current"}, "group_edit_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/charter/$', milestones.edit_milestones, {'milestone_set': "charter"}, "group_edit_charter_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/charter/reset/$', milestones.reset_charter_milestones, None, "group_reset_charter_milestones"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/ajax/searchdocs/$', milestones.ajax_search_docs, None, "group_ajax_search_docs"),
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/workflow/$', edit.customize_workflow),
)

View file

@ -10,7 +10,7 @@ from ietf.group.models import Group, GroupEvent, Role
from ietf.group.utils import save_group_in_history
from ietf.ietfauth.utils import has_role
from ietf.name.models import StreamName
from ietf.person.fields import AutocompletedEmailsField
from ietf.person.fields import SearchableEmailsField
from ietf.person.models import Email
import debug # pyflakes:ignore
@ -24,14 +24,16 @@ def stream_documents(request, acronym):
streams = [ s.slug for s in StreamName.objects.all().exclude(slug__in=['ietf', 'legacy']) ]
if not acronym in streams:
raise Http404("No such stream: %s" % acronym)
group = get_object_or_404(Group, acronym=acronym)
editable = has_role(request.user, "Secretariat") or group.has_role(request.user, "chair")
stream = StreamName.objects.get(slug=acronym)
form = SearchForm({'by':'stream', 'stream':acronym,
'rfcs':'on', 'activedrafts':'on'})
docs, meta = retrieve_search_results(form)
return render_to_response('group/stream_documents.html', {'stream':stream, 'docs':docs, 'meta':meta }, context_instance=RequestContext(request))
return render_to_response('group/stream_documents.html', {'stream':stream, 'docs':docs, 'meta':meta, 'editable':editable }, context_instance=RequestContext(request))
class StreamEditForm(forms.Form):
delegates = AutocompletedEmailsField(required=False, only_users=True)
delegates = SearchableEmailsField(required=False, only_users=True)
def stream_edit(request, acronym):
group = get_object_or_404(Group, acronym=acronym)
@ -62,7 +64,7 @@ def stream_edit(request, acronym):
for e in new:
Role.objects.get_or_create(name_id=slug, email=e, group=group, person=e.person)
return redirect("ietf.group.views.streams")
return redirect("ietf.group.views_stream.streams")
else:
form = StreamEditForm(initial=dict(delegates=Email.objects.filter(role__group=group, role__name="delegate")))
@ -72,4 +74,4 @@ def stream_edit(request, acronym):
'form': form,
},
context_instance=RequestContext(request))

View file

@ -85,50 +85,50 @@ def get_doc_section(doc):
def agenda_sections():
return OrderedDict([
('1', {'title':"Administrivia"}),
('1.1', {'title':"Roll Call"}),
('1.2', {'title':"Bash the Agenda"}),
('1.3', {'title':"Approval of the Minutes of Past Telechats"}),
('1.4', {'title':"List of Remaining Action Items from Last Telechat"}),
('2', {'title':"Protocol Actions"}),
('2.1', {'title':"WG Submissions"}),
('2.1.1', {'title':"New Items", 'docs': []}),
('2.1.2', {'title':"Returning Items", 'docs':[]}),
('2.1.3', {'title':"For Action", 'docs':[]}),
('2.2', {'title':"Individual Submissions"}),
('2.2.1', {'title':"New Items", 'docs':[]}),
('2.2.2', {'title':"Returning Items", 'docs':[]}),
('2.2.3', {'title':"For Action", 'docs':[]}),
('2.3', {'title':"Status Changes"}),
('2.3.1', {'title':"New Items", 'docs':[]}),
('2.3.2', {'title':"Returning Items", 'docs':[]}),
('2.3.3', {'title':"For Action", 'docs':[]}),
('3', {'title':"Document Actions"}),
('3.1', {'title':"WG Submissions"}),
('3.1.1', {'title':"New Items", 'docs':[]}),
('3.1.2', {'title':"Returning Items", 'docs':[]}),
('3.1.3', {'title':"For Action", 'docs':[]}),
('3.2', {'title':"Individual Submissions Via AD"}),
('3.2.1', {'title':"New Items", 'docs':[]}),
('3.2.2', {'title':"Returning Items", 'docs':[]}),
('3.2.3', {'title':"For Action", 'docs':[]}),
('3.3', {'title':"Status Changes"}),
('3.3.1', {'title':"New Items", 'docs':[]}),
('3.3.2', {'title':"Returning Items", 'docs':[]}),
('3.3.3', {'title':"For Action", 'docs':[]}),
('3.4', {'title':"IRTF and Independent Submission Stream Documents"}),
('3.4.1', {'title':"New Items", 'docs':[]}),
('3.4.2', {'title':"Returning Items", 'docs':[]}),
('3.4.3', {'title':"For Action", 'docs':[]}),
('4', {'title':"Working Group Actions"}),
('4.1', {'title':"WG Creation"}),
('4.1.1', {'title':"Proposed for IETF Review", 'docs':[]}),
('4.1.2', {'title':"Proposed for Approval", 'docs':[]}),
('4.2', {'title':"WG Rechartering"}),
('4.2.1', {'title':"Under Evaluation for IETF Review", 'docs':[]}),
('4.2.2', {'title':"Proposed for Approval", 'docs':[]}),
('5', {'title':"IAB News We Can Use"}),
('6', {'title':"Management Issues"}),
('7', {'title':"Working Group News"}),
('1.1', {'title':"Roll call"}),
('1.2', {'title':"Bash the agenda"}),
('1.3', {'title':"Approval of the minutes of past telechats"}),
('1.4', {'title':"List of remaining action items from last telechat"}),
('2', {'title':"Protocol actions"}),
('2.1', {'title':"WG submissions"}),
('2.1.1', {'title':"New items", 'docs': []}),
('2.1.2', {'title':"Returning items", 'docs':[]}),
('2.1.3', {'title':"For action", 'docs':[]}),
('2.2', {'title':"Individual submissions"}),
('2.2.1', {'title':"New items", 'docs':[]}),
('2.2.2', {'title':"Returning items", 'docs':[]}),
('2.2.3', {'title':"For action", 'docs':[]}),
('2.3', {'title':"Status changes"}),
('2.3.1', {'title':"New items", 'docs':[]}),
('2.3.2', {'title':"Returning items", 'docs':[]}),
('2.3.3', {'title':"For action", 'docs':[]}),
('3', {'title':"Document actions"}),
('3.1', {'title':"WG submissions"}),
('3.1.1', {'title':"New items", 'docs':[]}),
('3.1.2', {'title':"Returning items", 'docs':[]}),
('3.1.3', {'title':"For action", 'docs':[]}),
('3.2', {'title':"Individual submissions via AD"}),
('3.2.1', {'title':"New items", 'docs':[]}),
('3.2.2', {'title':"Returning items", 'docs':[]}),
('3.2.3', {'title':"For action", 'docs':[]}),
('3.3', {'title':"Status changes"}),
('3.3.1', {'title':"New items", 'docs':[]}),
('3.3.2', {'title':"Returning items", 'docs':[]}),
('3.3.3', {'title':"For action", 'docs':[]}),
('3.4', {'title':"IRTF and Independent Submission stream documents"}),
('3.4.1', {'title':"New items", 'docs':[]}),
('3.4.2', {'title':"Returning items", 'docs':[]}),
('3.4.3', {'title':"For action", 'docs':[]}),
('4', {'title':"Working Group actions"}),
('4.1', {'title':"WG creation"}),
('4.1.1', {'title':"Proposed for IETF review", 'docs':[]}),
('4.1.2', {'title':"Proposed for approval", 'docs':[]}),
('4.2', {'title':"WG rechartering"}),
('4.2.1', {'title':"Under evaluation for IETF review", 'docs':[]}),
('4.2.2', {'title':"Proposed for approval", 'docs':[]}),
('5', {'title':"IAB news we can use"}),
('6', {'title':"Management issues"}),
('7', {'title':"Working Group news"}),
])
def fill_in_agenda_administrivia(date, sections):
@ -185,7 +185,7 @@ def fill_in_agenda_docs(date, sections, matches=None):
# prune empty "For action" sections
empty_for_action = [n for n, section in sections.iteritems()
if section["title"] == "For Action" and not section["docs"]]
if section["title"] == "For action" and not section["docs"]]
for num in empty_for_action:
del sections[num]

View file

@ -2,24 +2,24 @@
# Portion Copyright (C) 2008-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
# 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
@ -58,6 +58,9 @@ from ietf.iesg.models import TelechatDate
from ietf.ietfauth.utils import has_role, role_required, user_is_person
from ietf.person.models import Person
# FACELIFT:
from ietf.doc.views_search import fill_in_search_attributes
def review_decisions(request, year=None):
events = DocEvent.objects.filter(type__in=("iesg_disapproved", "iesg_approved"))
@ -176,8 +179,8 @@ def agenda(request, date=None):
data = agenda_data(date)
if has_role(request.user, ["Area Director", "IAB Chair", "Secretariat"]):
data["sections"]["1.1"]["title"] = data["sections"]["1.1"]["title"].replace("Roll Call", '<a href="https://www.ietf.org/iesg/internal/rollcall.txt">Roll Call</a>')
data["sections"]["1.3"]["title"] = data["sections"]["1.3"]["title"].replace("Minutes", '<a href="https://www.ietf.org/iesg/internal/minutes.txt">Minutes</a>')
data["sections"]["1.1"]["title"] = data["sections"]["1.1"]["title"].replace("Roll call", '<a href="https://www.ietf.org/iesg/internal/rollcall.txt">Roll Call</a>')
data["sections"]["1.3"]["title"] = data["sections"]["1.3"]["title"].replace("minutes", '<a href="https://www.ietf.org/iesg/internal/minutes.txt">Minutes</a>')
request.session['ballot_edit_return_point'] = request.path_info
return render_to_response("iesg/agenda.html", {
@ -303,7 +306,7 @@ class RescheduleForm(forms.Form):
def __init__(self, *args, **kwargs):
dates = kwargs.pop('telechat_dates')
super(self.__class__, self).__init__(*args, **kwargs)
# telechat choices
@ -360,6 +363,9 @@ def agenda_documents(request):
telechats = []
for date in dates:
sections = agenda_sections()
# FACELIFT: augment the docs with the search attributes, since we're using
# the search_result_row view to display them (which expects them)
fill_in_search_attributes(docs_by_date[date])
fill_in_agenda_docs(date, sections, docs_by_date[date])
telechats.append({

View file

@ -217,7 +217,7 @@ class PersonForm(ModelForm):
# Make sure the alias table contains any new and/or old names.
old_names = set([x.name for x in Alias.objects.filter(person=self.instance)])
curr_names = set([x for x in [self.instance.name,
curr_names = set([x for x in [self.instance.name,
self.instance.ascii,
self.instance.ascii_short,
self.data['name'],

View file

@ -1,6 +1,7 @@
import datetime
import email
from django.utils.safestring import mark_safe
from django import forms
from ietf.group.models import Group
@ -102,20 +103,20 @@ class GenericDisclosureForm(forms.Form):
"""Custom ModelForm-like form to use for new Generic or NonDocSpecific Iprs.
If patent_info is submitted create a NonDocSpecificIprDisclosure object
otherwise create a GenericIprDisclosure object."""
compliant = forms.BooleanField(required=False)
compliant = forms.CharField(label="This disclosure complies with RFC 3979", required=False)
holder_legal_name = forms.CharField(max_length=255)
notes = forms.CharField(max_length=255,widget=forms.Textarea,required=False)
other_designations = forms.CharField(max_length=255,required=False)
holder_contact_name = forms.CharField(max_length=255)
holder_contact_email = forms.EmailField()
holder_contact_info = forms.CharField(max_length=255,widget=forms.Textarea,required=False)
holder_contact_name = forms.CharField(label="Name", max_length=255)
holder_contact_email = forms.EmailField(label="Email")
holder_contact_info = forms.CharField(label="Other Info (address, phone, etc.)", max_length=255,widget=forms.Textarea,required=False)
submitter_name = forms.CharField(max_length=255,required=False)
submitter_email = forms.EmailField(required=False)
patent_info = forms.CharField(max_length=255,widget=forms.Textarea,required=False)
patent_info = forms.CharField(max_length=255,widget=forms.Textarea, required=False, help_text="Patent, Serial, Publication, Registration, or Application/File number(s), Date(s) granted or applied for, Country, and any additional notes")
has_patent_pending = forms.BooleanField(required=False)
statement = forms.CharField(max_length=255,widget=forms.Textarea,required=False)
updates = AutocompletedIprDisclosuresField(required=False)
same_as_ii_above = forms.BooleanField(required=False)
same_as_ii_above = forms.BooleanField(label="Same as in section II above", required=False)
def __init__(self,*args,**kwargs):
super(GenericDisclosureForm, self).__init__(*args,**kwargs)
@ -155,7 +156,7 @@ class GenericDisclosureForm(forms.Form):
class IprDisclosureFormBase(forms.ModelForm):
"""Base form for Holder and ThirdParty disclosures"""
updates = AutocompletedIprDisclosuresField(required=False)
updates = AutocompletedIprDisclosuresField(required=False, help_text=mark_safe("If this disclosure <strong>updates</strong> other disclosures identify here which ones. Leave this field blank if this disclosure does not update any prior disclosures. Note: Updates to IPR disclosures must only be made by authorized representatives of the original submitters. Updates will automatically be forwarded to the current Patent Holder's Contact and to the Submitter of the original IPR disclosure."))
same_as_ii_above = forms.BooleanField(required=False)
def __init__(self,*args,**kwargs):
@ -163,6 +164,22 @@ class IprDisclosureFormBase(forms.ModelForm):
self.fields['submitter_name'].required = False
self.fields['submitter_email'].required = False
self.fields['compliant'].initial = True
self.fields['compliant'].label = "This disclosure complies with RFC 3979"
if "ietfer_name" in self.fields:
self.fields["ietfer_name"].label = "Name"
if "ietfer_contact_email" in self.fields:
self.fields["ietfer_contact_email"].label = "Email"
if "ietfer_contact_info" in self.fields:
self.fields["ietfer_contact_info"].label = "Other info"
self.fields["ietfer_contact_info"].help_text = "Address, phone, etc."
if "patent_info" in self.fields:
self.fields["patent_info"].help_text = "Patent, Serial, Publication, Registration, or Application/File number(s), Date(s) granted or applied for, Country, and any additional notes"
if "licensing" in self.fields:
self.fields["licensing_comments"].label = "Licensing information, comments, notes, or URL for further information"
if "submitter_claims_all_terms_disclosed" in self.fields:
self.fields["submitter_claims_all_terms_disclosed"].label = "The individual submitting this template represents and warrants that all terms and conditions that must be satisfied for implementers of any covered IETF specification to obtain a license have been disclosed in this IPR disclosure statement"
if "same_as_ii_above" in self.fields:
self.fields["same_as_ii_above"].label = "Same as in section II above"
class Meta:
"""This will be overridden"""
@ -267,13 +284,13 @@ class ThirdPartyIprDisclosureForm(IprDisclosureFormBase):
class SearchForm(forms.Form):
state = forms.MultipleChoiceField(choices=STATE_CHOICES,widget=forms.CheckboxSelectMultiple,required=False)
draft = forms.CharField(max_length=128,required=False)
rfc = forms.IntegerField(required=False)
holder = forms.CharField(max_length=128,required=False)
patent = forms.CharField(max_length=128,required=False)
group = GroupModelChoiceField(label="Working group name",queryset=Group.objects.filter(type='wg').order_by('acronym'),required=False)
doctitle = forms.CharField(max_length=128,required=False)
iprtitle = forms.CharField(max_length=128,required=False)
draft = forms.CharField(label="Draft name", max_length=128, required=False)
rfc = forms.IntegerField(label="RFC number", required=False)
holder = forms.CharField(label="Name of patent owner/applicant", max_length=128,required=False)
patent = forms.CharField(label="Text in patent information", max_length=128,required=False)
group = GroupModelChoiceField(label="Working group",queryset=Group.objects.filter(type='wg').order_by('acronym'),required=False, empty_label="(Select WG)")
doctitle = forms.CharField(label="Words in document title", max_length=128,required=False)
iprtitle = forms.CharField(label="Words in IPR disclosure title", max_length=128,required=False)
class StateForm(forms.Form):
state = forms.ModelChoiceField(queryset=IprDisclosureStateName.objects,label="New State",empty_label=None)

View file

@ -12,8 +12,8 @@ LICENSE_CHOICES = (
(3, 'c) Reasonable and Non-Discriminatory License to All Implementers with Possible Royalty/Fee.'),
(4, 'd) Licensing Declaration to be Provided Later (implies a willingness'
' to commit to the provisions of a), b), or c) above to all implementers;'
' otherwise, the next option "Unwilling to Commit to the Provisions of'
' a), b), or c) Above". - must be selected).'),
' otherwise, the next option - "Unwilling to Commit to the Provisions of'
' a), b), or c) Above" - must be selected).'),
(5, 'e) Unwilling to Commit to the Provisions of a), b), or c) Above.'),
(6, 'f) See Text Below for Licensing Declaration.'),
)
@ -27,10 +27,10 @@ SELECT_CHOICES = (
(2, 'NO'),
)
STATUS_CHOICES = (
( 0, "Waiting for approval" ),
( 1, "Approved and Posted" ),
( 2, "Rejected by Administrator" ),
( 3, "Removed by Request" ),
( 0, "Waiting for approval" ),
( 1, "Approved and Posted" ),
( 2, "Rejected by Administrator" ),
( 3, "Removed by Request" ),
)
class IprDetail(models.Model):
@ -45,35 +45,35 @@ class IprDetail(models.Model):
legacy_title_2 = models.CharField(blank=True, null=True, db_column="additional_old_title2", max_length=255)
# Patent holder fieldset
legal_name = models.CharField("Legal Name", db_column="p_h_legal_name", max_length=255)
legal_name = models.CharField("Legal name", db_column="p_h_legal_name", max_length=255)
# Patent Holder Contact fieldset
# self.contact.filter(contact_type=1)
# IETF Contact fieldset
# self.contact.filter(contact_type=3)
# Related IETF Documents fieldset
rfc_number = models.IntegerField(null=True, editable=False, blank=True) # always NULL
id_document_tag = models.IntegerField(null=True, editable=False, blank=True) # always NULL
other_designations = models.CharField(blank=True, max_length=255)
other_designations = models.CharField("Designations for other contributions", blank=True, max_length=255)
document_sections = models.TextField("Specific document sections covered", blank=True, max_length=255, db_column='disclouser_identify')
# Patent Information fieldset
patents = models.TextField("Patent Applications", db_column="p_applications", max_length=255)
date_applied = models.CharField(max_length=255)
patents = models.TextField("Patent, serial, publication, registration, or application/file number(s)", db_column="p_applications", max_length=255)
date_applied = models.CharField("Date(s) granted or applied for", max_length=255)
country = models.CharField(max_length=255)
notes = models.TextField("Additional notes", db_column="p_notes", blank=True)
is_pending = models.IntegerField("Unpublished Pending Patent Application", blank=True, null=True, choices=SELECT_CHOICES, db_column="selecttype")
applies_to_all = models.IntegerField("Applies to all IPR owned by Submitter", blank=True, null=True, choices=SELECT_CHOICES, db_column="selectowned")
is_pending = models.IntegerField("Unpublished pending patent application", blank=True, null=True, choices=SELECT_CHOICES, db_column="selecttype")
applies_to_all = models.IntegerField("Applies to all IPR owned by submitter", blank=True, null=True, choices=SELECT_CHOICES, db_column="selectowned")
# Licensing Declaration fieldset
licensing_option = models.IntegerField(null=True, blank=True, choices=LICENSE_CHOICES)
lic_opt_a_sub = models.IntegerField(null=True, editable=False, choices=STDONLY_CHOICES)
lic_opt_b_sub = models.IntegerField(null=True, editable=False, choices=STDONLY_CHOICES)
lic_opt_c_sub = models.IntegerField(null=True, editable=False, choices=STDONLY_CHOICES)
comments = models.TextField("Licensing Comments", blank=True)
lic_checkbox = models.BooleanField("All terms and conditions has been disclosed", default=False)
comments = models.TextField("Licensing comments", blank=True)
lic_checkbox = models.BooleanField("The individual submitting this template represents and warrants that all terms and conditions that must be satisfied for implementers of any covered IETF specification to obtain a license have been disclosed in this IPR disclosure statement.", default=False)
# Other notes fieldset
@ -119,8 +119,8 @@ class IprContact(models.Model):
name = models.CharField(max_length=255)
title = models.CharField(blank=True, max_length=255)
department = models.CharField(blank=True, max_length=255)
address1 = models.CharField(blank=True, max_length=255)
address2 = models.CharField(blank=True, max_length=255)
address1 = models.CharField("Address", blank=True, max_length=255)
address2 = models.CharField("Address (continued)", blank=True, max_length=255)
telephone = models.CharField(blank=True, max_length=25)
fax = models.CharField(blank=True, max_length=25)
email = models.EmailField(max_length=255)
@ -269,7 +269,7 @@ class HolderIprDisclosure(IprDisclosureBase):
has_patent_pending = models.BooleanField(default=False)
holder_contact_email = models.EmailField()
holder_contact_name = models.CharField(max_length=255)
holder_contact_info = models.TextField(blank=True)
holder_contact_info = models.TextField(blank=True, help_text="Address, phone, etc.")
licensing = models.ForeignKey(IprLicenseTypeName)
licensing_comments = models.TextField(blank=True)
submitter_claims_all_terms_disclosed = models.BooleanField(default=False)
@ -277,7 +277,7 @@ class HolderIprDisclosure(IprDisclosureBase):
class ThirdPartyIprDisclosure(IprDisclosureBase):
ietfer_name = models.CharField(max_length=255) # "Whose Personal Belief Triggered..."
ietfer_contact_email = models.EmailField()
ietfer_contact_info = models.TextField(blank=True)
ietfer_contact_info = models.TextField(blank=True, help_text="Address, phone, etc.")
patent_info = models.TextField()
has_patent_pending = models.BooleanField(default=False)
@ -285,7 +285,7 @@ class NonDocSpecificIprDisclosure(IprDisclosureBase):
'''A Generic IPR Disclosure w/ patent information'''
holder_contact_name = models.CharField(max_length=255)
holder_contact_email = models.EmailField()
holder_contact_info = models.TextField(blank=True)
holder_contact_info = models.TextField(blank=True, help_text="Address, phone, etc.")
patent_info = models.TextField()
has_patent_pending = models.BooleanField(default=False)
statement = models.TextField() # includes licensing info
@ -293,7 +293,7 @@ class NonDocSpecificIprDisclosure(IprDisclosureBase):
class GenericIprDisclosure(IprDisclosureBase):
holder_contact_name = models.CharField(max_length=255)
holder_contact_email = models.EmailField()
holder_contact_info = models.TextField(blank=True)
holder_contact_info = models.TextField(blank=True, help_text="Address, phone, etc.")
statement = models.TextField() # includes licensing info
class IprDocRel(models.Model):

View file

@ -13,7 +13,7 @@ from ietf.ipr.models import (IprDisclosureBase,GenericIprDisclosure,HolderIprDis
ThirdPartyIprDisclosure,RelatedIpr)
from ietf.ipr.utils import get_genitive, get_ipr_summary
from ietf.message.models import Message
from ietf.utils.test_utils import TestCase
from ietf.utils.test_utils import TestCase, login_testing_unauthorized
from ietf.utils.test_data import make_test_data
@ -247,7 +247,7 @@ class IprTests(TestCase):
})
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(len(q("ul.errorlist")) > 0)
self.assertTrue(len(q("form .has-error")) > 0)
# successful post
r = self.client.post(url, {
@ -468,14 +468,11 @@ I would like to revoke this declaration.
def test_post(self):
make_test_data()
ipr = IprDisclosureBase.objects.get(title='Statement regarding rights')
url = urlreverse("ipr_post",kwargs={ "id": ipr.id })
# fail if not logged in
url = urlreverse("ipr_post", kwargs={ "id": ipr.id })
login_testing_unauthorized(self, "secretary", url)
r = self.client.get(url,follow=True)
self.assertTrue("Sign In" in r.content)
# successful post
self.client.login(username="secretary", password="secretary+password")
r = self.client.get(url,follow=True)
self.assertEqual(r.status_code,200)
self.assertEqual(r.status_code, 200)
ipr = IprDisclosureBase.objects.get(title='Statement regarding rights')
self.assertEqual(ipr.state.slug,'posted')
@ -518,4 +515,4 @@ Subject: test
""".format(data['reply_to'],datetime.datetime.now().ctime())
result = process_response_email(message_string)
self.assertIsInstance(result,Message)
self.assertFalse(event.response_past_due())
self.assertFalse(event.response_past_due())

View file

@ -10,8 +10,7 @@ from django.db.models import Q
from django.forms.models import inlineformset_factory
from django.forms.formsets import formset_factory
from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.shortcuts import render_to_response as render, get_object_or_404, redirect
from django.template import RequestContext
from django.shortcuts import render, get_object_or_404, redirect
from django.template.loader import render_to_string
from ietf.doc.models import DocAlias
@ -212,7 +211,7 @@ def ajax_rfc_search(request):
# Views
# ----------------------------------------------------------------
def about(request):
return render("ipr/disclosure.html", {}, context_instance=RequestContext(request))
return render(request, "ipr/disclosure.html", {})
@role_required('Secretariat',)
def add_comment(request, id):
@ -239,8 +238,7 @@ def add_comment(request, id):
else:
form = AddCommentForm()
return render('ipr/add_comment.html',dict(ipr=ipr,form=form),
context_instance=RequestContext(request))
return render(request, 'ipr/add_comment.html',dict(ipr=ipr,form=form))
@role_required('Secretariat',)
def add_email(request, id):
@ -276,8 +274,7 @@ def add_email(request, id):
else:
form = AddEmailForm(ipr=ipr)
return render('ipr/add_email.html',dict(ipr=ipr,form=form),
context_instance=RequestContext(request))
return render(request, 'ipr/add_email.html',dict(ipr=ipr,form=form))
@role_required('Secretariat',)
def admin(request,state):
@ -293,12 +290,11 @@ def admin(request,state):
('Parked','parked',urlreverse('ipr_admin',kwargs={'state':'parked'}),True)]
template = 'ipr/admin_' + state + '.html'
return render(template, {
return render(request, template, {
'iprs': iprs,
'tabs': tabs,
'selected': state},
context_instance=RequestContext(request)
)
'selected': state
})
@role_required('Secretariat',)
def edit(request, id, updates=None):
@ -384,13 +380,12 @@ def edit(request, id, updates=None):
draft_formset = DraftFormset(instance=ipr, prefix='draft',queryset=dqs)
rfc_formset = RfcFormset(instance=ipr, prefix='rfc',queryset=rqs)
return render("ipr/details_edit.html", {
return render(request, "ipr/details_edit.html", {
'form': form,
'draft_formset':draft_formset,
'rfc_formset':rfc_formset,
'type':type},
context_instance=RequestContext(request)
)
'type':type
})
@role_required('Secretariat',)
def email(request, id):
@ -441,11 +436,10 @@ def email(request, id):
}
form = MessageModelForm(initial=initial)
return render("ipr/email.html", {
return render(request, "ipr/email.html", {
'ipr': ipr,
'form':form},
context_instance=RequestContext(request)
)
'form':form
})
def history(request, id):
"""Show the history for a specific IPR disclosure"""
@ -457,13 +451,12 @@ def history(request, id):
tabs = [('Disclosure','disclosure',urlreverse('ipr_show',kwargs={'id':id}),True),
('History','history',urlreverse('ipr_history',kwargs={'id':id}),True)]
return render("ipr/details_history.html", {
return render(request, "ipr/details_history.html", {
'events':events,
'ipr': ipr,
'tabs':tabs,
'selected':'history'},
context_instance=RequestContext(request)
)
'selected':'history'
})
def iprs_for_drafts_txt(request):
docipr = {}
@ -544,7 +537,7 @@ def new(request, type, updates=None):
"ipr/new_update_email.txt",
{"ipr": disclosure,})
return render("ipr/submitted.html", context_instance=RequestContext(request))
return render(request, "ipr/submitted.html")
else:
if updates:
@ -555,13 +548,12 @@ def new(request, type, updates=None):
draft_formset = DraftFormset(instance=disclosure, prefix='draft')
rfc_formset = RfcFormset(instance=disclosure, prefix='rfc')
return render("ipr/details_edit.html", {
return render(request, "ipr/details_edit.html", {
'form': form,
'draft_formset':draft_formset,
'rfc_formset':rfc_formset,
'type':type},
context_instance=RequestContext(request)
)
'type':type,
})
@role_required('Secretariat',)
def notify(request, id, type):
@ -597,11 +589,10 @@ def notify(request, id, type):
initial = [ {'type':'msgout','text':m} for m in get_posted_emails(ipr) ]
formset = NotifyFormset(initial=initial)
return render("ipr/notify.html", {
return render(request, "ipr/notify.html", {
'formset': formset,
'ipr': ipr},
context_instance=RequestContext(request)
)
'ipr': ipr,
})
@role_required('Secretariat',)
def post(request, id):
@ -731,44 +722,41 @@ def search(request):
iprs = sorted(iprs, key=lambda x: x.state.order)
else:
iprs = sorted(iprs, key=lambda x: (x.time, x.id), reverse=True)
return render(template, {
return render(request, template, {
"q": q,
"iprs": iprs,
"docs": docs,
"doc": doc,
"form":form,
"states":states},
context_instance=RequestContext(request)
)
"states":states
})
return HttpResponseRedirect(request.path)
else:
form = SearchForm(initial={'state':['all']})
return render("ipr/search.html", {"form":form }, context_instance=RequestContext(request))
return render(request, "ipr/search.html", {"form":form })
def show(request, id):
"""View of individual declaration"""
ipr = get_object_or_404(IprDisclosureBase, id=id).get_child()
if not has_role(request.user, 'Secretariat'):
if ipr.state.slug == 'removed':
return render("ipr/removed.html", {
'ipr': ipr},
context_instance=RequestContext(request)
)
return render(request, "ipr/removed.html", {
'ipr': ipr
})
elif ipr.state.slug != 'posted':
raise Http404
tabs = [('Disclosure','disclosure',urlreverse('ipr_show',kwargs={'id':id}),True),
('History','history',urlreverse('ipr_history',kwargs={'id':id}),True)]
return render("ipr/details_view.html", {
return render(request, "ipr/details_view.html", {
'ipr': ipr,
'tabs':tabs,
'selected':'disclosure'},
context_instance=RequestContext(request)
)
'selected':'disclosure',
})
def showlist(request):
"""List all disclosures by type, posted only"""
@ -781,12 +769,11 @@ def showlist(request):
generic = itertools.chain(generic,nondocspecific)
generic = sorted(generic, key=lambda x: x.time,reverse=True)
return render("ipr/list.html", {
return render(request, "ipr/list.html", {
'generic_disclosures' : generic,
'specific_disclosures': specific,
'thirdpty_disclosures': thirdpty},
context_instance=RequestContext(request)
)
'thirdpty_disclosures': thirdpty,
})
@role_required('Secretariat',)
def state(request, id):
@ -822,8 +809,7 @@ def state(request, id):
else:
form = StateForm(initial={'state':ipr.state.pk,'private':True})
return render('ipr/state.html',dict(ipr=ipr,form=form),
context_instance=RequestContext(request))
return render(request, 'ipr/state.html', dict(ipr=ipr, form=form))
# use for link to update specific IPR
def update(request, id):
@ -832,4 +818,4 @@ def update(request, id):
ipr = get_object_or_404(IprDisclosureBase,id=id)
child = ipr.get_child()
type = class_to_type[child.__class__.__name__]
return new(request, type, updates=id)
return new(request, type, updates=id)

50
ietf/liaisons/fields.py Normal file
View file

@ -0,0 +1,50 @@
import json
from django.utils.html import escape
from django import forms
from django.core.urlresolvers import reverse as urlreverse
from ietf.liaisons.models import LiaisonStatement
def select2_id_liaison_json(objs):
return json.dumps([{ "id": o.pk, "text": escape(o.title) } for o in objs])
class SearchableLiaisonStatementField(forms.IntegerField):
"""Server-based multi-select field for choosing liaison statements using
select2.js."""
def __init__(self, hint_text="Type in title to search for document", *args, **kwargs):
super(SearchableLiaisonStatementField, self).__init__(*args, **kwargs)
self.widget.attrs["class"] = "select2-field"
self.widget.attrs["data-placeholder"] = hint_text
self.widget.attrs["data-max-entries"] = 1
def prepare_value(self, value):
if not value:
value = None
elif isinstance(value, LiaisonStatement):
value = value
else:
value = LiaisonStatement.objects.exclude(approved=None).filter(pk=value).first()
self.widget.attrs["data-pre"] = select2_id_liaison_json([value] if value else [])
# doing this in the constructor is difficult because the URL
# patterns may not have been fully constructed there yet
self.widget.attrs["data-ajax-url"] = urlreverse("ajax_select2_search_liaison_statements")
return value
def clean(self, value):
value = super(SearchableLiaisonStatementField, self).clean(value)
if value == None:
return None
obj = LiaisonStatement.objects.filter(pk=value).first()
if not obj and self.required:
raise forms.ValidationError(u"You must select a value.")
return obj

View file

@ -11,11 +11,13 @@ from ietf.liaisons.accounts import (can_add_outgoing_liaison, can_add_incoming_l
get_person_for_user, is_secretariat, is_sdo_liaison_manager)
from ietf.liaisons.utils import IETFHM
from ietf.liaisons.widgets import (FromWidget, ReadOnlyWidget, ButtonWidget,
ShowAttachmentsWidget, RelatedLiaisonWidget)
ShowAttachmentsWidget)
from ietf.liaisons.models import LiaisonStatement, LiaisonStatementPurposeName
from ietf.liaisons.fields import SearchableLiaisonStatementField
from ietf.group.models import Group, Role
from ietf.person.models import Person, Email
from ietf.doc.models import Document
from ietf.utils.fields import DatepickerDateField
class LiaisonForm(forms.Form):
@ -28,8 +30,9 @@ class LiaisonForm(forms.Form):
technical_contact = forms.CharField(required=False, max_length=255)
cc1 = forms.CharField(widget=forms.Textarea, label="CC", required=False, help_text='Please insert one email address per line')
purpose = forms.ChoiceField()
deadline_date = forms.DateField(label='Deadline')
submitted_date = forms.DateField(label='Submission date', initial=datetime.date.today())
related_to = SearchableLiaisonStatementField(label=u'Related Liaison Statement', required=False)
deadline_date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Deadline', required=True)
submitted_date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Submission date', required=True, initial=datetime.date.today())
title = forms.CharField(label=u'Title')
body = forms.CharField(widget=forms.Textarea, required=False)
attachments = forms.CharField(label='Attachments', widget=ShowAttachmentsWidget, required=False)
@ -40,13 +43,12 @@ class LiaisonForm(forms.Form):
require=['id_attach_title', 'id_attach_file'],
required_label='title and file'),
required=False)
related_to = forms.ModelChoiceField(LiaisonStatement.objects.all(), label=u'Related Liaison', widget=RelatedLiaisonWidget, required=False)
fieldsets = [('From', ('from_field', 'replyto')),
('To', ('organization', 'to_poc')),
('Other email addresses', ('response_contact', 'technical_contact', 'cc1')),
('Purpose', ('purpose', 'deadline_date')),
('References', ('related_to', )),
('Reference', ('related_to', )),
('Liaison Statement', ('title', 'submitted_date', 'body', 'attachments')),
('Add attachment', ('attach_title', 'attach_file', 'attach_button')),
]
@ -80,7 +82,7 @@ class LiaisonForm(forms.Form):
self.initial["title"] = self.instance.title
self.initial["body"] = self.instance.body
self.initial["attachments"] = self.instance.attachments.all()
self.initial["related_to"] = self.instance.related_to_id
self.initial["related_to"] = self.instance.related_to
if "approved" in self.fields:
self.initial["approved"] = bool(self.instance.approved)

View file

@ -0,0 +1,264 @@
# -*- coding: utf-8 -*-
from south.v2 import SchemaMigration
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'LiaisonStatement'
# db.create_table(u'liaisons_liaisonstatement', (
# (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
# ('title', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
# ('purpose', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['name.LiaisonStatementPurposeName'])),
# ('body', self.gf('django.db.models.fields.TextField')(blank=True)),
# ('deadline', self.gf('django.db.models.fields.DateField')(null=True, blank=True)),
# ('related_to', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['liaisons.LiaisonStatement'], null=True, blank=True)),
# ('from_group', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='liaisonstatement_from_set', null=True, to=orm['group.Group'])),
# ('from_name', self.gf('django.db.models.fields.CharField')(max_length=255)),
# ('from_contact', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['person.Email'], null=True, blank=True)),
# ('to_group', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='liaisonstatement_to_set', null=True, to=orm['group.Group'])),
# ('to_name', self.gf('django.db.models.fields.CharField')(max_length=255)),
# ('to_contact', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
# ('reply_to', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
# ('response_contact', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
# ('technical_contact', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
# ('cc', self.gf('django.db.models.fields.TextField')(blank=True)),
# ('submitted', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
# ('modified', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
# ('approved', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
# ('action_taken', self.gf('django.db.models.fields.BooleanField')(default=False)),
# ))
# db.send_create_signal(u'liaisons', ['LiaisonStatement'])
# # Adding M2M table for field attachments on 'LiaisonStatement'
# m2m_table_name = db.shorten_name(u'liaisons_liaisonstatement_attachments')
# db.create_table(m2m_table_name, (
# ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
# ('liaisonstatement', models.ForeignKey(orm[u'liaisons.liaisonstatement'], null=False)),
# ('document', models.ForeignKey(orm[u'doc.document'], null=False))
# ))
# db.create_unique(m2m_table_name, ['liaisonstatement_id', 'document_id'])
pass
def backwards(self, orm):
# # Deleting model 'LiaisonStatement'
# db.delete_table(u'liaisons_liaisonstatement')
# # Removing M2M table for field attachments on 'LiaisonStatement'
# db.delete_table(db.shorten_name(u'liaisons_liaisonstatement_attachments'))
pass
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'doc.document': {
'Meta': {'object_name': 'Document'},
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': u"orm['person.Person']"}),
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['person.Email']", 'symmetrical': 'False', 'through': u"orm['doc.DocumentAuthor']", 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': u"orm['person.Email']"}),
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
},
u'doc.documentauthor': {
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']"}),
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
},
u'doc.state': {
'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'previous_states'", 'blank': 'True', 'to': u"orm['doc.State']"}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.StateType']"}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'doc.statetype': {
'Meta': {'object_name': 'StateType'},
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'})
},
u'group.group': {
'Meta': {'object_name': 'Group'},
'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': u"orm['doc.Document']"}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupTypeName']", 'null': 'True'}),
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
},
u'liaisons.liaisonstatement': {
'Meta': {'object_name': 'LiaisonStatement'},
'action_taken': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'approved': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'attachments': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.Document']", 'symmetrical': 'False', 'blank': 'True'}),
'body': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'cc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'deadline': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'from_contact': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']", 'null': 'True', 'blank': 'True'}),
'from_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'liaisonstatement_from_set'", 'null': 'True', 'to': u"orm['group.Group']"}),
'from_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'purpose': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.LiaisonStatementPurposeName']"}),
'related_to': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['liaisons.LiaisonStatement']", 'null': 'True', 'blank': 'True'}),
'reply_to': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'response_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'submitted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'technical_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'to_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'to_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'liaisonstatement_to_set'", 'null': 'True', 'to': u"orm['group.Group']"}),
'to_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
u'name.doctagname': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.doctypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.groupstatename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.grouptypename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.intendedstdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.liaisonstatementpurposename': {
'Meta': {'ordering': "['order']", 'object_name': 'LiaisonStatementPurposeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.stdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.streamname': {
'Meta': {'ordering': "['order']", 'object_name': 'StreamName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'person.email': {
'Meta': {'object_name': 'Email'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
u'person.person': {
'Meta': {'object_name': 'Person'},
'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['liaisons']

View file

@ -0,0 +1,229 @@
# -*- coding: utf-8 -*-
from south.v2 import DataMigration
class Migration(DataMigration):
def forwards(self, orm):
for l in orm.LiaisonStatement.objects.filter(title=""):
a = l.attachments.all().first()
if a:
l.title = a.title
l.save()
def backwards(self, orm):
pass
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'doc.document': {
'Meta': {'object_name': 'Document'},
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': u"orm['person.Person']"}),
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['person.Email']", 'symmetrical': 'False', 'through': u"orm['doc.DocumentAuthor']", 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': u"orm['person.Email']"}),
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
},
u'doc.documentauthor': {
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']"}),
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
},
u'doc.state': {
'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'previous_states'", 'blank': 'True', 'to': u"orm['doc.State']"}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.StateType']"}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'doc.statetype': {
'Meta': {'object_name': 'StateType'},
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'})
},
u'group.group': {
'Meta': {'object_name': 'Group'},
'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40'}),
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']", 'null': 'True', 'blank': 'True'}),
'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': u"orm['doc.Document']"}),
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupStateName']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupTypeName']", 'null': 'True'}),
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
},
u'liaisons.liaisonstatement': {
'Meta': {'object_name': 'LiaisonStatement'},
'action_taken': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'approved': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'attachments': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.Document']", 'symmetrical': 'False', 'blank': 'True'}),
'body': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'cc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'deadline': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'from_contact': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']", 'null': 'True', 'blank': 'True'}),
'from_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'liaisonstatement_from_set'", 'null': 'True', 'to': u"orm['group.Group']"}),
'from_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'purpose': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.LiaisonStatementPurposeName']"}),
'related_to': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['liaisons.LiaisonStatement']", 'null': 'True', 'blank': 'True'}),
'reply_to': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'response_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'submitted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'technical_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'to_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'to_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'liaisonstatement_to_set'", 'null': 'True', 'to': u"orm['group.Group']"}),
'to_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
u'name.doctagname': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.doctypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.groupstatename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.grouptypename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.intendedstdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.liaisonstatementpurposename': {
'Meta': {'ordering': "['order']", 'object_name': 'LiaisonStatementPurposeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.stdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.streamname': {
'Meta': {'ordering': "['order']", 'object_name': 'StreamName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'person.email': {
'Meta': {'object_name': 'Email'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']", 'null': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
},
u'person.person': {
'Meta': {'object_name': 'Person'},
'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
}
}
complete_apps = ['liaisons']
symmetrical = True

View file

View file

@ -49,4 +49,4 @@ class LiaisonStatement(models.Model):
return slugify("liaison" + " " + self.submitted.strftime("%Y-%m-%d") + " " + frm[:50] + " " + to[:50] + " " + self.title[:115])
def __unicode__(self):
return self.title or u"<no title>"
return self.title

View file

@ -5,7 +5,7 @@ from django.views.generic import RedirectView, TemplateView
urlpatterns = patterns('',
(r'^help/$', TemplateView.as_view(template_name='liaisons/help.html')),
(r'^help/fields/$', TemplateView.as_view(template_name='liaisons/field_help.html')),
url(r'^help/fields/$', TemplateView.as_view(template_name='liaisons/field_help.html'), name="liaisons_field_help"),
(r'^help/from_ietf/$', TemplateView.as_view(template_name='liaisons/guide_from_ietf.html')),
(r'^help/to_ietf/$', TemplateView.as_view(template_name='liaisons/guide_to_ietf.html')),
(r'^managers/$', RedirectView.as_view(url='http://www.ietf.org/liaison/managers.html')),
@ -18,6 +18,6 @@ urlpatterns += patterns('ietf.liaisons.views',
url(r'^for_approval/$', 'liaison_approval_list', name='liaison_approval_list'),
url(r'^for_approval/(?P<object_id>\d+)/$', 'liaison_approval_detail', name='liaison_approval_detail'),
url(r'^add/$', 'add_liaison', name='add_liaison'),
url(r'^ajax/get_info/$', 'get_info'),
url(r'^ajax/liaison_list/$', 'ajax_liaison_list', name='ajax_liaison_list'),
url(r'^ajax/get_info/$', 'ajax_get_liaison_info'),
url(r'^ajax/select2search/$', 'ajax_select2_search_liaison_statements', name='ajax_select2_search_liaison_statements'),
)

View file

@ -16,6 +16,7 @@ from ietf.liaisons.accounts import (get_person_for_user, can_add_outgoing_liaiso
from ietf.liaisons.forms import liaison_form_factory
from ietf.liaisons.utils import IETFHM, can_submit_liaison_required, approvable_liaison_statements
from ietf.liaisons.mails import notify_pending_by_email, send_liaison_by_email
from ietf.liaisons.fields import select2_id_liaison_json
@ -44,7 +45,7 @@ def add_liaison(request, liaison=None):
@can_submit_liaison_required
def get_info(request):
def ajax_get_liaison_info(request):
person = get_person_for_user(request.user)
to_entity_id = request.GET.get('to_entity_id', None)
@ -110,14 +111,20 @@ def liaison_list(request):
"sort": sort,
}, context_instance=RequestContext(request))
def ajax_liaison_list(request):
sort, order_by = normalize_sort(request)
liaisons = LiaisonStatement.objects.exclude(approved=None).order_by(order_by)
def ajax_select2_search_liaison_statements(request):
q = [w.strip() for w in request.GET.get('q', '').split() if w.strip()]
return render_to_response('liaisons/liaison_table.html', {
"liaisons": liaisons,
"sort": sort,
}, context_instance=RequestContext(request))
if not q:
objs = LiaisonStatement.objects.none()
else:
qs = LiaisonStatement.objects.exclude(approved=None).all()
for t in q:
qs = qs.filter(title__icontains=t)
objs = qs.distinct().order_by("-id")[:20]
return HttpResponse(select2_id_liaison_json(objs), content_type='application/json')
@can_submit_liaison_required
def liaison_approval_list(request):

View file

@ -1,12 +1,9 @@
from django.conf import settings
from django.core.urlresolvers import reverse as urlreverse
from django.db.models.query import QuerySet
from django.forms.widgets import Select, Widget, TextInput
from django.forms.widgets import Select, Widget
from django.utils.safestring import mark_safe
from django.utils.html import conditional_escape
from ietf.liaisons.models import LiaisonStatement
class FromWidget(Select):
@ -28,7 +25,7 @@ class FromWidget(Select):
value = option[0]
text = option[1]
base = u'<input type="hidden" value="%s" id="id_%s" name="%s" />%s' % (conditional_escape(value), conditional_escape(name), conditional_escape(name), conditional_escape(text))
base += u' (<a class="from_mailto" href="">' + conditional_escape(self.submitter) + u'</a>)'
base += u' <a class="from_mailto form-control" href="">' + conditional_escape(self.submitter) + u'</a>'
if self.full_power_on:
base += '<div style="display: none;" class="reducedToOptions">'
for from_code in self.full_power_on:
@ -40,9 +37,8 @@ class FromWidget(Select):
class ReadOnlyWidget(Widget):
def render(self, name, value, attrs=None):
html = u'<div id="id_%s">%s</div>' % (conditional_escape(name), conditional_escape(value or ''))
html = u'<div id="id_%s" class="form-control" style="height: auto; min-height: 34px;">%s</div>' % (conditional_escape(name), conditional_escape(value or ''))
return mark_safe(html)
@ -63,7 +59,7 @@ class ButtonWidget(Widget):
html += u'<span style="display: none" class="attachRequiredField">%s</span>' % conditional_escape(i)
required_str = u'Please fill in %s to attach a new file' % conditional_escape(self.required_label)
html += u'<span style="display: none" class="attachDisabledLabel">%s</span>' % conditional_escape(required_str)
html += u'<input type="button" class="addAttachmentWidget" value="%s" />' % conditional_escape(self.label)
html += u'<input type="button" class="addAttachmentWidget btn btn-primary btn-sm" value="%s" />' % conditional_escape(self.label)
return mark_safe(html)
@ -71,8 +67,8 @@ class ShowAttachmentsWidget(Widget):
def render(self, name, value, attrs=None):
html = u'<div id="id_%s">' % name
html += u'<span style="display: none" class="showAttachmentsEmpty">No files attached</span>'
html += u'<div class="attachedFiles">'
html += u'<span style="display: none" class="showAttachmentsEmpty form-control" style="height: auto; min-height: 34px;">No files attached</span>'
html += u'<div class="attachedFiles form-control" style="height: auto; min-height: 34px;">'
if value and isinstance(value, QuerySet):
for attachment in value:
html += u'<a class="initialAttach" href="%s%s">%s</a><br />' % (settings.LIAISON_ATTACH_URL, conditional_escape(attachment.external_url), conditional_escape(attachment.title))
@ -80,32 +76,3 @@ class ShowAttachmentsWidget(Widget):
html += u'No files attached'
html += u'</div></div>'
return mark_safe(html)
class RelatedLiaisonWidget(TextInput):
def render(self, name, value, attrs=None):
if not value:
value = ''
title = ''
noliaison = 'inline'
deselect = 'none'
else:
liaison = LiaisonStatement.objects.get(pk=value)
title = liaison.title
if not title:
attachments = liaison.attachments.all()
if attachments:
title = attachments[0].title
else:
title = 'Liaison #%s' % liaison.pk
noliaison = 'none'
deselect = 'inline'
html = u'<span class="noRelated" style="display: %s;">No liaison selected</span>' % conditional_escape(noliaison)
html += u'<span class="relatedLiaisonWidgetTitle">%s</span>' % conditional_escape(title)
html += u'<input type="hidden" name="%s" class="relatedLiaisonWidgetValue" value="%s" /> ' % (conditional_escape(name), conditional_escape(value))
html += u'<span style="display: none;" class="listURL">%s</span> ' % urlreverse('ajax_liaison_list')
html += u'<div style="display: none;" class="relatedLiaisonWidgetDialog" id="related-dialog" title="Select a liaison"></div> '
html += '<input type="button" id="id_%s" value="Select liaison" /> ' % conditional_escape(name)
html += '<input type="button" style="display: %s;" id="id_no_%s" value="Deselect liaison" />' % (conditional_escape(deselect), conditional_escape(name))
return mark_safe(html)

View file

@ -18,13 +18,13 @@ class MailingListTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q(".group-archives a:contains(\"%s\")" % group.acronym)), 0)
self.assertEqual(len(q(".content-wrapper a:contains(\"%s\")" % group.acronym)), 0)
# successful get
group.list_archive = "https://example.com/foo"
group.save()
r = self.client.get(url)
q = PyQuery(r.content)
self.assertEqual(len(q(".group-archives a:contains(\"%s\")" % group.acronym)), 1)
self.assertEqual(len(q(".content-wrapper a:contains(\"%s\")" % group.acronym)), 1)

View file

@ -39,19 +39,22 @@ class MeetingTests(TestCase):
session = Session.objects.filter(meeting=meeting, group__acronym="mars").first()
slot = TimeSlot.objects.get(scheduledsession__session=session)
time_interval = "%s-%s" % (slot.time.strftime("%H%M"), (slot.time + slot.duration).strftime("%H%M"))
time_interval = "%s-%s" % (slot.time.strftime("%H:%M").lstrip("0"), (slot.time + slot.duration).strftime("%H:%M").lstrip("0"))
# plain
r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number)))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
agenda_content = q("#agenda").html()
agenda_content = q(".content-wrapper").html()
self.assertTrue(session.group.acronym in agenda_content)
self.assertTrue(session.group.name in agenda_content)
self.assertTrue(session.group.parent.acronym.upper() in agenda_content)
self.assertTrue(slot.location.name in agenda_content)
self.assertTrue(time_interval in agenda_content)
# the rest of the results don't have as nicely formatted times
time_interval = time_interval.replace(":", "")
# mobile
r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number)),
{ '_testiphone': "1" })
@ -125,12 +128,11 @@ class MeetingTests(TestCase):
r = self.client.get(urlreverse("ietf.meeting.views.materials", kwargs=dict(meeting_num=meeting.number)))
self.assertEqual(r.status_code, 200)
#debug.show('r.content')
q = PyQuery(r.content)
row = q('.ietf-materials b:contains("%s")' % str(session.group.acronym.upper())).closest("tr")
self.assertTrue(row.find("a:contains(\"Agenda\")"))
self.assertTrue(row.find("a:contains(\"Minutes\")"))
self.assertTrue(row.find("a:contains(\"Slideshow\")"))
row = q('.content-wrapper td:contains("%s")' % str(session.group.acronym)).closest("tr")
self.assertTrue(row.find('a:contains("Agenda")'))
self.assertTrue(row.find('a:contains("Minutes")'))
self.assertTrue(row.find('a:contains("Slideshow")'))
# FIXME: missing tests of .pdf/.tar generation (some code can
# probably be lifted from similar tests in iesg/tests.py)

View file

@ -9,8 +9,7 @@ from ietf.meeting import ajax
urlpatterns = patterns('',
(r'^(?P<meeting_num>\d+)/materials.html$', views.materials),
(r'^agenda/$', views.agenda),
(r'^(?P<base>agenda-utc)(?P<ext>.html)?$', views.agenda),
(r'^agenda(?P<ext>.html)?$', views.agenda),
(r'^agenda(-utc)?(?P<ext>.html)?$', views.agenda),
(r'^agenda(?P<ext>.txt)$', views.agenda),
(r'^agenda(?P<ext>.csv)$', views.agenda),
(r'^agenda/edit$', views.edit_agenda),
@ -28,8 +27,7 @@ urlpatterns = patterns('',
(r'^(?P<num>\d+)/agenda/(?P<owner>[A-Za-z0-9-.+_]+@[A-Za-z0-9._]+)/(?P<name>[A-Za-z0-9-:_]+)/sessions.json$', ajax.scheduledsessions_json),
(r'^(?P<num>\d+)/agenda/(?P<owner>[A-Za-z0-9-.+_]+@[A-Za-z0-9._]+)/(?P<name>[A-Za-z0-9-:_]+).json$', ajax.agenda_infourl),
(r'^(?P<num>\d+)/agenda/edit$', views.edit_agenda),
(r'^(?P<num>\d+)/agenda(?P<ext>.html)?/?$', views.agenda),
(r'^(?P<num>\d+)/(?P<base>agenda-utc)(?P<ext>.html)?/?$', views.agenda),
(r'^(?P<num>\d+)/agenda(-utc)?(?P<ext>.html)?/?$', views.agenda),
(r'^(?P<num>\d+)/requests.html$', RedirectView.as_view(url='/meeting/%(num)s/requests', permanent=True)),
(r'^(?P<num>\d+)/requests$', views.meeting_requests),
(r'^(?P<num>\d+)/agenda(?P<ext>.txt)$', views.agenda),

View file

@ -0,0 +1,220 @@
# -*- coding: utf-8 -*-
from south.v2 import DataMigration
class Migration(DataMigration):
def forwards(self, orm):
orm.IprLicenseTypeName.objects.filter(slug="none-selected").update(desc="[None selected]")
def backwards(self, orm):
orm.IprLicenseTypeName.objects.filter(slug="none-selected").update(desc="")
models = {
u'name.ballotpositionname': {
'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'},
'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.constraintname': {
'Meta': {'ordering': "['order']", 'object_name': 'ConstraintName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'penalty': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.dbtemplatetypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DBTemplateTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.docrelationshipname': {
'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'revname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.docremindertypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.doctagname': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.doctypename': {
'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.draftsubmissionstatename': {
'Meta': {'ordering': "['order']", 'object_name': 'DraftSubmissionStateName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'previous_states'", 'blank': 'True', 'to': u"orm['name.DraftSubmissionStateName']"}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.feedbacktypename': {
'Meta': {'ordering': "['order']", 'object_name': 'FeedbackTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.groupmilestonestatename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupMilestoneStateName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.groupstatename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.grouptypename': {
'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.intendedstdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.iprdisclosurestatename': {
'Meta': {'ordering': "['order']", 'object_name': 'IprDisclosureStateName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.ipreventtypename': {
'Meta': {'ordering': "['order']", 'object_name': 'IprEventTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.iprlicensetypename': {
'Meta': {'ordering': "['order']", 'object_name': 'IprLicenseTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.liaisonstatementpurposename': {
'Meta': {'ordering': "['order']", 'object_name': 'LiaisonStatementPurposeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.meetingtypename': {
'Meta': {'ordering': "['order']", 'object_name': 'MeetingTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.nomineepositionstatename': {
'Meta': {'ordering': "['order']", 'object_name': 'NomineePositionStateName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.rolename': {
'Meta': {'ordering': "['order']", 'object_name': 'RoleName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.roomresourcename': {
'Meta': {'ordering': "['order']", 'object_name': 'RoomResourceName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.sessionstatusname': {
'Meta': {'ordering': "['order']", 'object_name': 'SessionStatusName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.stdlevelname': {
'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.streamname': {
'Meta': {'ordering': "['order']", 'object_name': 'StreamName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
u'name.timeslottypename': {
'Meta': {'ordering': "['order']", 'object_name': 'TimeSlotTypeName'},
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
}
}
complete_apps = ['name']
symmetrical = True

View file

@ -11,13 +11,13 @@ from ietf.dbtemplate.forms import DBTemplateForm
from ietf.group.models import Group, Role
from ietf.ietfauth.utils import role_required
from ietf.name.models import RoleName, FeedbackTypeName, NomineePositionStateName
from ietf.nomcom.models import ( NomCom, Nomination, Nominee, NomineePosition,
from ietf.nomcom.models import ( NomCom, Nomination, Nominee, NomineePosition,
Position, Feedback, ReminderDates )
from ietf.nomcom.utils import (NOMINATION_RECEIPT_TEMPLATE, FEEDBACK_RECEIPT_TEMPLATE,
get_user_email, validate_private_key, validate_public_key,
get_or_create_nominee, create_feedback_email)
from ietf.person.models import Email
from ietf.person.fields import AutocompletedEmailField
from ietf.person.fields import SearchableEmailField
from ietf.utils.fields import MultiEmailField
from ietf.utils.mail import send_mail
@ -207,62 +207,6 @@ class EditMembersFormPreview(FormPreview):
return redirect('nomcom_edit_members', year=self.year)
class EditChairForm(BaseNomcomForm, forms.Form):
chair = forms.EmailField(label="Chair email", required=False,
widget=forms.TextInput(attrs={'size': '40'}))
fieldsets = [('Chair info', ('chair',))]
class EditChairFormPreview(FormPreview):
form_template = 'nomcom/edit_chair.html'
preview_template = 'nomcom/edit_chair_preview.html'
@method_decorator(role_required("Secretariat"))
def __call__(self, request, *args, **kwargs):
year = kwargs['year']
group = get_nomcom_group_or_404(year)
self.state['group'] = group
self.state['rolodex_url'] = ROLODEX_URL
self.group = group
self.year = year
return super(EditChairFormPreview, self).__call__(request, *args, **kwargs)
def get_initial(self, request):
chair = self.group.get_chair()
if chair:
return { "chair": chair.email.address }
return {}
def process_preview(self, request, form, context):
chair_email = form.cleaned_data['chair']
try:
chair_email_obj = Email.objects.get(address=chair_email)
chair_person = chair_email_obj.person
except Email.DoesNotExist:
chair_person = None
chair_email_obj = None
chair_info = {'email': chair_email,
'email_obj': chair_email_obj,
'person': chair_person}
self.state.update({'chair_info': chair_info})
def done(self, request, cleaned_data):
chair_info = self.state['chair_info']
chair_exclude = self.group.role_set.filter(name__slug='chair').exclude(email__address=chair_info['email'])
chair_exclude.delete()
if chair_info['email_obj'] and chair_info['person']:
Role.objects.get_or_create(name=RoleName.objects.get(slug="chair"),
group=self.group,
person=chair_info['person'],
email=chair_info['email_obj'])
return redirect('nomcom_edit_chair', year=self.year)
class EditNomcomForm(BaseNomcomForm, forms.ModelForm):
fieldsets = [('Edit nomcom settings', ('public_key', 'initial_text',
@ -378,7 +322,7 @@ class MergeForm(BaseNomcomForm, forms.Form):
class NominateForm(BaseNomcomForm, forms.ModelForm):
comments = forms.CharField(label="Candidate's Qualifications for the Position:",
comments = forms.CharField(label="Candidate's qualifications for the position",
widget=forms.Textarea())
confirmation = forms.BooleanField(label='Email comments back to me as confirmation',
help_text="If you want to get a confirmation mail containing your feedback in cleartext, \
@ -400,6 +344,7 @@ class NominateForm(BaseNomcomForm, forms.ModelForm):
'candidate_email', 'candidate_phone',
'comments']
self.fields['nominator_email'].label = 'Nominator email'
if self.nomcom:
self.fields['position'].queryset = Position.objects.get_by_nomcom(self.nomcom).opened()
self.fields['comments'].help_text = self.nomcom.initial_text
@ -461,7 +406,7 @@ class NominateForm(BaseNomcomForm, forms.ModelForm):
# send receipt email to nominator
if confirmation:
if author:
subject = 'Nomination Receipt'
subject = 'Nomination receipt'
from_email = settings.NOMCOM_FROM_EMAIL
to_email = author.address
context = {'nominee': nominee.email.person.name,
@ -479,15 +424,15 @@ class NominateForm(BaseNomcomForm, forms.ModelForm):
class FeedbackForm(BaseNomcomForm, forms.ModelForm):
position_name = forms.CharField(label='position',
position_name = forms.CharField(label='Position',
widget=forms.TextInput(attrs={'size': '40'}))
nominee_name = forms.CharField(label='nominee name',
nominee_name = forms.CharField(label='Nominee name',
widget=forms.TextInput(attrs={'size': '40'}))
nominee_email = forms.CharField(label='nominee email',
nominee_email = forms.CharField(label='Nominee email',
widget=forms.TextInput(attrs={'size': '40'}))
nominator_email = forms.CharField(label='commenter email')
nominator_email = forms.CharField(label='Commenter email')
comments = forms.CharField(label='Comments on this candidate',
comments = forms.CharField(label='Comments on this nominee',
widget=forms.Textarea())
confirmation = forms.BooleanField(label='Email comments back to me as confirmation',
help_text="If you want to get a confirmation mail containing your feedback in cleartext, \
@ -656,7 +601,7 @@ class PositionForm(BaseNomcomForm, forms.ModelForm):
fieldsets = [('Position', ('name', 'description',
'is_open', 'incumbent'))]
incumbent = AutocompletedEmailField(required=False)
incumbent = SearchableEmailField(required=False)
class Meta:
model = Position

View file

@ -38,7 +38,7 @@ class NomCom(models.Model):
upload_to=upload_path_handler, blank=True, null=True)
group = models.ForeignKey(Group)
send_questionnaire = models.BooleanField(verbose_name='Send questionnaires automatically"', default=False,
send_questionnaire = models.BooleanField(verbose_name='Send questionnaires automatically', default=False,
help_text='If you check this box, questionnaires are sent automatically after nominations')
reminder_interval = models.PositiveIntegerField(help_text='If the nomcom user sets the interval field then a cron command will \
send reminders to the nominees who have not responded using \

View file

@ -41,12 +41,8 @@ def add_num_nominations(user, position, nominee):
nominees__in=[nominee],
author=author,
type='comment').count()
if count:
mark = """<span style="white-space: pre; color: red;">*</span>"""
else:
mark = """<span style="white-space: pre;"> </span> """
return '<span title="%d earlier comments from you on %s as %s">%s</span>&nbsp;' % (count, nominee.email.address, position, mark)
return '<span class="badge" title="%d earlier comments from you on %s as %s">%s</span>&nbsp;' % (count, nominee.email.address, position, count)
@register.filter
@ -76,7 +72,7 @@ def decrypt(string, request, year, plain=False):
code, out, error = pipe(command % (settings.OPENSSL_COMMAND,
encrypted_file.name), key)
if code != 0:
log("openssl error: %s:\n Error %s: %s" %(command, code, error))
log("openssl error: %s:\n Error %s: %s" %(command, code, error))
os.unlink(encrypted_file.name)

View file

@ -1,4 +1,4 @@
# -*- coding: UTF-8-No-BOM -*-
# -*- coding: utf-8 -*-
import tempfile
import datetime
@ -22,7 +22,7 @@ from ietf.nomcom.test_data import nomcom_test_data, generate_cert, check_comment
from ietf.nomcom.models import NomineePosition, Position, Nominee, \
NomineePositionStateName, Feedback, FeedbackTypeName, \
Nomination
from ietf.nomcom.forms import EditChairForm, EditChairFormPreview, EditMembersForm
from ietf.nomcom.forms import EditMembersForm, EditMembersFormPreview
from ietf.nomcom.utils import get_nomcom_by_year, get_or_create_nominee
from ietf.nomcom.management.commands.send_reminders import Command, is_time_to_send
@ -54,7 +54,6 @@ class NomcomViewsTest(TestCase):
self.private_index_url = reverse('nomcom_private_index', kwargs={'year': self.year})
self.private_merge_url = reverse('nomcom_private_merge', kwargs={'year': self.year})
self.edit_members_url = reverse('nomcom_edit_members', kwargs={'year': self.year})
self.edit_chair_url = reverse('nomcom_edit_chair', kwargs={'year': self.year})
self.edit_nomcom_url = reverse('nomcom_edit_nomcom', kwargs={'year': self.year})
self.private_nominate_url = reverse('nomcom_private_nominate', kwargs={'year': self.year})
self.add_questionnaire_url = reverse('nomcom_private_questionnaire', kwargs={'year': self.year})
@ -214,31 +213,36 @@ class NomcomViewsTest(TestCase):
"primary_email": nominees[0]}
response = self.client.post(self.private_merge_url, test_data)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "info-message-error")
q = PyQuery(response.content)
self.assertTrue(q("form .has-error"))
test_data = {"primary_email": nominees[0],
"secondary_emails": ""}
response = self.client.post(self.private_merge_url, test_data)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "info-message-error")
q = PyQuery(response.content)
self.assertTrue(q("form .has-error"))
test_data = {"primary_email": "",
"secondary_emails": nominees[0]}
response = self.client.post(self.private_merge_url, test_data)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "info-message-error")
q = PyQuery(response.content)
self.assertTrue(q("form .has-error"))
test_data = {"primary_email": "unknown@example.com",
"secondary_emails": nominees[0]}
response = self.client.post(self.private_merge_url, test_data)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "info-message-error")
q = PyQuery(response.content)
self.assertTrue(q("form .has-error"))
test_data = {"primary_email": nominees[0],
"secondary_emails": "unknown@example.com"}
response = self.client.post(self.private_merge_url, test_data)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "info-message-error")
q = PyQuery(response.content)
self.assertTrue(q("form .has-error"))
test_data = {"secondary_emails": """%s,
%s,
@ -247,7 +251,7 @@ class NomcomViewsTest(TestCase):
response = self.client.post(self.private_merge_url, test_data)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "info-message-success")
self.assertContains(response, "alert-success")
self.assertEqual(Nominee.objects.filter(email__address=nominees[1],
duplicated__isnull=False).count(), 1)
@ -293,7 +297,7 @@ class NomcomViewsTest(TestCase):
# preview
self.client.post(self.edit_members_url, test_data)
hash = EditChairFormPreview(EditChairForm).security_hash(None, EditMembersForm(test_data))
hash = EditMembersFormPreview(EditMembersForm).security_hash(None, EditMembersForm(test_data))
test_data.update({'hash': hash, 'stage': 2})
# submit
@ -318,33 +322,6 @@ class NomcomViewsTest(TestCase):
self.check_url_status(self.private_index_url, 403)
self.client.logout()
def change_chair(self, user):
test_data = {'chair': '%s%s' % (user, EMAIL_DOMAIN),
'stage': 1}
# preview
self.client.post(self.edit_chair_url, test_data)
hash = EditChairFormPreview(EditChairForm).security_hash(None, EditChairForm(test_data))
test_data.update({'hash': hash, 'stage': 2})
# submit
self.client.post(self.edit_chair_url, test_data)
def test_edit_chair_view(self):
self.access_secretariat_url(self.edit_chair_url)
self.change_chair(COMMUNITY_USER)
# check chair actions
self.client.login(username=COMMUNITY_USER,password=COMMUNITY_USER+"+password")
self.check_url_status(self.edit_members_url, 200)
self.check_url_status(self.edit_nomcom_url, 200)
self.client.logout()
# revert edit nomcom chair
login_testing_unauthorized(self, SECRETARIAT_USER, self.edit_chair_url)
self.change_chair(CHAIR_USER)
self.client.logout()
def test_edit_nomcom_view(self):
r = self.access_chair_url(self.edit_nomcom_url)
q = PyQuery(r.content)
@ -433,7 +410,8 @@ class NomcomViewsTest(TestCase):
nomcom = get_nomcom_by_year(self.year)
if not nomcom.public_key:
self.assertNotContains(response, "nominateform")
q = PyQuery(response.content)
self.assertEqual(len(q("#nominate-form")), 0)
# save the cert file in tmp
nomcom.public_key.storage.location = tempfile.gettempdir()
@ -441,7 +419,8 @@ class NomcomViewsTest(TestCase):
response = self.client.get(nominate_url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "nominateform")
q = PyQuery(response.content)
self.assertEqual(len(q("#nominate-form")), 1)
position = Position.objects.get(name=position_name)
candidate_email = nominee_email
@ -459,7 +438,8 @@ class NomcomViewsTest(TestCase):
response = self.client.post(nominate_url, test_data)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "info-message-success")
q = PyQuery(response.content)
self.assertContains(response, "alert-success")
# check objects
email = Email.objects.get(address=candidate_email)
@ -526,7 +506,7 @@ class NomcomViewsTest(TestCase):
response = self.client.post(self.add_questionnaire_url, test_data)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "info-message-success")
self.assertContains(response, "alert-success")
## check objects
feedback = Feedback.objects.filter(positions__in=[position],
@ -597,17 +577,18 @@ class NomcomViewsTest(TestCase):
nominee_position = NomineePosition.objects.get(nominee=nominee,
position=position)
state = nominee_position.state
if not state.slug == 'accepted':
if state.slug != 'accepted':
response = self.client.post(feedback_url, test_data)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "info-message-error")
q = PyQuery(response.content)
self.assertTrue(q("form .has-error"))
# accept nomination
nominee_position.state = NomineePositionStateName.objects.get(slug='accepted')
nominee_position.save()
response = self.client.post(feedback_url, test_data)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "info-message-success")
self.assertContains(response, "alert-success")
## check objects
feedback = Feedback.objects.filter(positions__in=[position],

View file

@ -1,8 +1,7 @@
from django.conf.urls import patterns, url
from django.views.generic import TemplateView
from ietf.nomcom.forms import ( EditChairForm, EditChairFormPreview,
EditMembersForm, EditMembersFormPreview )
from ietf.nomcom.forms import EditMembersForm, EditMembersFormPreview
urlpatterns = patterns('ietf.nomcom.views',
url(r'^$', 'index'),
@ -22,7 +21,6 @@ urlpatterns = patterns('ietf.nomcom.views',
# url(r'^(?P<year>\d{4})/private/send-reminder-mail/$', RedirectView.as_view(url=reverse_lazy('nomcom_send_reminder_mail',kwargs={'year':year,'type':'accept'}))),
url(r'^(?P<year>\d{4})/private/send-reminder-mail/(?P<type>\w+)/$', 'send_reminder_mail', name='nomcom_send_reminder_mail'),
url(r'^(?P<year>\d{4})/private/edit-members/$', EditMembersFormPreview(EditMembersForm), name='nomcom_edit_members'),
url(r'^(?P<year>\d{4})/private/edit-chair/$', EditChairFormPreview(EditChairForm), name='nomcom_edit_chair'),
url(r'^(?P<year>\d{4})/private/edit-nomcom/$', 'edit_nomcom', name='nomcom_edit_nomcom'),
url(r'^(?P<year>\d{4})/private/delete-nomcom/$', 'delete_nomcom', name='nomcom_delete_nomcom'),
url(r'^deleted/$', TemplateView.as_view(template_name='nomcom/deleted.html'), name='nomcom_deleted'),

View file

@ -56,7 +56,7 @@ def index(request):
nomcom.ann_url = None
return render_to_response('nomcom/index.html',
{'nomcom_list': nomcom_list,}, RequestContext(request))
def year_index(request, year):
nomcom = get_nomcom_by_year(year)
@ -70,21 +70,21 @@ def year_index(request, year):
def announcements(request):
address_re = re.compile("<.*>")
nomcoms = Group.objects.filter(type="nomcom")
regimes = []
for n in nomcoms:
e = GroupEvent.objects.filter(group=n, type="changed_state", changestategroupevent__state="active").order_by('time')[:1]
n.start_year = e[0].time.year if e else 0
e = GroupEvent.objects.filter(group=n, type="changed_state", changestategroupevent__state="conclude").order_by('time')[:1]
n.end_year = e[0].time.year if e else n.start_year + 1
r = n.role_set.select_related().filter(name="chair")
chair = None
if r:
chair = r[0]
r = n.role_set.select_related().filter(name="chair")
chair = None
if r:
chair = r[0]
announcements = Message.objects.filter(related_groups=n).order_by('-time')
for a in announcements:
@ -311,8 +311,8 @@ def nominate(request, year, public):
template = 'nomcom/private_nominate.html'
if not has_publickey:
message = ('warning', "This Nomcom is not yet accepting nominations")
return render_to_response(template,
message = ('warning', "This Nomcom is not yet accepting nominations")
return render_to_response(template,
{'message': message,
'nomcom': nomcom,
'year': year,
@ -361,17 +361,19 @@ def feedback(request, year, public):
positions = Position.objects.get_by_nomcom(nomcom=nomcom).opened()
if public:
template = 'nomcom/public_feedback.html'
base_template = "nomcom/nomcom_public_base.html"
else:
template = 'nomcom/private_feedback.html'
base_template = "nomcom/nomcom_private_base.html"
if not has_publickey:
message = ('warning', "This Nomcom is not yet accepting comments")
return render_to_response(template,
{'message': message,
'nomcom': nomcom,
'year': year,
'selected': 'feedback'}, RequestContext(request))
return render(request, 'nomcom/feedback.html', {
'message': message,
'nomcom': nomcom,
'year': year,
'selected': 'feedback',
'base_template': base_template
})
message = None
if request.method == 'POST':
@ -385,14 +387,16 @@ def feedback(request, year, public):
form = FeedbackForm(nomcom=nomcom, user=request.user, public=public,
position=position, nominee=nominee)
return render_to_response(template,
{'form': form,
'message': message,
'nomcom': nomcom,
'year': year,
'positions': positions,
'submit_disabled': submit_disabled,
'selected': 'feedback'}, RequestContext(request))
return render(request, 'nomcom/feedback.html', {
'form': form,
'message': message,
'nomcom': nomcom,
'year': year,
'positions': positions,
'submit_disabled': submit_disabled,
'selected': 'feedback',
'base_template': base_template
})
@role_required("Nomcom Chair", "Nomcom Advisor")

View file

@ -8,7 +8,7 @@ import debug # pyflakes:ignore
from ietf.person.models import Email, Person
def tokeninput_id_name_json(objs):
def select2_id_name_json(objs):
def format_email(e):
return escape(u"%s <%s>" % (e.person.name, e.address))
def format_person(p):
@ -16,63 +16,63 @@ def tokeninput_id_name_json(objs):
formatter = format_email if objs and isinstance(objs[0], Email) else format_person
return json.dumps([{ "id": o.pk, "name": formatter(o) } for o in objs])
return json.dumps([{ "id": o.pk, "text": formatter(o) } for o in objs])
class AutocompletedPersonsField(forms.CharField):
"""Tokenizing autocompleted multi-select field for choosing
persons/emails or just persons using jquery.tokeninput.js.
class SearchablePersonsField(forms.CharField):
"""Server-based multi-select field for choosing
persons/emails or just persons using select2.js.
The field operates on either Email or Person models. In the case
of Email models, the person name is shown next to the email address.
of Email models, the person name is shown next to the email
address.
The field uses a comma-separated list of primary keys in a
CharField element as its API, the tokeninput Javascript adds some
selection magic on top of this so we have to pass it a JSON
representation of ids and user-understandable labels."""
CharField element as its API with some extra attributes used by
the Javascript part."""
def __init__(self,
max_entries=None, # max number of selected objs
only_users=False, # only select persons who also have a user
model=Person, # or Email
hint_text="Type in name to search for person",
hint_text="Type in name to search for person.",
*args, **kwargs):
kwargs["max_length"] = 1000
self.max_entries = max_entries
self.only_users = only_users
self.model = model
super(AutocompletedPersonsField, self).__init__(*args, **kwargs)
super(SearchablePersonsField, self).__init__(*args, **kwargs)
self.widget.attrs["class"] = "tokenized-field"
self.widget.attrs["data-hint-text"] = hint_text
self.widget.attrs["class"] = "select2-field"
self.widget.attrs["data-placeholder"] = hint_text
if self.max_entries != None:
self.widget.attrs["data-max-entries"] = self.max_entries
def parse_tokenized_value(self, value):
def parse_select2_value(self, value):
return [x.strip() for x in value.split(",") if x.strip()]
def prepare_value(self, value):
if not value:
value = ""
if isinstance(value, basestring):
pks = self.parse_tokenized_value(value)
pks = self.parse_select2_value(value)
value = self.model.objects.filter(pk__in=pks).select_related("person")
if isinstance(value, self.model):
value = [value]
self.widget.attrs["data-pre"] = tokeninput_id_name_json(value)
self.widget.attrs["data-pre"] = select2_id_name_json(value)
# doing this in the constructor is difficult because the URL
# patterns may not have been fully constructed there yet
self.widget.attrs["data-ajax-url"] = urlreverse("ajax_tokeninput_search", kwargs={ "model_name": self.model.__name__.lower() })
self.widget.attrs["data-ajax-url"] = urlreverse("ajax_select2_search_person_email", kwargs={ "model_name": self.model.__name__.lower() })
if self.only_users:
self.widget.attrs["data-ajax-url"] += "?user=1" # require a Datatracker account
return ",".join(e.address for e in value)
return u",".join(e.address for e in value)
def clean(self, value):
value = super(AutocompletedPersonsField, self).clean(value)
pks = self.parse_tokenized_value(value)
value = super(SearchablePersonsField, self).clean(value)
pks = self.parse_select2_value(value)
objs = self.model.objects.filter(pk__in=pks)
if self.model == Email:
@ -92,32 +92,32 @@ class AutocompletedPersonsField(forms.CharField):
return objs
class AutocompletedPersonField(AutocompletedPersonsField):
"""Version of AutocompletedPersonsField specialized to a single object."""
class SearchablePersonField(SearchablePersonsField):
"""Version of SearchablePersonsField specialized to a single object."""
def __init__(self, *args, **kwargs):
kwargs["max_entries"] = 1
super(AutocompletedPersonField, self).__init__(*args, **kwargs)
super(SearchablePersonField, self).__init__(*args, **kwargs)
def clean(self, value):
return super(AutocompletedPersonField, self).clean(value).first()
return super(SearchablePersonField, self).clean(value).first()
class AutocompletedEmailsField(AutocompletedPersonsField):
"""Version of AutocompletedPersonsField with the defaults right for Emails."""
class SearchableEmailsField(SearchablePersonsField):
"""Version of SearchablePersonsField with the defaults right for Emails."""
def __init__(self, model=Email, hint_text="Type in name or email to search for person and email address",
def __init__(self, model=Email, hint_text="Type in name or email to search for person and email address.",
*args, **kwargs):
super(AutocompletedEmailsField, self).__init__(model=model, hint_text=hint_text, *args, **kwargs)
super(SearchableEmailsField, self).__init__(model=model, hint_text=hint_text, *args, **kwargs)
class AutocompletedEmailField(AutocompletedEmailsField):
"""Version of AutocompletedEmailsField specialized to a single object."""
class SearchableEmailField(SearchableEmailsField):
"""Version of SearchableEmailsField specialized to a single object."""
def __init__(self, *args, **kwargs):
kwargs["max_entries"] = 1
super(AutocompletedEmailField, self).__init__(*args, **kwargs)
super(SearchableEmailField, self).__init__(*args, **kwargs)
def clean(self, value):
return super(AutocompletedEmailField, self).clean(value).first()
return super(SearchableEmailField, self).clean(value).first()

View file

@ -11,7 +11,7 @@ class PersonTests(TestCase):
draft = make_test_data()
person = draft.ad
r = self.client.get(urlreverse("ietf.person.views.ajax_tokeninput_search", kwargs={ "model_name": "email"}), dict(q=person.name))
r = self.client.get(urlreverse("ietf.person.views.ajax_select2_search", kwargs={ "model_name": "email"}), dict(q=person.name))
self.assertEqual(r.status_code, 200)
data = json.loads(r.content)
self.assertEqual(data[0]["id"], person.email_address())

View file

@ -2,6 +2,6 @@ from django.conf.urls import patterns
from ietf.person import ajax
urlpatterns = patterns('',
(r'^search/(?P<model_name>(person|email))/$', "ietf.person.views.ajax_tokeninput_search", None, 'ajax_tokeninput_search'),
(r'^search/(?P<model_name>(person|email))/$', "ietf.person.views.ajax_select2_search", None, 'ajax_select2_search_person_email'),
(r'^(?P<personid>[a-z0-9]+).json$', ajax.person_json),
)

View file

@ -2,9 +2,9 @@ from django.http import HttpResponse
from django.db.models import Q
from ietf.person.models import Email, Person
from ietf.person.fields import tokeninput_id_name_json
from ietf.person.fields import select2_id_name_json
def ajax_tokeninput_search(request, model_name):
def ajax_select2_search(request, model_name):
if model_name == "email":
model = Email
else:
@ -39,6 +39,11 @@ def ajax_tokeninput_search(request, model_name):
if only_users:
objs = objs.exclude(user=None)
objs = objs.distinct()[:10]
try:
page = int(request.GET.get("p", 1)) - 1
except ValueError:
page = 0
return HttpResponse(tokeninput_id_name_json(objs), content_type='application/json')
objs = objs.distinct()[page:page + 10]
return HttpResponse(select2_id_name_json(objs), content_type='application/json')

View file

@ -8,7 +8,7 @@ from ietf.doc.models import Document, DocAlias, State
from ietf.name.models import IntendedStdLevelName, DocRelationshipName
from ietf.group.models import Group
from ietf.person.models import Person, Email
from ietf.person.fields import AutocompletedEmailField
from ietf.person.fields import SearchableEmailField
from ietf.secr.groups.forms import get_person
@ -132,7 +132,7 @@ class EditModelForm(forms.ModelForm):
iesg_state = forms.ModelChoiceField(queryset=State.objects.filter(type='draft-iesg'),required=False)
group = GroupModelChoiceField(required=True)
review_by_rfc_editor = forms.BooleanField(required=False)
shepherd = AutocompletedEmailField(required=False, only_users=True)
shepherd = SearchableEmailField(required=False, only_users=True)
class Meta:
model = Document

View file

@ -2,7 +2,7 @@ from django import forms
from ietf.group.models import Group
from ietf.meeting.models import ResourceAssociation
from ietf.person.fields import AutocompletedPersonsField
from ietf.person.fields import SearchablePersonsField
# -------------------------------------------------
@ -67,7 +67,7 @@ class SessionForm(forms.Form):
wg_selector3 = forms.ChoiceField(choices=WG_CHOICES,required=False)
third_session = forms.BooleanField(required=False)
resources = forms.MultipleChoiceField(choices=[(x.pk,x.desc) for x in ResourceAssociation.objects.all()], widget=forms.CheckboxSelectMultiple,required=False)
bethere = AutocompletedPersonsField(label="Must be present", required=False)
bethere = SearchablePersonsField(label="Must be present", required=False)
def __init__(self, *args, **kwargs):
super(SessionForm, self).__init__(*args, **kwargs)

View file

@ -11,7 +11,7 @@
{% block extrastyle %}{% endblock %}
{% block extrahead %}
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/jquery-1.5.1.min.js"></script>
<script type="text/javascript" src="/facelift/js/lib/jquery-1.11.1.min.js"></script>
{% endblock %}
{% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE" />{% endblock %}

View file

@ -7,9 +7,58 @@
<link rel="stylesheet" type="text/css" href="{{ SECR_STATIC_URL }}css/jquery.ui.autocomplete.css" />
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/jquery-ui-1.8.1.custom.min.js"></script>
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/utils.js"></script>
<link rel="stylesheet" type="text/css" href="/css/token-input.css"></link>
<script type="text/javascript" src="/js/lib/jquery.tokeninput.js"></script>
<script type="text/javascript" src="/js/tokenized-field.js"></script>
<link rel="stylesheet" href="/facelift/css/lib/select2.css">
<link rel="stylesheet" href="/facelift/css/lib/select2-bootstrap.css">
<script src="/facelift/js/lib/select2-3.5.2.min.js"></script>
<script>
// this is copy-pasted from ietf.js, we should include that here too instead of this
$(document).ready(function () {
$(".select2-field").each(function () {
var e = $(this);
if (e.data("ajax-url")) {
var maxEntries = e.data("max-entries");
var multiple = maxEntries != 1;
var prefetched = e.data("pre");
e.select2({
multiple: multiple,
minimumInputLength: 2,
width: "off",
allowClear: true,
maximumSelectionSize: maxEntries,
ajax: {
url: e.data("ajax-url"),
dataType: "json",
quietMillis: 250,
data: function (term, page) {
return {
q: term,
p: page
};
},
results: function (results) {
return {
results: results,
more: results.length == 10
};
}
},
escapeMarkup: function (m) {
return m;
},
initSelection: function (element, cb) {
if (!multiple && prefetched.length > 0)
cb(prefetched[0]);
else
cb(prefetched);
},
dropdownCssClass: "bigdrop"
});
}
});
});
</script>
{% endblock %}
{% block breadcrumbs %}{{ block.super }}

View file

@ -5,9 +5,58 @@
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/utils.js"></script>
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/sessions.js"></script>
<script type="text/javascript" src="/js/lib/jquery.tokeninput.js"></script>
<script type="text/javascript" src="/js/tokenized-field.js"></script>
<link rel="stylesheet" type="text/css" href="/css/token-input.css"></link>
<link rel="stylesheet" href="/facelift/css/lib/select2.css">
<link rel="stylesheet" href="/facelift/css/lib/select2-bootstrap.css">
<script src="/facelift/js/lib/select2-3.5.2.min.js"></script>
<script>
// this is copy-pasted from ietf.js, we should include that here too instead of this
$(document).ready(function () {
$(".select2-field").each(function () {
var e = $(this);
if (e.data("ajax-url")) {
var maxEntries = e.data("max-entries");
var multiple = maxEntries != 1;
var prefetched = e.data("pre");
e.select2({
multiple: multiple,
minimumInputLength: 2,
width: "off",
allowClear: true,
maximumSelectionSize: maxEntries,
ajax: {
url: e.data("ajax-url"),
dataType: "json",
quietMillis: 250,
data: function (term, page) {
return {
q: term,
p: page
};
},
results: function (results) {
return {
results: results,
more: results.length == 10
};
}
},
escapeMarkup: function (m) {
return m;
},
initSelection: function (element, cb) {
if (!multiple && prefetched.length > 0)
cb(prefetched[0]);
else
cb(prefetched);
},
dropdownCssClass: "bigdrop"
});
}
});
});
</script>
{% endblock %}
{% block breadcrumbs %}{{ block.super }}

View file

@ -5,9 +5,58 @@
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/utils.js"></script>
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/sessions.js"></script>
<script type="text/javascript" src="/js/lib/jquery.tokeninput.js"></script>
<script type="text/javascript" src="/js/tokenized-field.js"></script>
<link rel="stylesheet" type="text/css" href="/css/token-input.css"></link>
<link rel="stylesheet" href="/facelift/css/lib/select2.css">
<link rel="stylesheet" href="/facelift/css/lib/select2-bootstrap.css">
<script src="/facelift/js/lib/select2-3.5.2.min.js"></script>
<script>
// this is copy-pasted from ietf.js, we should include that here too instead of this
$(document).ready(function () {
$(".select2-field").each(function () {
var e = $(this);
if (e.data("ajax-url")) {
var maxEntries = e.data("max-entries");
var multiple = maxEntries != 1;
var prefetched = e.data("pre");
e.select2({
multiple: multiple,
minimumInputLength: 2,
width: "off",
allowClear: true,
maximumSelectionSize: maxEntries,
ajax: {
url: e.data("ajax-url"),
dataType: "json",
quietMillis: 250,
data: function (term, page) {
return {
q: term,
p: page
};
},
results: function (results) {
return {
results: results,
more: results.length == 10
};
}
},
escapeMarkup: function (m) {
return m;
},
initSelection: function (element, cb) {
if (!multiple && prefetched.length > 0)
cb(prefetched[0]);
else
cb(prefetched);
},
dropdownCssClass: "bigdrop"
});
}
});
});
</script>
{% endblock %}
{% block breadcrumbs %}{{ block.super }}

View file

@ -14,7 +14,7 @@ except ImportError:
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# a place to put ajax logs if necessary.
LOG_DIR = '/var/log/datatracker'
LOG_DIR = '/var/log/datatracker'
import sys
sys.path.append(os.path.abspath(BASE_DIR + "/.."))
@ -63,9 +63,9 @@ DATABASES = {
}
DATABASE_TEST_OPTIONS = {
# Comment this out if your database doesn't support InnoDB
'init_command': 'SET storage_engine=InnoDB',
}
# Comment this out if your database doesn't support InnoDB
'init_command': 'SET storage_engine=InnoDB',
}
# Local time zone for this installation. Choices can be found here:
# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
@ -87,10 +87,11 @@ USE_I18N = False
USE_TZ = False
MEDIA_URL = 'http://www.ietf.org/'
MEDIA_URL = '//www.ietf.org/'
STATIC_URL = "/"
STATIC_ROOT = os.path.abspath(BASE_DIR + "/../static/")
STATIC_URL = STATIC_ROOT + '/'
WSGI_APPLICATION = "ietf.wsgi.application"
@ -232,8 +233,34 @@ INSTALLED_APPS = (
'ietf.secr.sreq',
'ietf.nomcom',
'ietf.dbtemplate',
# FACELIFT: apps for facelift UI
'widget_tweaks',
'typogrify',
'bootstrap3',
)
# Settings for django-bootstrap3
# See http://django-bootstrap3.readthedocs.org/en/latest/settings.html
BOOTSTRAP3 = {
# Label class to use in horizontal forms
'horizontal_label_class': 'col-md-2',
# Field class to use in horiozntal forms
'horizontal_field_class': 'col-md-10',
# Set HTML required attribute on required fields
'set_required': True,
# Set placeholder attributes to label if no placeholder is provided
'set_placeholder': False,
# Class to indicate required
'form_required_class': 'bootstrap3-required',
# Class to indicate error
'form_error_class': 'bootstrap3-error',
}
INTERNAL_IPS = (
# AMS servers
'64.170.98.32',
@ -292,22 +319,22 @@ INTERNET_DRAFT_ARCHIVE_DIR = '/a/www/www6s/draft-archive'
MEETING_RECORDINGS_DIR = '/a/www/audio'
# Mailing list info URL for lists hosted on the IETF servers
MAILING_LIST_INFO_URL = "https://www.ietf.org/mailman/listinfo/%(list_addr)s"
MAILING_LIST_INFO_URL = "//www.ietf.org/mailman/listinfo/%(list_addr)s"
# Ideally, more of these would be local -- but since we don't support
# versions right now, we'll point to external websites
DOC_HREFS = {
"charter": "http://www.ietf.org/charter/{doc.name}-{doc.rev}.txt",
"draft": "http://www.ietf.org/archive/id/{doc.name}-{doc.rev}.txt",
"slides": "http://www.ietf.org/slides/{doc.name}-{doc.rev}",
"conflrev": "http://www.ietf.org/cr/{doc.name}-{doc.rev}.txt",
"statchg": "http://www.ietf.org/sc/{doc.name}-{doc.rev}.txt",
"charter": "//www.ietf.org/charter/{doc.name}-{doc.rev}.txt",
"draft": "//www.ietf.org/archive/id/{doc.name}-{doc.rev}.txt",
"slides": "//www.ietf.org/slides/{doc.name}-{doc.rev}",
"conflrev": "//www.ietf.org/cr/{doc.name}-{doc.rev}.txt",
"statchg": "//www.ietf.org/sc/{doc.name}-{doc.rev}.txt",
}
MEETING_DOC_HREFS = {
"agenda": "/meeting/{meeting}/agenda/{doc.group.acronym}/",
"minutes": "http://www.ietf.org/proceedings/{meeting}/minutes/{doc.external_url}",
"slides": "http://www.ietf.org/proceedings/{meeting}/slides/{doc.external_url}",
"minutes": "//www.ietf.org/proceedings/{meeting}/minutes/{doc.external_url}",
"slides": "//www.ietf.org/proceedings/{meeting}/slides/{doc.external_url}",
"recording": "{doc.external_url}",
}
@ -332,15 +359,15 @@ IANA_APPROVE_EMAIL = "drafts-approval@icann.org"
# Put real password in settings_local.py
IANA_SYNC_PASSWORD = "secret"
IANA_SYNC_CHANGES_URL = "https://datatracker.iana.org:4443/data-tracker/changes"
IANA_SYNC_PROTOCOLS_URL = "http://www.iana.org/protocols/"
IANA_SYNC_CHANGES_URL = "//datatracker.iana.org:4443/data-tracker/changes"
IANA_SYNC_PROTOCOLS_URL = "//www.iana.org/protocols/"
RFC_TEXT_RSYNC_SOURCE="ftp.rfc-editor.org::rfcs-text-only"
RFC_EDITOR_SYNC_PASSWORD="secret"
RFC_EDITOR_SYNC_NOTIFICATION_URL = "http://www.rfc-editor.org/parser/parser.php"
RFC_EDITOR_QUEUE_URL = "http://www.rfc-editor.org/queue2.xml"
RFC_EDITOR_INDEX_URL = "http://www.rfc-editor.org/rfc/rfc-index.xml"
RFC_EDITOR_SYNC_NOTIFICATION_URL = "//www.rfc-editor.org/parser/parser.php"
RFC_EDITOR_QUEUE_URL = "//www.rfc-editor.org/queue2.xml"
RFC_EDITOR_INDEX_URL = "//www.rfc-editor.org/rfc/rfc-index.xml"
# Liaison Statement Tool settings
LIAISON_UNIVERSAL_FROM = 'Liaison Statement Management Tool <lsmt@' + IETF_DOMAIN + '>'
@ -376,7 +403,7 @@ INTERNET_DRAFT_DAYS_TO_EXPIRE = 185
IDSUBMIT_REPOSITORY_PATH = INTERNET_DRAFT_PATH
IDSUBMIT_STAGING_PATH = '/a/www/www6s/staging/'
IDSUBMIT_STAGING_URL = 'http://www.ietf.org/staging/'
IDSUBMIT_STAGING_URL = '//www.ietf.org/staging/'
IDSUBMIT_IDNITS_BINARY = '/a/www/ietf-datatracker/scripts/idnits'
IDSUBMIT_MAX_PLAIN_DRAFT_SIZE = 6291456 # Max size of the txt draft in bytes
@ -410,7 +437,7 @@ TZDATA_ICS_PATH = BASE_DIR + '/../vzic/zoneinfo/'
CHANGELOG_PATH = '/www/ietf-datatracker/web/changelog'
SECR_BLUE_SHEET_PATH = '/a/www/ietf-datatracker/documents/blue_sheet.rtf'
SECR_BLUE_SHEET_URL = 'https://datatracker.ietf.org/documents/blue_sheet.rtf'
SECR_BLUE_SHEET_URL = '//datatracker.ietf.org/documents/blue_sheet.rtf'
SECR_INTERIM_LISTING_DIR = '/a/www/www6/meeting/interim'
SECR_MAX_UPLOAD_SIZE = 40960000
SECR_PROCEEDINGS_DIR = '/a/www/www6s/proceedings/'
@ -447,9 +474,8 @@ BADNESS_MUCHTOOBIG = 500
SELENIUM_TESTS = False
SELENIUM_TESTS_ONLY = False
# Path to the email alias lists. Used by ietf.utils.aliases
DRAFT_ALIASES_PATH = "/a/postfix/draft-aliases"
DRAFT_VIRTUAL_PATH = "/a/postfix/draft-virtual"
# Set debug apps in DEV_APPS settings_local
DEV_APPS = ()
DRAFT_VIRTUAL_DOMAIN = "virtual.ietf.org"
GROUP_ALIASES_PATH = "/a/postfix/group-aliases"
@ -462,6 +488,9 @@ POSTCONFIRM_PATH = "/a/postconfirm/test-wrapper"
# sensitive or site-specific changes. DO NOT commit settings_local.py to svn.
from settings_local import * # pyflakes:ignore
# Add DEV_APPS to INSTALLED_APPS
INSTALLED_APPS += DEV_APPS
# We provide a secret key only for test and development modes. It's
# absolutely vital that django fails to start in production mode unless a
# secret key has been provided elsewhere, not in this file which is

View file

@ -47,20 +47,20 @@ class UploadForm(forms.Form):
ietf_monday = Meeting.get_ietf_monday()
if now.date() >= (first_cut_off-timedelta(days=settings.CUTOFF_WARNING_DAYS)) and now.date() < first_cut_off:
self.cutoff_warning = ( 'The pre-meeting cut-off date for new documents (i.e., version -00 Internet-Drafts) is %s at %02sh UTC.<br/>' % (first_cut_off, settings.CUTOFF_HOUR) +
'The pre-meeting cut-off date for revisions to existing documents is %s at %02sh UTC.<br/>' % (second_cut_off, settings.CUTOFF_HOUR) )
self.cutoff_warning = ( 'The pre-meeting cut-off date for new documents (i.e., version -00 Internet-Drafts) is %s at %02sh UTC.' % (first_cut_off, settings.CUTOFF_HOUR) +
'The pre-meeting cut-off date for revisions to existing documents is %s at %02sh UTC.' % (second_cut_off, settings.CUTOFF_HOUR) )
elif now.date() >= first_cut_off and now.date() < second_cut_off: # We are in the first_cut_off
if now.date() == first_cut_off and now.hour < settings.CUTOFF_HOUR:
self.cutoff_warning = 'The pre-meeting cut-off date for new documents (i.e., version -00 Internet-Drafts) is %s, at %02sh UTC. After that, you will not be able to submit a new document until %s, at %sh UTC' % (first_cut_off, settings.CUTOFF_HOUR, ietf_monday, settings.CUTOFF_HOUR, )
else: # No 00 version allowed
self.cutoff_warning = 'The pre-meeting cut-off date for new documents (i.e., version -00 Internet-Drafts) was %s at %sh UTC. You will not be able to submit a new document until %s, at %sh UTC.<br>You can still submit a version -01 or higher Internet-Draft until %sh UTC, %s' % (first_cut_off, settings.CUTOFF_HOUR, ietf_monday, settings.CUTOFF_HOUR, settings.CUTOFF_HOUR, second_cut_off, )
self.cutoff_warning = 'The pre-meeting cut-off date for new documents (i.e., version -00 Internet-Drafts) was %s at %sh UTC. You will not be able to submit a new document until %s, at %sh UTC. You can still submit a version -01 or higher Internet-Draft until %sh UTC, %s' % (first_cut_off, settings.CUTOFF_HOUR, ietf_monday, settings.CUTOFF_HOUR, settings.CUTOFF_HOUR, second_cut_off, )
self.in_first_cut_off = True
elif now.date() >= second_cut_off and now.date() < ietf_monday:
if now.date() == second_cut_off and now.hour < settings.CUTOFF_HOUR: # We are in the first_cut_off yet
self.cutoff_warning = 'The pre-meeting cut-off date for new documents (i.e., version -00 Internet-Drafts) was %s at %02sh UTC. You will not be able to submit a new document until %s, at %02sh UTC.<br>The I-D submission tool will be shut down at %02sh UTC today, and reopened at %02sh UTC on %s' % (first_cut_off, settings.CUTOFF_HOUR, ietf_monday, settings.CUTOFF_HOUR, settings.CUTOFF_HOUR, settings.CUTOFF_HOUR, ietf_monday)
self.cutoff_warning = 'The pre-meeting cut-off date for new documents (i.e., version -00 Internet-Drafts) was %s at %02sh UTC. You will not be able to submit a new document until %s, at %02sh UTC. The I-D submission tool will be shut down at %02sh UTC today, and reopened at %02sh UTC on %s' % (first_cut_off, settings.CUTOFF_HOUR, ietf_monday, settings.CUTOFF_HOUR, settings.CUTOFF_HOUR, settings.CUTOFF_HOUR, ietf_monday)
self.in_first_cut_off = True
else: # Completely shut down of the tool
self.cutoff_warning = 'The cut-off time for the I-D submission was %02dh UTC, %s.<br>The I-D submission tool will be reopened at %02dh local time at the IETF meeting location, %s.' % (settings.CUTOFF_HOUR, second_cut_off, settings.CUTOFF_HOUR, ietf_monday)
self.cutoff_warning = 'The cut-off time for the I-D submission was %02dh UTC, %s. The I-D submission tool will be reopened at %02dh local time at the IETF meeting location, %s.' % (settings.CUTOFF_HOUR, second_cut_off, settings.CUTOFF_HOUR, ietf_monday)
self.shutdown = True
def clean_file(self, field_name, parser_class):
@ -116,7 +116,7 @@ class UploadForm(forms.Form):
# check existing
existing = Submission.objects.filter(name=self.parsed_draft.filename, rev=self.parsed_draft.revision).exclude(state__in=("posted", "cancel"))
if existing:
raise forms.ValidationError(mark_safe('Submission with same name and revision is currently being processed. <a href="%s">Check the status here</a>' % urlreverse("submit_submission_status", kwargs={ 'submission_id': existing[0].pk })))
raise forms.ValidationError(mark_safe('Submission with same name and revision is currently being processed. <a href="%s">Check the status here.</a>' % urlreverse("submit_submission_status", kwargs={ 'submission_id': existing[0].pk })))
# cut-off
if self.parsed_draft.revision == '00' and self.in_first_cut_off:
@ -239,7 +239,7 @@ class EditSubmissionForm(forms.ModelForm):
pages = forms.IntegerField(required=True)
abstract = forms.CharField(widget=forms.Textarea, required=True)
note = forms.CharField(label=mark_safe(u'Comment to<br/> the Secretariat'), widget=forms.Textarea, required=False)
note = forms.CharField(label=mark_safe(u'Comment to the Secretariat'), widget=forms.Textarea, required=False)
class Meta:
model = Submission

View file

@ -17,7 +17,7 @@ def show_submission_files(context, submission):
exists = True
elif submission.state_id == "posted":
continue
result.append({'name': '[%s version]' % ext[1:].upper(),
result.append({'name': '%s' % ext[1:],
'exists': exists,
'url': '%s%s-%s%s' % (settings.IDSUBMIT_STAGING_URL, submission.name, submission.rev, ext)})
return {'files': result}
@ -27,15 +27,16 @@ def show_submission_files(context, submission):
def two_pages_decorated_with_errors(submission, errors):
pages = submission.first_two_pages or ''
if 'rev' not in errors.keys():
return mark_safe('<pre class="twopages">%s</pre>' % escape(pages))
result = '<pre class="twopages">\n'
return mark_safe('<pre>%s</pre>' % escape(pages))
result = '<pre>\n'
for line in pages.split('\n'):
if line.find('%s-%s' % (submission.name, submission.rev)) > -1:
result += '</pre><pre class="twopages" style="background: red;">'
result += '<div class="bg-danger"><b>'
result += escape(line)
result += '\n'
result += '</pre><pre class="twopages">\n'
result += '</b></div>\n'
else:
result += escape(line)
result += '\n'
result += '</pre>pre>\n'
return mark_safe(result)

View file

@ -89,7 +89,7 @@ class SubmitTests(TestCase):
# check the page
r = self.client.get(status_url)
q = PyQuery(r.content)
post_button = q('input[type=submit][value*="Post"]')
post_button = q('[type=submit]:contains("Post")')
self.assertEqual(len(post_button), 1)
action = post_button.parents("form").find('input[type=hidden][name="action"]').val()
@ -142,7 +142,7 @@ class SubmitTests(TestCase):
r = self.client.get(status_url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
approve_button = q('input[type=submit][value*="Approve"]')
approve_button = q('[type=submit]:contains("Approve")')
self.assertEqual(len(approve_button), 1)
action = approve_button.parents("form").find('input[type=hidden][name="action"]').val()
@ -237,7 +237,7 @@ class SubmitTests(TestCase):
# go to confirm page
r = self.client.get(confirm_url)
q = PyQuery(r.content)
self.assertEqual(len(q('input[type=submit][value*="Confirm"]')), 1)
self.assertEqual(len(q('[type=submit]:contains("Confirm")')), 1)
# confirm
mailbox_before = len(outbox)
@ -305,7 +305,7 @@ class SubmitTests(TestCase):
# go to confirm page
r = self.client.get(confirm_url)
q = PyQuery(r.content)
self.assertEqual(len(q('input[type=submit][value*="Confirm"]')), 1)
self.assertEqual(len(q('[type=submit]:contains("Confirm")')), 1)
# confirm
mailbox_before = len(outbox)
@ -363,10 +363,10 @@ class SubmitTests(TestCase):
r = self.client.get(status_url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
cancel_button = q('input[type=submit][value*="Cancel"]')
cancel_button = q('[type=submit]:contains("Cancel")')
self.assertEqual(len(cancel_button), 1)
action = cancel_button.parents("form").find("input[type=hidden][name=\"action\"]").val()
action = cancel_button.parents("form").find('input[type=hidden][name="action"]').val()
# cancel
r = self.client.post(status_url, dict(action=action))
@ -385,7 +385,7 @@ class SubmitTests(TestCase):
r = self.client.get(status_url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
adjust_button = q('input[type=submit][value*="Adjust"]')
adjust_button = q('[type=submit]:contains("Adjust")')
self.assertEqual(len(adjust_button), 1)
action = adjust_button.parents("form").find('input[type=hidden][name="action"]').val()
@ -447,7 +447,7 @@ class SubmitTests(TestCase):
r = self.client.get(status_url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
post_button = q('input[type=submit][value*="Force"]')
post_button = q('[type=submit]:contains("Force")')
self.assertEqual(len(post_button), 1)
action = post_button.parents("form").find('input[type=hidden][name="action"]').val()
@ -482,16 +482,16 @@ class SubmitTests(TestCase):
# status page as unpriviliged => no edit button
r = self.client.get(unprivileged_status_url)
self.assertEqual(r.status_code, 200)
self.assertTrue(("status of submission of %s" % name) in r.content.lower())
self.assertTrue(("submission status of %s" % name) in r.content.lower())
q = PyQuery(r.content)
adjust_button = q('input[type=submit][value*="Adjust"]')
adjust_button = q('[type=submit]:contains("Adjust")')
self.assertEqual(len(adjust_button), 0)
# as Secretariat, we should get edit button
self.client.login(username="secretary", password="secretary+password")
r = self.client.get(unprivileged_status_url)
q = PyQuery(r.content)
adjust_button = q('input[type=submit][value*="Adjust"]')
adjust_button = q('[type=submit]:contains("Adjust")')
self.assertEqual(len(adjust_button), 1)
action = adjust_button.parents("form").find('input[type=hidden][name="action"]').val()
@ -521,13 +521,13 @@ class SubmitTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
request_button = q('input[type=submit][value*="Request full access"]')
request_button = q('[type=submit]:contains("Request full access")')
self.assertEqual(len(request_button), 1)
# request URL to be sent
mailbox_before = len(outbox)
action = request_button.parents("form").find("input[type=hidden][name=\"action\"]").val()
action = request_button.parents("form").find('input[type=hidden][name="action"]').val()
r = self.client.post(url, dict(action=action))
self.assertEqual(r.status_code, 200)
@ -650,12 +650,13 @@ class ApprovalsTestCase(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('input[type=submit]')), 1)
self.assertEqual(len(q('[type=submit]:contains("Save")')), 1)
# faulty post
r = self.client.post(url, dict(name="draft-test-nonexistingwg-something"))
self.assertEqual(r.status_code, 200)
self.assertTrue("errorlist" in r.content)
q = PyQuery(r.content)
self.assertTrue(len(q("form .has-error")) > 0)
# add
name = "draft-ietf-mars-foo"
@ -676,7 +677,7 @@ class ApprovalsTestCase(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('input[type=submit]')), 1)
self.assertEqual(len(q('[type=submit]:contains("Cancel")')), 1)
# cancel
r = self.client.post(url, dict(action="cancel"))

View file

@ -39,7 +39,7 @@ def validate_submission(submission):
for ext in submission.file_types.split(','):
source = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s%s' % (submission.name, submission.rev, ext))
if not os.path.exists(source):
errors['files'] = '"%s" was not found in the staging area.<br />We recommend you that you cancel this submission and upload your files again.' % os.path.basename(source)
errors['files'] = '"%s" was not found in the staging area. We recommend you that you cancel this submission and upload your files again.' % os.path.basename(source)
break
if not submission.title:

View file

@ -1,9 +1,9 @@
{# Copyright The IETF Trust 2012, All Rights Reserved #}
{% extends "base.html" %}
{% extends "ietf.html" %}
{% block title %}401 Unauthorized{% endblock %}
{% block content %}
<img src="/images/ietflogo2e.png" height="80" width="140"/>
<img class="ietflogo" src="/images/ietflogo.png" alt="IETF" style="width: 10em">
<h2>Authentication Required</h2>

View file

@ -1,9 +1,9 @@
{# Copyright The IETF Trust 2007, All Rights Reserved #}
{% extends "base.html" %}
{% extends "ietf.html" %}
{% block title %}404 Not Found{% endblock %}
{% block content %}
<img src="/images/ietflogo2e.png" height="80" width="140"/>
<img class="ietflogo" src="/images/ietflogo.png" alt="IETF" style="width: 10em">
<h2>The page you were looking for couldn't be found.</h2>

View file

@ -1,9 +1,9 @@
{# Copyright The IETF Trust 2007, All Rights Reserved #}
{% extends "base.html" %}
{% extends "ietf.html" %}
{% block title %}500 Internal Server Error{% endblock %}
{% block content %}
<img src="/images/ietflogo2e.png" height="80" width="140">
<img class="ietflogo" src="/images/ietflogo.png" alt="IETF" style="width: 10em">
<h2>Internal Server Error.</h2>

View file

@ -0,0 +1,153 @@
{% load wg_menu %}
{% load streams_menu %}
{% load ietf_filters community_tags %}
<li {% if flavor == "top" %}class="dropdown"{% else %}class="nav-header"{% endif %}>
{% if flavor == "top" %}<a href="#" class="dropdown-toggle" data-toggle="dropdown">{% endif %}
{#<span class="fa fa-user"></span>#}
{% if user.is_authenticated %} {{ user }} {% else %} User {% endif %}
{% if flavor == "top" %}
</a>
<ul class="dropdown-menu" role="menu">
{% endif %}
{% if request.get_full_path == "/accounts/logout/" %}
<li><a rel="nofollow" href="/accounts/login{% if "/accounts/logout/" not in request.get_full_path %}/?next={{request.get_full_path|urlencode}}{% endif %}">Sign in</a></li>
{% else %}
{% if user.is_authenticated %}
<li><a rel="nofollow" href="/accounts/logout/" >Sign out</a></li>
<li><a rel="nofollow" href="/accounts/profile/">Edit profile</a></li>
{% else %}
<li><a rel="nofollow" href="/accounts/login/?next={{request.get_full_path|urlencode}}">Sign in</a></li>
{% endif %}
{% endif %}
<li><a href="{% url "account_index" %}">{% if request.user.is_authenticated %}Manage account{% else %}New account{% endif %}</a></li>
<li><a href="{%url "ietf.cookies.views.settings" %}" rel="nofollow">Settings</a></li>
{% if user|has_role:"Area Director" %}
{% if flavor == "top" %}<li class="divider hidden-xs"></li>{% endif %}
<li {%if flavor == "top" %}class="dropdown-header hidden-xs"{% else %}class="nav-header"{% endif %}>AD dashboard</li>
<li><a href="{% url "docs_for_ad" name=user.person.full_name_as_key %}">My docs</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 "ietf.iesg.views.milestones_needing_review" %}">Milestone review</a></li>
{% endif %}
{% if user|has_role:"Secretariat" %}
{% if flavor == "top" %}<li class="divider hidden-xs"></li>{% endif %}
<li {%if flavor == "top" %}class="dropdown-header hidden-xs"{% else %}class="nav-header"{% endif %}>IETF secretariat</li>
<li><a href="/admin/iesg/telechatdate/">Telechat dates</a></li>
<li><a href="/admin/iesg/telechatagendaitem/">Management items</a></li>
<li><a href="{% url "ietf.iesg.views.milestones_needing_review" %}">Milestones</a></li>
<li><a href="{% url "ietf.sync.views.discrepancies" %}">Sync discrepancies</a>
{% endif %}
{% if user|has_role:"IANA" %}
{% if flavor == "top" %}<li class="divider hidden-xs"></li>{% endif %}
<li {%if flavor == "top" %}class="dropdown-header hidden-xs"{% else %}class="nav-header"{% endif %}>IANA</li>
<li><a href="{% url "ietf.sync.views.discrepancies" %}">Sync discrepancies</a></li>
{% endif %}
{% if user|has_role:"RFC Editor" %}
{% if flavor == "top" %}<li class="divider hidden-xs"></li>{% endif %}
<li {%if flavor == "top" %}class="dropdown-header hidden-xs"{% else %}class="nav-header"{% endif %}>RFC Editor</li>
<li><a href="{% url "ietf.sync.views.discrepancies" %}">Sync discrepancies</a></li>
{% endif %}
{% if flavor == "top" %}</ul>{% endif %}
<li {% if flavor == "top" %}class="dropdown"{% else %}class="nav-header"{% endif %}>
{% if flavor == "top" %}<a href="#" class="dropdown-toggle" data-toggle="dropdown">{% endif %}
{#<span class="fa fa-users"></span>#}
Groups
{% if flavor == "top" %}
</a>
<ul class="dropdown-menu" role="menu">
{% endif %}
<li><a href="{% url "ietf.group.info.active_groups" group_type="wg" %}">Active WGs</a></li>
<li><a href="{% url "ietf.group.info.active_groups" group_type="rg" %}">Active RGs</a></li>
{% if flavor == "top" %}<li class="divider visible-lg-block"></li>{% endif %}
<li {%if flavor == "top" %}class="dropdown-header visible-lg-block"{% else %}class="nav-header hidden-nojs"{% endif %}>By area/parent</li>
{% wg_menu "facelift" %}
<li class="hidden-lg hidden-nojs"><a href="#" data-toggle="modal" data-target="#navmodal">Jump to group</a></li>
{% if flavor == "top" %}<li class="divider hidden-xs"></li>{% endif %}
<li {%if flavor == "top" %}class="dropdown-header hidden-xs"{% else %}class="nav-header"{% endif %}>New work</li>
<li><a href="{% url "ietf.group.info.chartering_groups" %}">Chartering WGs</a></li>
<li><a href="{% url "ietf.group.info.bofs" group_type="wg" %}">BOFs</a></li>
{% if flavor == "top" %}<li class="divider hidden-xs"></li>{% endif %}
<li {%if flavor == "top" %}class="dropdown-header hidden-xs"{% else %}class="nav-header"{% endif %}>Other groups</li>
<li><a href="{% url "ietf.group.info.concluded_groups" %}">Concluded WGs</a></li>
<li><a href="//www.ietf.org/list/nonwg.html">Non-WG lists</a></li>
{% if flavor == "top" %}</ul>{% endif %}
<li {% if flavor == "top" %}class="dropdown"{% else %}class="nav-header"{% endif %}>
{% if flavor == "top" %}<a href="#" class="dropdown-toggle" data-toggle="dropdown">{% endif %}
{#<span class="fa fa-file-text"></span>#}
Documents
{% if flavor == "top" %}
</a>
<ul class="dropdown-menu" role="menu">
{% endif %}
<li><a href="{% url "submit_upload_submission" %}">Submit a draft</a></li>
{% if user|has_role:"WG Chair" %}
{% if flavor == "top" %}<li class="divider hidden-xs"></li>{% endif %}
<li {%if flavor == "top" %}class="dropdown-header hidden-xs"{% else %}class="nav-header"{% endif %}>WG chair</li>
<li><a href="{% url "submit_approvals" %}">Approve a draft</a></li>
{% endif %}
{% get_user_managed_lists user as community_lists %}
{% if community_lists %}
<li><a href="{{ community_lists.personal.get_manage_url }}">My tracked docs</a></li>
{% for cl in community_lists.group %}
<li><a href="{{ cl.get_manage_url }}">WG {{ cl.short_name }} docs</a></li>
{% endfor %}
{% else %}
<li><a rel="nofollow" href="/accounts/login/?next={{request.get_full_path|urlencode}}">Sign in to track docs</a></li>
{% endif %}
{% if user|has_role:"Area Director,Secretariat" %}
{% if flavor == "top" %}<li class="divider hidden-xs"></li>{% endif %}
<li><a href="{% url "rfc_status_changes" %}">RFC status changes</a></li>
{% endif %}
{% if flavor == "top" %}<li class="divider hidden-xs"></li>{% endif %}
<li {%if flavor == "top" %}class="dropdown-header hidden-xs"{% else %}class="nav-header"{% endif %}>RFC streams</li>
<li><a href="{% url "ietf.group.views_stream.streams" %}iab/">IAB</a></li>
<li><a href="{% url "ietf.group.views_stream.streams" %}irtf/">IRTF</a></li>
<li><a href="{% url "ietf.group.views_stream.streams" %}ise/">ISE</a></li>
{% if flavor == "top" %}</ul>{% endif %}
<li {% if flavor == "top" %}class="dropdown"{% else %}class="nav-header"{% endif %}>
{% if flavor == "top" %}<a href="#" class="dropdown-toggle" data-toggle="dropdown">{% endif %}
{#<span class="fa fa-comments"></span>#}
Meetings
{% if flavor == "top" %}
</a>
<ul class="dropdown-menu" role="menu">
{% endif %}
<li><a href="/meeting/agenda/">Agenda</a></li>
<li><a href="/meeting/">Materials</a></li>
<li><a href="//www.ietf.org/meeting/proceedings.html">Past proceedings</a></li>
<li><a href="//www.ietf.org/meeting/upcoming.html">Upcoming</a></li>
<li><a href="/meeting/requests">Session requests</a></li>
{% if flavor == "top" %}</ul>{% endif %}
<li {% if flavor == "top" %}class="dropdown"{% else %}class="nav-header"{% endif %}>
{% if flavor == "top" %}<a href="#" class="dropdown-toggle" data-toggle="dropdown">{% endif %}
{#<span class="fa fa-folder"></span>#}
Other
{% if flavor == "top" %}
</a>
<ul class="dropdown-menu" role="menu">
{% endif %}
<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>
{% if flavor == "top" %}</ul>{% endif %}

View file

@ -0,0 +1,19 @@
{% for area in areas %}
<li class="hidden-nojs dropdown-submenu visible-lg-block">
<a href="/wg/#{{ area.acronym }}">{{ area.short_area_name }}</a>
<ul class="dropdown-menu" role="menu">
{% for g in area.active_groups %}
<li><a href="{% url "ietf.group.info.group_home" group_type=g.type_id acronym=g.acronym %}">{{ g.acronym }} &mdash; {{ g.name}}</a></li>
{% endfor %}
</ul>
</li>
{% endfor %}
<li class="hidden-nojs dropdown-submenu visible-lg-block">
<a href="">IRTF</a>
<ul class="dropdown-menu" role="menu">
{% for group in rgs %}
<li><a href="{% url "ietf.group.info.group_home" group_type=group.type_id acronym=group.acronym %}">{{ group.acronym }} &mdash; {{ group.name}}</a></li>
{% endfor %}
</ul>
</li>

View file

@ -0,0 +1,42 @@
<div class="table-responsive">
{% spaceless %}
<table class="table table-condensed">
<thead>
<tr>
{% for area in areas %}
<th class="text-center">{{area.acronym|upper}}</th>
{% endfor %}
<th class="text-center">IRTF</th>
</tr>
</thead>
<tbody>
<tr>
{% for area in areas %}
<td>
<div class="btn-group-vertical btn-block">
{% for group in area.active_groups %}
<div class="btn-group btn-group-xs btn-group-justified">
<a href="{% url "ietf.group.info.group_home" group_type=group.type_id acronym=group.acronym %}" class="btn btn-default">
{{group.acronym}}
</a>
</div>
{% endfor %}
</div>
</td>
{% endfor %}
<td>
<div class="btn-group-vertical btn-block">
{% for group in rgs %}
<div class="btn-group btn-group-xs btn-group-justified">
<a href="{% url "ietf.group.info.group_home" group_type=group.type_id acronym=group.acronym %}" class="btn btn-default">
{{group.acronym}}
</a>
</div>
{% endfor %}
</div>
</td>
</tr>
</tbody>
</table>
{% endspaceless %}
</div>

View file

@ -1,18 +1,24 @@
<h2>Display customization</h2>
{% load bootstrap3 %}
<form action="#custom" method="post" />{% csrf_token %}
<h3>Sort method</h2>
{{ display_form.sort_method }}
{% bootstrap_messages %}
<h3>Show fields</h2>
<div>
{% for field in dc.get_display_fields_config %}
<div style="float: left; width: 30%;">
<input id="id_{{ field.codename }}" type="checkbox" name="{{ field.codename }}"{% if field.active %} checked="checked"{% endif %} />
<label for="id_{{ field.codename }}">{{ field.description }}</label>
</div>
{% endfor %}
</div>
<div style="clear: both;"><br /></div>
<input type="submit" value="Save configuration" name="save_display" />
<form role="form" method="post" action="#custom">
{% csrf_token %}
{% bootstrap_form display_form %}
<div class="form-group">
<label>Show fields</label>
{% for field in dc.get_display_fields_config %}
<div class="checkbox">
<label for="id_{{ field.codename }}">
<input id="id_{{ field.codename }}" type="checkbox" name="{{ field.codename }}" {% if field.active %}checked{% endif %}>
{{ field.description }}
</label>
</div>
{% endfor %}
</div>
{% buttons %}
<input type="submit" class="btn btn-primary" name="save_display">
{% endbuttons %}
</form>

View file

@ -1,124 +1,130 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% load bootstrap3 %}
{% block title %}{{ cl.long_name }}{% endblock %}
{% block content %}
<h1>{{ cl.long_name }}</h1>
<div id="mytabs" class="yui-navset">
<ul class="yui-nav">
<li class="selected"><a href="#view"><em>Documents</em></a></li>
<li><a href="#documents"><em>Explicitly added</em></a></li>
<li><a href="#rules"><em>Rules</em></a></li>
<li><a href="#custom"><em>Display customization</em></a></li>
<li><a href="#info"><em>Exports</em></a></li>
{% bootstrap_messages %}
<ul class="nav nav-tabs nav-memory">
<li class="active"><a href="#view" data-toggle="tab">Documents</a></li>
<li><a href="#documents" data-toggle="tab">Explicitly added</a></li>
<li><a href="#rules" data-toggle="tab">Rules</a></li>
<li><a href="#custom" data-toggle="tab">Display customization</a></li>
<li><a href="#info" data-toggle="tab">Exports</a></li>
</ul>
<div class="yui-content">
<p></p>
<div id="view">
{% include "community/view_list.html" %}
<div class="tab-content">
<div class="tab-pane active" id="view">
{% include "community/view_list.html" %}
</div>
<div class="tab-pane" id="documents">
<p>
In order to add some individual documents to your list, you have to:
</p>
<ul>
<li>Search for the document or documents you want to add using the datatracker search form.</li>
<li>In the search results, you'll find a link to add individual documents to your list.</li>
</ul>
<a class="btn btn-default" href="/doc/search">Search documents</a>
<table class="table table-condensed table-striped">
<thead>
<tr>
<th>Name</th><th>State</th><th>Title</th><th></th>
</tr>
</thead>
<tbody>
{% for doc in cl.added_ids.all %}
<tr>
<td>{{ doc.display_name }}</td>
<td>{{ doc.get_state }}</td>
<td><a href="{{ doc.get_absolute_url }}">{{ doc.title }}</a></td>
<td><a class="btn btn-danger btn-xs" href="{% url "community_remove_document" cl.pk doc.pk %}">Remove</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="tab-pane" id="rules">
<table class="table table-condensed table-striped">
<thead>
<tr><th>Rule</th><th>Value</th><th>Documents</th><th></th></tr>
</thead>
<tbody>
{% for rule in cl.rule_set.all %}
{% with rule.get_callable_rule as callable %}
<tr>
<td>{{ callable.description }}</td>
<td>{{ callable.show_value }}</td>
<td>{% with rule.cached_ids.count as count %}{{ count }} document{{ count|pluralize }}{% endwith %}</td>
<td><a class="btn btn-danger btn-xs" href="{% url "community_remove_rule" cl.pk rule.pk %}">Remove</a></td>
</tr>
{% endwith %}
{% endfor %}
</tbody>
</table>
<h3>Add a new rule</h3>
<form role="form" method="post">
{% csrf_token %}
{% bootstrap_form rule_form %}
{% buttons %}
<input type="submit" class="btn btn-primary" name="save_rule" value="Add rule">
{% endbuttons %}
</form>
</div>
<div class="tab-pane" id="custom">
{% include "community/customize_display.html" %}
</div>
<div class="tab-pane" id="info">
<p>Feel free to share the following links if you need to:</p>
<ul>
<li><a href="{{ cl.secret }}/view/">Read only view for {{ cl.long_name }}</a></li>
<li><a href="{{ cl.secret }}/changes/feed/">Feed for every change in status of {{ cl.long_name }}</a></li>
<li><a href="{{ cl.secret }}/changes/significant/feed/">Feed for significant change in status of {{ cl.long_name }}</a></li>
<li><a href="{{ cl.secret }}/subscribe/">Subscribe to the mailing list for every change in status of {{ cl.long_name }}</a></li>
<li><a href="{{ cl.secret }}/subscribe/significant/">Subscribe to the mailing list for significant change in status of {{ cl.long_name }}</a></li>
</ul>
<p>Export your list to CSV format:</p>
<ul>
<li><a href="csv/">CSV for {{ cl.long_name }}</a></li>
<li><a href="{{ cl.secret }}/csv/">Read only CSV for {{ cl.long_name }}</a></li>
</ul>
</div>
</div>
<div id="documents">
<h2>Documents explicitly included, from a document search.</a></h2>
<p>
In order to add some individual documents to your list you have to:
</p>
<ul>
<li>Search for the document or documents you want to add using the datatracker search form.</li>
<li>In the search result you'll find a link to add individual documents to your list.</li>
</ul>
<p>
<a href="/doc/search"><b>Go to the Search Form to search for and add specific documents</b></a>
</p>
<table class="ietf-table">
<tr><th>Name</th><th>State</th><th>Title</th><th>Remove from list</th></tr>
{% for doc in cl.added_ids.all %}
<tr class="{% cycle oddrow,evenrow %}">
<td>{{ doc.display_name }}</td>
<td>{{ doc.get_state }}</td>
<td><a href="{{ doc.get_absolute_url }}"</a>{{ doc.title }}</td>
<td><a href="{% url "community_remove_document" cl.pk doc.pk %}">Remove</a></td>
</tr>
{% endfor %}
</table>
</div>
<div id="rules">
<h2>Rules added to this list</h2>
<table class="ietf-table">
<tr><th>Rule</th><th>Value</th><th>Documents</th><th>Remove from list</th></tr>
{% for rule in cl.rule_set.all %}
{% with rule.get_callable_rule as callable %}
<tr class="{% cycle oddrow,evenrow %}">
<td>{{ callable.description }}</td>
<td>{{ callable.show_value }}</td>
<td>{% with rule.cached_ids.count as count %}{{ count }} document{{ count|pluralize }}{% endwith %}</td>
<td><a href="{% url "community_remove_rule" cl.pk rule.pk %}">Remove</a></td>
</tr>
{% endwith %}
{% endfor %}
</table>
<h3>Add a new rule</h3>
<form method="post" action="#rules">{% csrf_token %}
{{ rule_form.as_p }}
<input type="submit" name="save_rule" value="Add rule" />
</form>
<div class="rule_values" style="display: none;">
{% for entry in rule_form.get_all_options %}
<div class="{{ entry.type }}">
<select>
{% for option in entry.options %}
<option value={{ option.0 }}>{{ option.1|safe }}</option>
{% endfor %}
</select>
</div>
{% endfor %}
</div>
</div>
<div id="custom">
{% include "community/customize_display.html" %}
</div>
<div id="info">
<p>Feel free to share the following links if you need it.</p>
<ul>
<li><a href="{{ cl.secret }}/view/">Read only view for {{ cl.long_name }}</a></li>
<li><a href="{{ cl.secret }}/changes/feed/">Feed for every change in status of {{ cl.long_name }}</a></li>
<li><a href="{{ cl.secret }}/changes/significant/feed/">Feed for significant change in status of {{ cl.long_name }}</a></li>
<li><a href="{{ cl.secret }}/subscribe/">Subscribe to the mailing list for every change in status of {{ cl.long_name }}</a></li>
<li><a href="{{ cl.secret }}/subscribe/significant/">Subscribe to the mailing list for significant change in status of {{ cl.long_name }}</a></li>
</ul>
<p>Export your list to CSV format</p>
<ul>
<li><a href="csv/">CSV for {{ cl.long_name }}</a></li>
<li><a href="{{ cl.secret }}/csv/">Read only CSV for {{ cl.long_name }}</a></li>
</ul>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script type="text/javascript" src="/js/community.js"></script>
{% endblock js %}
{% comment %}
XXX scrolling jumps around when using this, unfortunately
<script>
$('.nav-memory a').click(function (e) {
e.preventDefault();
$(this).tab('show');
});
{% block scripts %}
var tabView = new YAHOO.widget.TabView('mytabs');
var url = location.href.split('#');
if (url[1]) {
url[1] = "#"+url[1];
var tabs = tabView.get('tabs');
for (var i = 0; i < tabs.length; i++) {
if (url[1].indexOf(tabs[i].get('href')) == 0) {
tabView.set('activeIndex', i);
break;
}
}
}
{% endblock %}
// store the currently selected tab in the hash value
$("ul.nav-tabs > li > a").on("shown.bs.tab", function (e) {
var id = $(e.target).attr("href").substr(1);
window.location.hash = id;
});
// on load of the page: switch to the currently selected tab
var hash = window.location.hash;
$('.nav-memory a[href="' + hash + '"]').tab('show');
</script>
{% endcomment %}

View file

@ -1,22 +1,29 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% load bootstrap3 %}
{% block title %}Subscribe to {{ cl.long_name }}{% endblock %}
{% block content %}
<h1>Subscribe to {{ cl.long_name }}</h1>
{% if success %}
<p>
We have sent an email to your email address with instructions to complete your subscription.
</p>
<h1>Subscription successful</h1>
<p>We have sent an email to your email address with instructions to complete your subscription.</p>
{% else %}
<p>
Subscribe to the email list for notifications of {% if significant %}significant {% endif %}changes on {{ cl.long_name }}.
</p>
<form action="" method="post">{% csrf_token %}
<table>
{{ form }}
</table>
<input type="submit" value="Subscribe" />
</form>
<h1>Subscribe to {{ cl.long_name }}</h1>
{% bootstrap_messages %}
<p>Subscribe to the email list for notifications of {% if significant %}significant {% endif %}changes on {{ cl.long_name }}.</p>
<form role="form" method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Subscribe</button>
{% endbuttons %}
</form>
{% endif %}
{% endblock %}

View file

@ -1,13 +1,13 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% block title %}Subscription to {{ cl.long_name }}{% endblock %}
{% block content %}
<h1>Subscription to {{ cl.long_name }}</h1>
<p>Your email address {{ email }} has been successfully subscribed to {{ cl.long_name }}.</p>
<p>
You email address {{ email }} has been successfully subscribed to {{ cl.long_name }}
</p>
<p>
<a href="{% url "view_personal_list" secret=cl.secret %}">Return to the list view</a>
<a class="btn btn-primary" href="{% url "view_personal_list" secret=cl.secret %}">Back</a>
</p>
{% endblock %}

View file

@ -1,22 +1,33 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% load bootstrap3 %}
{% block title %}Cancel subscription to {{ cl.long_name }}{% endblock %}
{% block content %}
<h1>Cancel subscription to {{ cl.long_name }}</h1>
{% if success %}
<p>
You will receive a confirmation email shortly containing further instructions on how to cancel your subscription.
</p>
<h1>Cancellation successful</h1>
<p>
You will receive a confirmation email shortly containing further instructions on how to cancel your subscription.
</p>
{% else %}
<p>
Cancel your subscription to the email list for notifications of {% if significant %}significant {% endif %}changes on {{ cl.long_name }}.
</p>
<form action="" method="post">{% csrf_token %}
<table>
{{ form }}
</table>
<input type="submit" value="Cancel subscription" />
</form>
<h1>Cancel subscription to {{ cl.long_name }}</h1>
{% bootstrap_messages %}
<p>
Cancel your subscription to the email list for notifications of {% if significant %}significant {% endif %}changes on {{ cl.long_name }}.
</p>
<form role="form" method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Subscribe</button>
{% endbuttons %}
</form>
{% endif %}
{% endblock %}

View file

@ -1,13 +1,15 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% block title %}Canceled subscription to {{ cl.long_name }}{% endblock %}
{% block title %}Cancelled subscription to {{ cl.long_name }}{% endblock %}
{% block content %}
<h1>Canceled subscription to {{ cl.long_name }}</h1>
<h1>Cancelled subscription to {{ cl.long_name }}</h1>
<p>
You email address {{ email }} has been successfully removed form {{ cl.long_name }} {% if significant %}significant {% endif %}changes mailing list.
Your email address {{ email }} has been successfully removed from the {{ cl.long_name }} {% if significant %}significant {% endif %}changes mailing list.
</p>
<p>
<a href="{{ cl.get_public_url }}">Return to the list view</a>
<a class="btn btn-primary" href="{{ cl.get_public_url }}">Back</a>
</p>
{% endblock %}

View file

@ -1,8 +1,8 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% block pagehead %}
<link rel="alternate" type="application/rss+xml" title="Changes on {{ cl.long_name }} RSS Feed" href="../changes/feed/" />
<link rel="alternate" type="application/rss+xml" title="Significant changes on {{ cl.long_name }} RSS Feed" href="../changes/significant/feed/" />
<link rel="alternate" type="application/atom+xml" title="Changes on {{ cl.long_name }}" href="../changes/feed/" />
<link rel="alternate" type="application/atom+xml" title="Significant changes on {{ cl.long_name }}" href="../changes/significant/feed/" />
{% endblock %}
{% block title %}{{ cl.long_name }}{% endblock %}
@ -10,7 +10,7 @@
{% block content %}
<h1>{{ cl.long_name }}</h1>
<p>
Subscribe to notification email list:
Subscribe to notification email lists:
</p>
<ul>
<li><a href="../subscribe/">All changes email list</a></li>

View file

@ -3,42 +3,45 @@
{% with cl.get_rfcs_and_drafts as documents %}
{% with dc.get_active_fields as fields %}
<h2>Drafts</h2>
<table class="ietf-table">
<tr>
{% for field in fields %}
<th>{{ field.description }}</th>
{% endfor %}
</tr>
{% for doc in documents.1 %}
<tr class="{% cycle oddrow,evenrow %}">
{% for field in fields %}
<td>
{% show_field field doc %}
</td>
{% endfor %}
</tr>
{% endfor %}
<table class="table table-condensed table-striped">
<thead>
<tr>
{% for field in fields %}
<th>{{ field.description }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for doc in documents.1 %}
<tr>
{% for field in fields %}
<td>{% show_field field doc %}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% endwith %}
<h2>RFCs</h2>
{% with dc.get_active_fields as fields %}
<table class="ietf-table">
<tr>
{% for field in fields %}
<th>{{ field.rfcDescription }}</th>
{% endfor %}
</tr>
{% for doc in documents.0 %}
<tr class="{% cycle oddrow,evenrow %}">
{% for field in fields %}
<td>
{% show_field field doc %}
</td>
{% endfor %}
</tr>
{% endfor %}
<h2>RFCs</h2>
<table class="table table-condensed table-striped">
<thead>
<tr>
{% for field in fields %}
<th>{{ field.rfcDescription }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for doc in documents.0 %}
<tr>
{% for field in fields %}
<td>{% show_field field doc %}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% endwith %}
{% endwith %}

View file

@ -1,68 +1,51 @@
{# Copyright The IETF Trust 2010, All Rights Reserved #}
{% extends "base.html" %}
{% load ietf_filters %}
{% extends "ietf.html" %}
{% block title %}User settings{% endblock %}
{% block content %}
<h2>Cookie settings for the IETF datatracker</h2>
<h1>User settings</h1>
<p> Following settings are implemented using cookies, so if you have
cookies disabled then you are not able to change the settings
(everything still continues to work by using default settings).</p>
<p>
The following settings are implemented using cookies, so if you have
cookies disabled then you will not be able to change the settings
(everything still continues to work by using default settings).
</p>
<table id="settings">
<tr class="setting-header">
<td colspan="6">
<h2 class="ietf-divider">How many days is considered new</h2>
</td>
<tr>
<tr class="setting-description">
<td colspan="6">
<p>This setting affects how many days is considered new enough to get the special marking in the drafts page. Default setting is 14 days.</p>
</td>
</tr>
<tr class="settings-values">
<td>{% if new_enough == 7 %}<span class="ietf-highlight-y"><a href="/accounts/settings/new_enough/7">7 days</a></span>{%else%}<a href="/accounts/settings/new_enough/7">7 days</a>{% endif %}</td></td>
<td>{% if new_enough == 14 %}<span class="ietf-highlight-y"><a href="/accounts/settings/new_enough/14">14 days</a></span>{%else%}<a href="/accounts/settings/new_enough/14">14 days</a>{% endif %}</td>
<td>{% if new_enough == 21 %}<span class="ietf-highlight-y"><a href="/accounts/settings/new_enough/21">21 days</a></span>{%else%}<a href="/accounts/settings/new_enough/21">21 days</a>{% endif %}</td>
<td>{% if new_enough == 30 %}<span class="ietf-highlight-y"><a href="/accounts/settings/new_enough/30">30 days</a></span>{%else%}<a href="/accounts/settings/new_enough/30">30 days</a>{% endif %}</td>
<td>{% if new_enough == 60 %}<span class="ietf-highlight-y"><a href="/accounts/settings/new_enough/60">60 days</a></span>{%else%}<a href="/accounts/settings/new_enough/60">60 days</a>{% endif %}</td>
<td>{% if new_enough == 90 %}<span class="ietf-highlight-y"><a href="/accounts/settings/new_enough/90">90 days</a></span>{%else%}<a href="/accounts/settings/new_enough/90">90 days</a>{% endif %}</td>
</tr>
<h2>How many days is considered "new"?</h2>
<tr class="setting-header">
<td colspan="6">
<h2 class="ietf-divider">How many days is considered soon</h2>
</td>
<tr>
<tr class="setting-description">
<td colspan="6">
<p>This setting tells what is considered soon when showing document which is going to be expire soon. Default setting is 14 days.</p>
</td>
</tr>
<tr class="settings-values">
<td>{% if expires_soon == 7 %}<span class="ietf-highlight-y"><a href="/accounts/settings/expires_soon/7">7 days</a></span>{%else%}<a href="/accounts/settings/expires_soon/7">7 days</a>{% endif %}</td></td>
<td>{% if expires_soon == 14 %}<span class="ietf-highlight-y"><a href="/accounts/settings/expires_soon/14">14 days</a></span>{%else%}<a href="/accounts/settings/expires_soon/14">14 days</a>{% endif %}</td>
<td>{% if expires_soon == 21 %}<span class="ietf-highlight-y"><a href="/accounts/settings/expires_soon/21">21 days</a></span>{%else%}<a href="/accounts/settings/expires_soon/21">21 days</a>{% endif %}</td>
<td>{% if expires_soon == 30 %}<span class="ietf-highlight-y"><a href="/accounts/settings/expires_soon/30">30 days</a></span>{%else%}<a href="/accounts/settings/expires_soon/30">30 days</a>{% endif %}</td>
<td>{% if expires_soon == 60 %}<span class="ietf-highlight-y"><a href="/accounts/settings/expires_soon/60">60 days</a></span>{%else%}<a href="/accounts/settings/expires_soon/60">60 days</a>{% endif %}</td>
<td>{% if expires_soon == 90 %}<span class="ietf-highlight-y"><a href="/accounts/settings/expires_soon/90">90 days</a></span>{%else%}<a href="/accounts/settings/expires_soon/90">90 days</a>{% endif %}</td>
</tr>
<p>This setting affects how many days are considered "new enough" to get the special highlighting in the documents table. Default setting is 14 days.</p>
<div class="btn-group btn-group-justified">
<a class="btn btn-default {% if new_enough == 7 %}active{% endif %}" href="/accounts/settings/new_enough/7">7 days</a>
<a class="btn btn-default {% if new_enough == 14 %}active{% endif %}" href="/accounts/settings/new_enough/14">14 days</a>
<a class="btn btn-default {% if new_enough == 21 %}active{% endif %}" href="/accounts/settings/new_enough/21">21 days</a>
<a class="btn btn-default {% if new_enough == 30 %}active{% endif %}" href="/accounts/settings/new_enough/30">30 days</a>
<a class="btn btn-default {% if new_enough == 60 %}active{% endif %}" href="/accounts/settings/new_enough/60">60 days</a>
<a class="btn btn-default {% if new_enough == 90 %}active{% endif %}" href="/accounts/settings/new_enough/90">90 days</a>
</div>
<h2 class="ietf-divider">How many days is considered "soon"?</h2>
<p>This setting tells what is considered "soon" when showing documents that are going to be expire soon. Default setting is 14 days.</p>
<div class="btn-group btn-group-justified">
<a class="btn btn-default {% if expires_soon == 7 %}active{% endif %}" href="/accounts/settings/expires_soon/7">7 days</a>
<a class="btn btn-default {% if expires_soon == 14 %}active{% endif %}" href="/accounts/settings/expires_soon/14">14 days</a>
<a class="btn btn-default {% if expires_soon == 21 %}active{% endif %}" href="/accounts/settings/expires_soon/21">21 days</a>
<a class="btn btn-default {% if expires_soon == 30 %}active{% endif %}" href="/accounts/settings/expires_soon/30">30 days</a>
<a class="btn btn-default {% if expires_soon == 60 %}active{% endif %}" href="/accounts/settings/expires_soon/60">60 days</a>
<a class="btn btn-default {% if expires_soon == 90 %}active{% endif %}" href="/accounts/settings/expires_soon/90">90 days</a>
</div>
<h2 class="ietf-divider">Show full document text by default?</h2>
<p>Show the full text immediately on the document page instead of only showing beginning of it. This defaults to off.</p>
<div class="btn-group btn-group-justified">
<a class="btn btn-default {% if full_draft == "off" %}active{% endif %}" href="/accounts/settings/full_draft/off">Off</a>
<a class="btn btn-default {% if full_draft == "on" %}active{% endif %}" href="/accounts/settings/full_draft/on">On</a>
</div>
<tr class="setting-header">
<td colspan="6">
<h2 class="ietf-divider">Show full document text in document page</h2>
</td>
<tr>
<tr class="setting-description">
<td colspan="6">
<p>Show the full draft immediately on the document page instead of only showing beginning of it. This defaults to off.</p>
</td>
</tr>
<tr class="settings-values">
<td>{% if full_draft == "off" %}<span class="ietf-highlight-y"><a href="/accounts/settings/full_draft/off">off</a></span>{%else%}<a href="/accounts/settings/full_draft/off">off</a>{% endif %}</td></td>
<td>{% if full_draft == "on" %}<span class="ietf-highlight-y"><a href="/accounts/settings/full_draft/on">on</a></span>{%else%}<a href="/accounts/settings/full_draft/on">on</a>{% endif %}</td></td>
</tr>
</table>
{% endblock %}

View file

@ -1,33 +1,23 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% block title %}Add comment on {{ doc }}{% endblock %}
{% load bootstrap3 %}
{% block morecss %}
form.add-comment #id_comment {
width: 600px;
height: 300px;
}
form.add-comment .actions {
padding-top: 20px;
}
{% endblock %}
{% block title %}Add comment for {{ doc }}{% endblock %}
{% block content %}
<h1>Add comment on {{ doc }}</h1>
<h1>Add comment<br><small>{{ doc }}</small></h1>
<p>The comment will be added to the history trail.</p>
{% bootstrap_messages %}
<form class="add-comment" action="" method="post">{% csrf_token %}
<table>
{{ form.as_table }}
<tr>
<td></td>
<td class="actions">
<a href="{% url "doc_history" name=doc.name %}">Back</a>
<input type="submit" value="Add comment"/>
</td>
</tr>
</table>
<form role="form" method="post">
{% csrf_token %}
{% bootstrap_form form %}
<p class="help-block">The comment will be added to the history trail.</p>
{% buttons %}
<button type="submit" class="btn btn-primary">Submit</button>
<a class="btn btn-default pull-right" href="{% url "doc_view" name=doc.name %}">Back</a>
{% endbuttons %}
</form>
{% endblock %}

View file

@ -1,36 +1,27 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% load bootstrap3 %}
{% load ietf_filters %}
{% block title %}Approval announcement writeup for {{ doc }}{% endblock %}
{% block morecss %}
form #id_approval_text {
width: 700px;
height: 600px;
}
{% endblock %}
{% block content %}
<h1>Approval announcement writeup for {{ doc }}</h1>
<h1>Approval announcement writeup<br><small>{{ doc }}</small></h1>
<form action="" method="post">{% csrf_token %}
{% bootstrap_messages %}
<p>Sent after approval.</p>
<form role="form" method="post">
{% csrf_token %}
{% bootstrap_form approval_text_form %}
{{ approval_text_form.approval_text }}
<div class="actions">
<a href="{{ back_url }}">Back</a>
<input type="submit" name="save_approval_text" value="Save Approval Announcement Text" />
<input type="submit" name="regenerate_approval_text" value="Regenerate Approval Announcement Text" />
</div>
{% buttons %}
<button type="submit" class="btn btn-primary" name="save_approval_text">Save text</button>
<button type="submit" class="btn btn-warning" name="regenerate_approval_text">Regenerate text</button>
{% if user|has_role:"Secretariat" and can_announce %}
<a class="btn btn-default" href="{% url "doc_approve_ballot" name=doc.name %}">Approve ballot</a>
{% endif %}
<a class="btn btn-default pull-right" href="{% url "doc_view" name=doc.name %}">Back</a>
{% endbuttons %}
</form>
{% load ietf_filters %}
{% if user|has_role:"Secretariat" %}
<p>
{% if can_announce %}
<a href="{% url "doc_approve_ballot" name=doc.name %}">Approve ballot</a>
{% endif %}
</p>
{% endif %}
{% endblock%}

View file

@ -1,45 +1,23 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% block title %}Approve ballot for {{ doc }}{% 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 {{ doc }}</h1>
<h1>Approve ballot<br><small>{{ doc }}</small></h1>
<div>IETF announcement:</div>
<form role="form" method="post">
{% csrf_token %}
<pre>{{ announcement }}</pre>
<form class="approve-ballot" action="" method="post">{% csrf_token %}
<div class="announcement">
<pre>{{ announcement }}</pre>
</div>
<div class="actions">
<a href="{% url "doc_ballot_approvaltext" name=doc.name %}">Back</a>
{% if action == "to_announcement_list" %}
<input type="submit" value="Notify the RFC Editor, send out the announcement and close ballot"/>
{% endif %}
{% if action == "to_rfc_editor" %}
<input type="submit" value="Send message to the RFC Editor and close ballot"/>
{% endif %}
{% if action == "do_not_publish" %}
<input type="submit" value="Send message to the RFC Editor (DNP) and close ballot"/>
{% endif %}
</div>
<div class="buttonlist">
{% if action == "to_announcement_list" %}
<button class="btn btn-warning" type="submit">Notify RFC Editor, send announcement & close ballot</button>
{% elif action == "to_rfc_editor" %}
<button class="btn btn-warning" type="submit">Email RFC Editor & close ballot</button>
{% elif action == "do_not_publish" %}
<button class="btn btn-warning" type="submit">Email RFC Editor (DNP) & close ballot"/>
{% endif %}
<a class="btn btn-default pull-right" href="{% url "doc_ballot_approvaltext" name=doc.name %}">Back</a>
</div>
</form>
{% endblock %}

View file

@ -1,19 +1,17 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% block title %}Ballot for {{ doc }} issued{% endblock %}
{% block title %}Ballot issued for {{ doc }}{% endblock %}
{% block content %}
<h1>Ballot for {{ doc }} issued</h1>
<h1>Ballot issued<br><small>{{ doc }}</small></h1>
<p>Ballot has been sent out.</p>
{% if doc.telechat_date %}
<p>The document is currently on the {{ doc.telechat_date }} telechat agenda.</p>
<p>The document is currently on the {{ doc.telechat_date }} telechat agenda.</p>
{% else %}
<p>The document is not on any telechat agenda.</p>
<p>The document is not on any telechat agenda.</p>
{% endif %}
<div class="actions">
<a href="{{ back_url }}">Back to document</a>
</div>
<a class="btn btn-default pull-right" href="{{ back_url }}">Back</a>
{% endblock %}

View file

@ -1,18 +1,22 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% block title %}Clear ballot for {{ doc }}{% endblock %}
{% block content %}
<h1>Clear ballot for {{ doc }}</h1>
<h1>Clear ballot<br><small>{{ doc }}</small></h1>
<form action="" method="post">{% csrf_token %}
<p>Clear the ballot for {{ doc.file_tag }}?</p>
<form role="form" method="post">
{% csrf_token %}
<p class="alert alert-danger">
<b>Clear the ballot for {{ doc }}?</b>
<br>
This will clear all ballot positions and discuss entries.
</p>
<p>This will clear all ballot positions and discuss entries.</p>
<div class="actions">
<a href="{{ back_url }}ballot/">Back</a>
<input type="submit" value="Clear ballot"/>
</div>
<div class="buttonlist">
<button type="submit" class="btn btn-danger">Clear</button>
<a class="btn btn-default pull-right" href="{{ back_url }}ballot/">Back</a>
</div>
</form>
{% endblock %}

View file

@ -1,18 +1,22 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% block title %}Defer ballot for {{ doc }}{% endblock %}
{% block content %}
<h1>Defer ballot for {{ doc }}</h1>
<h1>Defer ballot<br><small>{{ doc }}</small></h1>
<form class="defer" action="" method="post">{% csrf_token %}
<p>Defer the ballot for {{ doc.file_tag }}?</p>
<form role="form" method="post">
{% csrf_token %}
<p class="alert alert-danger">
<b>Defer the ballot for {{ doc }}?</b>
<br>
The ballot will then be put on the IESG agenda of {{ telechat_date }}.
</p>
<p>The ballot will then be on the IESG agenda of {{ telechat_date }}.</p>
<div class="actions">
<a href="{{ back_url }}">Back</a>
<input type="submit" value="Defer ballot"/>
</div>
<div class="buttonlist">
<input type="submit" class="btn btn-danger" value="Defer ballot">
<a class="btn btn-default pull-right" href="{% url "doc_view" name=doc.name %}">Back</a>
</div>
</form>
{% endblock %}

View file

@ -1,92 +1,64 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% load bootstrap3 %}
{% block title %}Change position for {{ ad.plain_name }} on {{ doc }}{% endblock %}
{% block morecss %}
div.ballot-deferred {
margin-top: 8px;
margin-bottom: 8px;
}
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 .discuss-text {
padding-top: 20px
}
form.position-form #id_discuss,
form.position-form #id_comment {
width: 700px;
height: 250px;
}
form.position-form .comment-text {
margin-top: 20px;
}
div.question {
font-size: 173%;
padding-left: 5px;
padding-bottom: 10px;
}
{% endblock %}
{% block content %}
<h1>Change position for {{ ad.plain_name }} on {{ doc }}</h1>
<h1>Change position for {{ ad.plain_name }}<br><small>{{ doc }}</small></h1>
{% bootstrap_messages %}
<div class="question">{{ ballot.ballot_type.question }}</div>
{% if ballot_deferred %}
<div class="ballot-deferred">Ballot deferred by {{ ballot_deferred.by }} on {{ ballot_deferred.time|date:"Y-m-d" }}.</div>
<p class="alert alert-info">
Ballot deferred by {{ ballot_deferred.by }} on {{ ballot_deferred.time|date:"Y-m-d" }}.
</p>
{% endif %}
<form class="position-form" action="" method="post">{% csrf_token %}
<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"/>
{% if doc.type_id == "draft" or doc.type_id == "conflrev" %}
{% if ballot_deferred %}<input type="submit" name="Undefer" value="Undefer"/>{% else %}<input type="submit" name="Defer" value="Defer"/>{% endif %}
{% endif %}
</span>
</div>
<form role="form" method="post">
{% csrf_token %}
<div style="clear:left"></div>
<div class="discuss-widgets"{% if not show_discuss_text %} style="display:none"{% endif %}>
<div class="discuss-text">
{{ form.discuss.label_tag }}
{% if old_pos and old_pos.discuss_time %}<span class="last-edited">(last edited {{ old_pos.discuss_time }})</span>{% endif %}
</div>
{{ form.discuss.errors }}
{{ form.discuss }}
</div>
<div class="comment-text">
{{ 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 }}
{% for field in form %}
{% bootstrap_field field %}
{% if field.name == "discuss" and old_pos and old_pos.discuss_time %}
<span class="help-block">Last edited {{ old_pos.discuss_time }}</span>
{% elif field.name == "comment" and old_pos and old_pos.comment_time %}
<span class="help-block">Last edited {{ old_pos.comment_time }}</span>
{% endif %}
{% endfor %}
<div class="actions">
<a href="{{ return_to_url }}">Back</a>
</div>
{{ form.return_to_url }}
{% buttons %}
<button type="submit" class="btn btn-primary" name="send_mail">Save & send email</button>
<input type="submit" class="btn btn-default" value="Save">
{% if doc.type_id == "draft" or doc.type_id == "conflrev" %}
{% if ballot_deferred %}
<input type="submit" class="btn btn-warning" name="Undefer" value="Undefer ballot">
{% else %}
<input type="submit" class="btn btn-danger" name="Defer" value="Defer ballot">
{% endif %}
{% endif %}
<a class="btn btn-default pull-right" href="{{ doc.get_absolute_url }}">Back</a>
{% endbuttons %}
</form>
{% endblock %}
{% block content_end %}
<script>
var blockingPositions = {{ blocking_positions|safe }};
</script>
<script type="text/javascript" src="/js/doc-edit-position.js"></script>
{% block scripts %}
var block_pos = {{ blocking_positions|safe }};
function discuss_toggle(val) {
if (val in block_pos) {
$("#div_id_discuss").show();
} else {
$("#div_id_discuss").hide();
}
}
$("input[name=position]").click(function () {
discuss_toggle($(this).val());
});
discuss_toggle($("input[name=position]:checked").val());
{% endblock %}

View file

@ -1,46 +1,36 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% block title %}Last Call text for {{ doc }}{% endblock %}
{% load bootstrap3 %}
{% load ietf_filters %}
{% block morecss %}
form #id_last_call_text {
width: 700px;
height: 600px;
}
{% endblock %}
{% block title %}Last call text for {{ doc }}{% endblock %}
{% block content %}
<h1>Last Call text for {{ doc }}</h1>
<h1>Last call text<br><small>{{ doc }}</small></h1>
<form action="" method="post">{% csrf_token %}
{% bootstrap_messages %}
<p>{{ last_call_form.last_call_text.errors }}</p>
<form role="form" method="post">
{% csrf_token %}
{% bootstrap_form last_call_form %}
{{ last_call_form.last_call_text }}
{% if can_request_last_call and need_intended_status %}
<span class="help-block">
You need to select intended status of {{ need_intended_status }} and regenerate last call text to request last call.
</span>
{% endif %}
<p>{{ last_call_form.last_call_text.errors }}</p>
{% if can_request_last_call and need_intended_status %}
<p>You need to select intended status of {{ need_intended_status }} and regenerate last call text to request last call.</p>
{% endif %}
<div class="actions">
<a href="{{ back_url }}">Back</a>
<input type="submit" name="save_last_call_text" value="Save Last Call Text" />
<input type="submit" name="regenerate_last_call_text" value="Regenerate Last Call Text" />
{% if can_request_last_call and not need_intended_status %}
<input style="margin-left: 8px" type="submit" name="send_last_call_request" value="Save and Request Last Call" />
{% endif %}
</div>
{% buttons %}
<button type="submit" class="btn btn-primary" name="save_last_call_text">Save text</button>
{% if can_request_last_call and not need_intended_status %}
<button type="submit" class="btn btn-warning" name="send_last_call_request">Save text & request last call</button>
{% endif %}
<button type="submit" class="btn btn-warning" name="regenerate_last_call_text">Regenerate text</button>
{% if user|has_role:"Secretariat" and can_make_last_call %}
<a class="btn btn-danger" href="{% url "doc_make_last_call" name=doc.name %}">Issue last call</a>
{% endif %}
<a class="btn btn-default pull-right" href="{% url "doc_view" name=doc.name %}">Back</a>
{% endbuttons %}
</form>
{% load ietf_filters %}
{% if user|has_role:"Secretariat" %}
<p>
{% if can_make_last_call %}
<a href="{% url "doc_make_last_call" name=doc.name %}">Make Last Call</a>
{% endif %}
</p>
{% endif %}
{% endblock%}

View file

@ -1,47 +1,55 @@
{% extends "base.html" %}
{% load ietf_filters %}
{% block title %}Send ballot position email for {{ ad }}{% endblock %}
{% extends "ietf.html" %}
{% block morecss %}
form.send-ballot pre {
margin: 0;
padding: 4px;
border-top: 4px solid #eee;
border-bottom: 4px solid #eee;
}
{% endblock %}
{% load ietf_filters %}
{% block title %}Send ballot position for {{ ad }}{% endblock %}
{% block content %}
<h1>Send ballot position email for {{ ad }}</h1>
<h1>Send ballot position for {{ ad }}</h1>
<form role="form" method="post">
{% csrf_token %}
<div class="form-group">
<label>From</label>
<input class="form-control" type="text" placeholder="{{ frm }}" disabled>
</div>
<div class="form-group">
<label>To</label>
<input class="form-control" type="text" placeholder="{{ to }}" disabled>
</div>
<div class="form-group">
<label>Cc</label>
<input class="form-control" type="email" name="cc">
<span class="help-block">Separate email addresses with commas.</span>
</div>
{% if doc.notify %}
<div class="checkbox">
<label>
<input type="checkbox" name="cc_state_change" value="1" checked>
<b>Cc:</b> {{ doc.notify }}
</label>
</div>
{% endif %}
<div class="form-group">
<label>Subject</label>
<input class="form-control" type="text" placeholder="{{ subject }}" disabled>
</div>
<div class="form-group">
<label>Body</label>
<pre>{{ body|wrap_text }}</pre>
</div>
<div class="buttonlist">
<input type="submit" class="btn btn-danger" value="Send">
<a class="btn btn-default pull-right" href="{{ back_url }}">Back</a>
</div>
<form class="send-ballot" action="" method="post">{% csrf_token %}
<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" /><br/>
{% if doc.notify %}
<label>
<input type="checkbox" name="cc_state_change" value="1" checked="checked" />
{{ doc.notify }}
</label>
{% endif %}
</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 %}

View file

@ -1,34 +1,28 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% load bootstrap3 %}
{% block title %}Ballot writeup and notes for {{ doc }}{% endblock %}
{% block morecss %}
form #id_ballot_writeup {
width: 700px;
height: 600px;
}
{% endblock %}
{% block content %}
<h1>Ballot writeup and notes for {{ doc }}</h1>
<form action="" method="post">{% csrf_token %}
<p>(Technical Summary, Working Group Summary, Document Quality,
Personnel, RFC Editor Note, IRTF Note, IESG Note, IANA Note)</p>
<p>This text will be appended to all announcements and messages to
the IRTF or RFC Editor.</p>
<h1>Ballot writeup and notes<br><small>{{ doc }}</small></h1>
{{ ballot_writeup_form.ballot_writeup }}
<div class="actions">
<a href="{{ back_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 ballot_issued %}Re-{% endif %}Issue Ballot" />
</div>
{% bootstrap_messages %}
<form role="form" method="post">
{% csrf_token %}
{% bootstrap_form ballot_writeup_form %}
<span class="help-block">
Technical summary, Working Group summary, document quality, personnel, RFC Editor note, IRTF note, IESG note, IANA note. This text will be appended to all announcements and messages to the IRTF or RFC Editor.
</span>
{% buttons %}
<button type="submit" class="btn btn-primary" name="save_ballot_writeup">Save</button>
<button type="submit" class="btn btn-warning" name="issue_ballot">Save & {% if ballot_issued %}re-{% endif %}issue ballot</button>
<a class="btn btn-default pull-right" href="{% url "doc_view" name=doc.name %}">Back</a>
{% endbuttons %}
</form>
{% endblock%}

View file

@ -1,14 +1,37 @@
{% load ietf_filters %}
<div class="ballot-popup">
<div class="content">
{{ ballot_content }}
</div>
<div class="actions">
{% if request.user|has_role:"Area Director" %}
<a href="{% url "ietf.doc.views_ballot.edit_position" name=doc.name ballot_id=ballot_id %}" class="button" style="margin-right: 1em;">Edit Position</a>
{% endif %}
<a href="" class="button close">Close</a>
</div>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="modal-title-{{ ballot_id }}">Ballot for {{ doc.name }}</h4>
{% if deferred %}
<p>Ballot deferred by {{ deferred.by }} on {{ deferred.time|date:"Y-m-d" }}.</p>
{% endif %}
</div>
<div class="modal-body">
{{ ballot_content }}
</div>
<div class="modal-footer">
{% if editable and user|has_role:"Area Director,Secretariat" %}
{% if user|has_role:"Area Director" %}
<a class="btn btn-default" href="{% url "ietf.doc.views_ballot.edit_position" name=doc.name ballot_id=ballot_id %}">Edit position</a>
{% endif %}
{% if doc.type_id == "draft" or doc.type_id == "conflrev" %}
{% if deferred %}
<a class="btn btn-default" href="{% url "doc_undefer_ballot" name=doc.name %}">Undefer ballot</a>
{% else %}
<a class="btn btn-warning" href="{% url "doc_defer_ballot" name=doc.name %}">Defer ballot</a>
{% endif %}
{% if user|has_role:"Secretariat" %}
<a class="btn btn-danger" href="{% url "doc_clear_ballot" name=doc.name %}">Clear ballot</a>
{% endif %}
{% endif %}
{% endif %}
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
</div>

View file

@ -1,39 +1,22 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% block morecss %}
.warning {
font-weight: bold;
color: #a00;
}
{% endblock %}
{% load bootstrap3 %}
{% block title %}
Change the shepherding AD for {{titletext}}
{% endblock %}
{% block title %}Change shepherding AD for {{titletext}}{% endblock %}
{% block content %}
<h1>Change the shepherding AD for {{titletext}}</h1>
<h1>Change shepherding AD<br><small>{{titletext}}</small></h1>
<form class="edit-info" action="" enctype="multipart/form-data" method="post">{% csrf_token %}
<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 "doc_view" name=doc.canonical_name %}">Back</a>
<input type="submit" value="Submit"/>
</td>
</tr>
</table>
{% bootstrap_messages %}
<form role="form" enctype="multipart/form-data" method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Submit</button>
<a class="btn btn-default pull-right" href="{% url "doc_view" name=doc.canonical_name %}">Back</a>
{% endbuttons %}
</form>
{% endblock %}

View file

@ -1,49 +1,35 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% block morecss %}
.warning {
font-weight: bold;
color: #a00;
}
{% endblock %}
{% load bootstrap3 %}
{% block title %}
Change the document shepherd for {{ doc.name }}-{{ doc.rev }}
Change document shepherd for {{ doc.name }}-{{ doc.rev }}
{% endblock %}
{% block pagehead %}
<link rel="stylesheet" type="text/css" href="/css/token-input.css"></link>
<link rel="stylesheet" href="/facelift/css/lib/select2.css">
<link rel="stylesheet" href="/facelift/css/lib/select2-bootstrap.css">
{% endblock %}
{% block content %}
<h1>Change the document shepherd for {{ doc.name }}-{{ doc.rev }}</h1>
<h1>Change document shepherd<br><small>{{ doc.name }}-{{ doc.rev }}</small></h1>
<p>The shepherd needs to have a Datatracker account. A new account can be
<a href="{% url "create_account" %}">created here</a>.</p>
<form class="edit-info" action="" method="post">{% csrf_token %}
<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 class="button" href="{% url "doc_view" name=doc.name %}">Cancel</a>
<input class="button" type="submit" value="Save"/>
</td>
</tr>
</table>
{% bootstrap_messages %}
<form role="form" enctype="multipart/form-data" method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Submit</button>
<a class="btn btn-default pull-right" href="{% url "doc_view" name=doc.name %}">Back</a>
{% endbuttons %}
</form>
{% endblock %}
{% block content_end %}
<script type="text/javascript" src="/js/lib/jquery.tokeninput.js"></script>
<script type="text/javascript" src="/js/tokenized-field.js"></script>
{% block js %}
<script src="/facelift/js/lib/select2-3.5.2.min.js"></script>
{% endblock %}

View file

@ -17,7 +17,7 @@ Change the document shepherd email for {{ doc.name }}-{{ doc.rev }}
<tr>
<td></td>
<td class="actions">
<a class="button" href="{% url "doc_view" name=doc.name %}">Cancel</a>
<a class="button" href="{% url "doc_view" name=doc.name %}">Back</a>
<input class="button" type="submit" value="Save"/>
</td>
</tr>

View file

@ -1,46 +1,24 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% block title %}Change State: {{doc.title}}{% endblock %}
{% load bootstrap3 %}
{% 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 title %}Change state for {{doc.title}}{% endblock %}
{% block content %}
<h1>Change State: {{doc.title}}</h1>
<h1>Change state<br><small>{{doc.title}}</small></h1>
<p class="helptext">For help on the states, see the <a href="{{help_url}}">state table</a>.</p>
{% bootstrap_messages %}
<form class="change-state" action="" method="post">{% csrf_token %}
<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 %}
<p><a class="btn btn-info" href="{{help_url}}">Help on states</a></p>
{{ field.errors }}
</td>
</tr>
{% endfor %}
<tr>
<td colspan="2" class="actions">
<a href="{% url "doc_view" name=doc.name %}">Back</a>
<input type="submit" value="Submit"/>
</td>
</tr>
</table>
<form role="form" method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Submit</button>
<a class="btn btn-default pull-right" href="{% url "doc_view" name=doc.name %}">Back</a>
{% endbuttons %}
</form>
{% endblock %}

View file

@ -1,42 +1,22 @@
{% extends "base.html" %}
{% extends "ietf.html" %}
{% block morecss %}
.warning {
font-weight: bold;
color: #a00;
}
form.edit-info #id_title {
width: 600px;
}
{% endblock %}
{% load bootstrap3 %}
{% block title %}
Change the title for {{titletext}}
{% endblock %}
{% block title %}Change title for {{doc.title}}{% endblock %}
{% block content %}
<h1>Change the title for {{titletext}}</h1>
<h1>Change title<br><small>{{titletext}}</small></h1>
<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 "doc_view" name=doc.canonical_name %}">Back</a>
<input type="submit" value="Submit"/>
</td>
</tr>
</table>
{% bootstrap_messages %}
<form role="form" method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Submit</button>
<a class="btn btn-default pull-right" href="{% url "doc_view" name=doc.canonical_name %}">Back</a>
{% endbuttons %}
</form>
{% endblock %}

Some files were not shown because too many files have changed in this diff Show more