Skip to content

Commit d0999c8

Browse files
committed
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
2 parents c3ff43d + 9a25b00 commit d0999c8

341 files changed

Lines changed: 16350 additions & 9831 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
This is the "facelift" datatracker branch that uses Twitter Bootstrap for
2+
the UI.
3+
4+
You need to install a few new django extensions:
5+
https://pypi.python.org/pypi/django-widget-tweaks
6+
https://pypi.python.org/pypi/django-bootstrap3
7+
https://pypi.python.org/pypi/django-typogrify
8+
9+
The meta goal of this effort is: *** NO CHANGES TO THE PYTHON CODE ***
10+
11+
Whenever changes to the python code are made, they can only fix HTML bugs,
12+
or add comments (tagged with "FACELIFT") about functionality that can be
13+
removed once the facelift templates become default. Or they need to add
14+
functionality that is only called from the new facelift templates.
15+
16+
Javascript that is only used on one template goes into that template.
17+
Javascript that is used by more than one template goes into ietf.js.
18+
19+
CSS that is only used on one template goes into that template.
20+
CSS that is used by more than one template goes into ietf.css. No CSS in the
21+
templates or - god forbid - style tags! (And no CSS or HTML styling in
22+
python code!!)
23+
24+
Templates that use jquery or bootstrap plugins include the css in the pagehead
25+
block, and the js in the js block.

TODO

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

ietf/community/templatetags/community_tags.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from django import template
22
from django.template.loader import render_to_string
3+
from django.conf import settings
34

45
from ietf.community.models import CommunityList
56
from ietf.group.models import Role
@@ -9,7 +10,7 @@
910

1011

1112
class CommunityListNode(template.Node):
12-
13+
1314
def __init__(self, user, var_name):
1415
self.user = user
1516
self.var_name = var_name
@@ -57,13 +58,13 @@ def show_field(field, doc):
5758

5859

5960
class CommunityListViewNode(template.Node):
60-
61+
6162
def __init__(self, clist):
6263
self.clist = clist
6364

6465
def render(self, context):
6566
clist = self.clist.resolve(context)
66-
if not clist.cached:
67+
if settings.DEBUG or not clist.cached:
6768
clist.cached = render_to_string('community/raw_view.html',
6869
{'cl': clist,
6970
'dc': clist.get_display_config()})

ietf/doc/fields.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import json
2+
3+
from django.utils.html import escape
4+
from django import forms
5+
from django.core.urlresolvers import reverse as urlreverse
6+
7+
import debug # pyflakes:ignore
8+
9+
from ietf.doc.models import Document, DocAlias
10+
11+
def select2_id_doc_name_json(objs):
12+
return json.dumps([{ "id": o.pk, "text": escape(o.name) } for o in objs])
13+
14+
class SearchableDocumentsField(forms.CharField):
15+
"""Server-based multi-select field for choosing documents using
16+
select2.js.
17+
18+
The field uses a comma-separated list of primary keys in a
19+
CharField element as its API with some extra attributes used by
20+
the Javascript part."""
21+
22+
def __init__(self,
23+
max_entries=None, # max number of selected objs
24+
model=Document,
25+
hint_text="Type in name to search for document",
26+
doc_type="draft",
27+
*args, **kwargs):
28+
kwargs["max_length"] = 10000
29+
self.max_entries = max_entries
30+
self.doc_type = doc_type
31+
self.model = model
32+
33+
super(SearchableDocumentsField, self).__init__(*args, **kwargs)
34+
35+
self.widget.attrs["class"] = "select2-field"
36+
self.widget.attrs["data-placeholder"] = hint_text
37+
if self.max_entries != None:
38+
self.widget.attrs["data-max-entries"] = self.max_entries
39+
40+
def parse_select2_value(self, value):
41+
return [x.strip() for x in value.split(",") if x.strip()]
42+
43+
def prepare_value(self, value):
44+
if not value:
45+
value = ""
46+
if isinstance(value, basestring):
47+
pks = self.parse_select2_value(value)
48+
value = self.model.objects.filter(pk__in=pks)
49+
filter_args = {}
50+
if self.model == DocAlias:
51+
filter_args["document__type"] = self.doc_type
52+
else:
53+
filter_args["type"] = self.doc_type
54+
value = value.filter(**filter_args)
55+
if isinstance(value, self.model):
56+
value = [value]
57+
58+
self.widget.attrs["data-pre"] = select2_id_doc_name_json(value)
59+
60+
# doing this in the constructor is difficult because the URL
61+
# patterns may not have been fully constructed there yet
62+
self.widget.attrs["data-ajax-url"] = urlreverse("ajax_select2_search_docs", kwargs={
63+
"doc_type": self.doc_type,
64+
"model_name": self.model.__name__.lower()
65+
})
66+
67+
return u",".join(unicode(o.pk) for o in value)
68+
69+
def clean(self, value):
70+
value = super(SearchableDocumentsField, self).clean(value)
71+
pks = self.parse_select2_value(value)
72+
73+
objs = self.model.objects.filter(pk__in=pks)
74+
75+
found_pks = [str(o.pk) for o in objs]
76+
failed_pks = [x for x in pks if x not in found_pks]
77+
if failed_pks:
78+
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)))
79+
80+
if self.max_entries != None and len(objs) > self.max_entries:
81+
raise forms.ValidationError(u"You can select at most %s entries only." % self.max_entries)
82+
83+
return objs
84+
85+
class SearchableDocAliasesField(SearchableDocumentsField):
86+
def __init__(self, model=DocAlias, *args, **kwargs):
87+
super(SearchableDocAliasesField, self).__init__(model=model, *args, **kwargs)
88+

ietf/doc/migrations/0022_fill_in_shepherd_email.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# -*- coding: utf-8 -*-
1+
# -*- coding: utf-8 -*-
22
from south.v2 import DataMigration
33

44
class Migration(DataMigration):

ietf/doc/templatetags/ballot_icon.py

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -87,35 +87,32 @@ def sort_key(t):
8787
positions = list(doc.active_ballot().active_ad_positions().items())
8888
positions.sort(key=sort_key)
8989

90-
edit_position_url = ""
91-
if has_role(user, "Area Director"):
92-
edit_position_url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=doc.name, ballot_id=ballot.pk))
93-
94-
title = "IESG positions (click to show more%s)" % (", right-click to edit position" if edit_position_url else "")
95-
96-
res = ['<a href="%s" data-popup="%s" data-edit="%s" title="%s" class="ballot-icon"><table>' % (
97-
urlreverse("doc_ballot", kwargs=dict(name=doc.name, ballot_id=ballot.pk)),
90+
res = ['<a href="%s" data-toggle="modal" data-target="#modal-%d" title="IESG positions (click to show more)" class="ballot-icon"><table>' % (
9891
urlreverse("ietf.doc.views_doc.ballot_popup", kwargs=dict(name=doc.name, ballot_id=ballot.pk)),
99-
edit_position_url,
100-
title
101-
)]
92+
ballot.pk)]
10293

10394
res.append("<tr>")
10495

10596
for i, (ad, pos) in enumerate(positions):
10697
if i > 0 and i % 5 == 0:
107-
res.append("</tr>")
108-
res.append("<tr>")
98+
res.append("</tr><tr>")
10999

110100
c = "position-%s" % (pos.pos.slug if pos else "norecord")
111101

112102
if user_is_person(user, ad):
113103
c += " my"
114104

115-
res.append('<td class="%s" />' % c)
105+
res.append('<td class="%s"></td>' % c)
106+
107+
# add sufficient table calls to last row to avoid HTML validation warning
108+
while (i + 1) % 5 != 0:
109+
res.append('<td class="empty"></td>')
110+
i = i + 1
116111

117-
res.append("</tr>")
118-
res.append("</table></a>")
112+
res.append("</tr></table></a>")
113+
# XXX FACELIFT: Loading via href will go away in bootstrap 4.
114+
# See http://getbootstrap.com/javascript/#modals-usage
115+
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)
119116

120117
return "".join(res)
121118

@@ -137,7 +134,8 @@ def ballotposition(doc, user):
137134

138135

139136
@register.filter
140-
def state_age_colored(doc):
137+
# FACELIFT: added flavor argument for styling
138+
def state_age_colored(doc, flavor=""):
141139
if doc.type_id == 'draft':
142140
if not doc.get_state_slug() in ["active", "rfc"]:
143141
# Don't show anything for expired/withdrawn/replaced drafts
@@ -156,7 +154,7 @@ def state_age_colored(doc):
156154
except IndexError:
157155
state_date = datetime.date(1990,1,1)
158156
days = (datetime.date.today() - state_date).days
159-
# loosely based on
157+
# loosely based on
160158
# http://trac.tools.ietf.org/group/iesg/trac/wiki/PublishPath
161159
if iesg_state == "lc":
162160
goal1 = 30
@@ -180,16 +178,26 @@ def state_age_colored(doc):
180178
goal1 = 14
181179
goal2 = 28
182180
if days > goal2:
183-
class_name = "ietf-small ietf-highlight-r"
181+
if flavor == "facelift":
182+
class_name = "label label-danger"
183+
else:
184+
class_name = "ietf-small ietf-highlight-r"
184185
elif days > goal1:
185-
class_name = "ietf-small ietf-highlight-y"
186+
if flavor == "facelift":
187+
class_name = "label label-warning"
188+
else:
189+
class_name = "ietf-small ietf-highlight-y"
186190
else:
187191
class_name = "ietf-small"
188192
if days > goal1:
189193
title = ' title="Goal is &lt;%d days"' % (goal1,)
190194
else:
191195
title = ''
192-
return mark_safe('<span class="%s"%s>(for&nbsp;%d&nbsp;day%s)</span>' % (
193-
class_name, title, days, 's' if days != 1 else ''))
196+
# It's too bad that this function returns HTML; this makes it hard to
197+
# style. For the facelift, I therefore needed to add a new "flavor"
198+
# parameter, which is ugly.
199+
return mark_safe('<span class="%s"%s>%sfor %d day%s%s</span>' % (
200+
class_name, title, '(' if flavor != "facelift" else "", days,
201+
's' if days != 1 else '', '(' if flavor != "facelift" else "" ))
194202
else:
195203
return ""

0 commit comments

Comments
 (0)