Skip to content

Commit 373644e

Browse files
committed
Port liaison submission/editing to Bootstrap, revamp the select related widget to use select2 rather than the longish popup table
- Legacy-Id: 8738
1 parent 2e862b6 commit 373644e

10 files changed

Lines changed: 179 additions & 179 deletions

File tree

ietf/liaisons/fields.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
from ietf.liaisons.models import LiaisonStatement
8+
9+
def select2_id_liaison_json(objs):
10+
return json.dumps([{ "id": o.pk, "text": escape(o.title) } for o in objs])
11+
12+
class SearchableLiaisonStatementField(forms.IntegerField):
13+
"""Server-based multi-select field for choosing liaison statements using
14+
select2.js."""
15+
16+
def __init__(self, hint_text="Type in title to search for document", *args, **kwargs):
17+
super(SearchableLiaisonStatementField, self).__init__(*args, **kwargs)
18+
19+
self.widget.attrs["class"] = "select2-field"
20+
self.widget.attrs["data-placeholder"] = hint_text
21+
self.widget.attrs["data-max-entries"] = 1
22+
23+
def prepare_value(self, value):
24+
if not value:
25+
value = None
26+
elif isinstance(value, LiaisonStatement):
27+
value = value
28+
else:
29+
value = LiaisonStatement.objects.exclude(approved=None).filter(pk=value).first()
30+
31+
self.widget.attrs["data-pre"] = select2_id_liaison_json([value] if value else [])
32+
33+
# doing this in the constructor is difficult because the URL
34+
# patterns may not have been fully constructed there yet
35+
self.widget.attrs["data-ajax-url"] = urlreverse("ajax_select2_search_liaison_statements")
36+
37+
return value
38+
39+
def clean(self, value):
40+
value = super(SearchableLiaisonStatementField, self).clean(value)
41+
42+
if value == None:
43+
return None
44+
45+
obj = LiaisonStatement.objects.filter(pk=value).first()
46+
if not obj and self.required:
47+
raise forms.ValidationError(u"You must select a value.")
48+
49+
return obj
50+

ietf/liaisons/forms.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
get_person_for_user, is_secretariat, is_sdo_liaison_manager)
1212
from ietf.liaisons.utils import IETFHM
1313
from ietf.liaisons.widgets import (FromWidget, ReadOnlyWidget, ButtonWidget,
14-
ShowAttachmentsWidget, RelatedLiaisonWidget)
14+
ShowAttachmentsWidget)
1515
from ietf.liaisons.models import LiaisonStatement, LiaisonStatementPurposeName
16+
from ietf.liaisons.fields import SearchableLiaisonStatementField
1617
from ietf.group.models import Group, Role
1718
from ietf.person.models import Person, Email
1819
from ietf.doc.models import Document
20+
from ietf.utils.fields import DatepickerDateField
1921

2022

2123
class LiaisonForm(forms.Form):
@@ -28,8 +30,9 @@ class LiaisonForm(forms.Form):
2830
technical_contact = forms.CharField(required=False, max_length=255)
2931
cc1 = forms.CharField(widget=forms.Textarea, label="CC", required=False, help_text='Please insert one email address per line')
3032
purpose = forms.ChoiceField()
31-
deadline_date = forms.DateField(label='Deadline')
32-
submitted_date = forms.DateField(label='Submission date', initial=datetime.date.today())
33+
related_to = SearchableLiaisonStatementField(label=u'Related Liaison Statement', required=False)
34+
deadline_date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Deadline', required=True)
35+
submitted_date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Submission date', required=True, initial=datetime.date.today())
3336
title = forms.CharField(label=u'Title')
3437
body = forms.CharField(widget=forms.Textarea, required=False)
3538
attachments = forms.CharField(label='Attachments', widget=ShowAttachmentsWidget, required=False)
@@ -40,13 +43,12 @@ class LiaisonForm(forms.Form):
4043
require=['id_attach_title', 'id_attach_file'],
4144
required_label='title and file'),
4245
required=False)
43-
related_to = forms.ModelChoiceField(LiaisonStatement.objects.all(), label=u'Related Liaison', widget=RelatedLiaisonWidget, required=False)
4446

4547
fieldsets = [('From', ('from_field', 'replyto')),
4648
('To', ('organization', 'to_poc')),
4749
('Other email addresses', ('response_contact', 'technical_contact', 'cc1')),
4850
('Purpose', ('purpose', 'deadline_date')),
49-
('References', ('related_to', )),
51+
('Reference', ('related_to', )),
5052
('Liaison Statement', ('title', 'submitted_date', 'body', 'attachments')),
5153
('Add attachment', ('attach_title', 'attach_file', 'attach_button')),
5254
]
@@ -80,7 +82,7 @@ def __init__(self, user, *args, **kwargs):
8082
self.initial["title"] = self.instance.title
8183
self.initial["body"] = self.instance.body
8284
self.initial["attachments"] = self.instance.attachments.all()
83-
self.initial["related_to"] = self.instance.related_to_id
85+
self.initial["related_to"] = self.instance.related_to
8486
if "approved" in self.fields:
8587
self.initial["approved"] = bool(self.instance.approved)
8688

ietf/liaisons/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@ def name(self):
4949
return slugify("liaison" + " " + self.submitted.strftime("%Y-%m-%d") + " " + frm[:50] + " " + to[:50] + " " + self.title[:115])
5050

5151
def __unicode__(self):
52-
return self.title or u"<no title>"
52+
return self.title

ietf/liaisons/urls.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
urlpatterns = patterns('',
77
(r'^help/$', TemplateView.as_view(template_name='liaisons/help.html')),
8-
(r'^help/fields/$', TemplateView.as_view(template_name='liaisons/field_help.html')),
8+
url(r'^help/fields/$', TemplateView.as_view(template_name='liaisons/field_help.html'), name="liaisons_field_help"),
99
(r'^help/from_ietf/$', TemplateView.as_view(template_name='liaisons/guide_from_ietf.html')),
1010
(r'^help/to_ietf/$', TemplateView.as_view(template_name='liaisons/guide_to_ietf.html')),
1111
(r'^managers/$', RedirectView.as_view(url='http://www.ietf.org/liaison/managers.html')),
@@ -18,6 +18,6 @@
1818
url(r'^for_approval/$', 'liaison_approval_list', name='liaison_approval_list'),
1919
url(r'^for_approval/(?P<object_id>\d+)/$', 'liaison_approval_detail', name='liaison_approval_detail'),
2020
url(r'^add/$', 'add_liaison', name='add_liaison'),
21-
url(r'^ajax/get_info/$', 'get_info'),
22-
url(r'^ajax/liaison_list/$', 'ajax_liaison_list', name='ajax_liaison_list'),
21+
url(r'^ajax/get_info/$', 'ajax_get_liaison_info'),
22+
url(r'^ajax/select2search/$', 'ajax_select2_search_liaison_statements', name='ajax_select2_search_liaison_statements'),
2323
)

ietf/liaisons/views.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from ietf.liaisons.forms import liaison_form_factory
1717
from ietf.liaisons.utils import IETFHM, can_submit_liaison_required, approvable_liaison_statements
1818
from ietf.liaisons.mails import notify_pending_by_email, send_liaison_by_email
19+
from ietf.liaisons.fields import select2_id_liaison_json
1920

2021

2122

@@ -44,7 +45,7 @@ def add_liaison(request, liaison=None):
4445

4546

4647
@can_submit_liaison_required
47-
def get_info(request):
48+
def ajax_get_liaison_info(request):
4849
person = get_person_for_user(request.user)
4950

5051
to_entity_id = request.GET.get('to_entity_id', None)
@@ -110,14 +111,20 @@ def liaison_list(request):
110111
"sort": sort,
111112
}, context_instance=RequestContext(request))
112113

113-
def ajax_liaison_list(request):
114-
sort, order_by = normalize_sort(request)
115-
liaisons = LiaisonStatement.objects.exclude(approved=None).order_by(order_by)
114+
def ajax_select2_search_liaison_statements(request):
115+
q = [w.strip() for w in request.GET.get('q', '').split() if w.strip()]
116116

117-
return render_to_response('liaisons/liaison_table.html', {
118-
"liaisons": liaisons,
119-
"sort": sort,
120-
}, context_instance=RequestContext(request))
117+
if not q:
118+
objs = LiaisonStatement.objects.none()
119+
else:
120+
qs = LiaisonStatement.objects.exclude(approved=None).all()
121+
122+
for t in q:
123+
qs = qs.filter(title__icontains=t)
124+
125+
objs = qs.distinct().order_by("-id")[:20]
126+
127+
return HttpResponse(select2_id_liaison_json(objs), content_type='application/json')
121128

122129
@can_submit_liaison_required
123130
def liaison_approval_list(request):

ietf/liaisons/widgets.py

Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
from django.conf import settings
2-
from django.core.urlresolvers import reverse as urlreverse
32
from django.db.models.query import QuerySet
4-
from django.forms.widgets import Select, Widget, TextInput
3+
from django.forms.widgets import Select, Widget
54
from django.utils.safestring import mark_safe
65
from django.utils.html import conditional_escape
76

8-
from ietf.liaisons.models import LiaisonStatement
9-
107

118
class FromWidget(Select):
129

@@ -28,7 +25,7 @@ def render(self, name, value, attrs=None, choices=()):
2825
value = option[0]
2926
text = option[1]
3027
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))
31-
base += u' (<a class="from_mailto" href="">' + conditional_escape(self.submitter) + u'</a>)'
28+
base += u' <a class="from_mailto form-control" href="">' + conditional_escape(self.submitter) + u'</a>'
3229
if self.full_power_on:
3330
base += '<div style="display: none;" class="reducedToOptions">'
3431
for from_code in self.full_power_on:
@@ -40,9 +37,8 @@ def render(self, name, value, attrs=None, choices=()):
4037

4138

4239
class ReadOnlyWidget(Widget):
43-
4440
def render(self, name, value, attrs=None):
45-
html = u'<div id="id_%s">%s</div>' % (conditional_escape(name), conditional_escape(value or ''))
41+
html = u'<div id="id_%s" class="form-control" style="height: auto; min-height: 34px;">%s</div>' % (conditional_escape(name), conditional_escape(value or ''))
4642
return mark_safe(html)
4743

4844

@@ -63,49 +59,20 @@ def render(self, name, value, attrs=None):
6359
html += u'<span style="display: none" class="attachRequiredField">%s</span>' % conditional_escape(i)
6460
required_str = u'Please fill in %s to attach a new file' % conditional_escape(self.required_label)
6561
html += u'<span style="display: none" class="attachDisabledLabel">%s</span>' % conditional_escape(required_str)
66-
html += u'<input type="button" class="addAttachmentWidget" value="%s" />' % conditional_escape(self.label)
62+
html += u'<input type="button" class="addAttachmentWidget btn btn-primary btn-sm" value="%s" />' % conditional_escape(self.label)
6763
return mark_safe(html)
6864

6965

7066
class ShowAttachmentsWidget(Widget):
7167

7268
def render(self, name, value, attrs=None):
7369
html = u'<div id="id_%s">' % name
74-
html += u'<span style="display: none" class="showAttachmentsEmpty">No files attached</span>'
75-
html += u'<div class="attachedFiles">'
70+
html += u'<span style="display: none" class="showAttachmentsEmpty form-control" style="height: auto; min-height: 34px;">No files attached</span>'
71+
html += u'<div class="attachedFiles form-control" style="height: auto; min-height: 34px;">'
7672
if value and isinstance(value, QuerySet):
7773
for attachment in value:
7874
html += u'<a class="initialAttach" href="%s%s">%s</a><br />' % (settings.LIAISON_ATTACH_URL, conditional_escape(attachment.external_url), conditional_escape(attachment.title))
7975
else:
8076
html += u'No files attached'
8177
html += u'</div></div>'
8278
return mark_safe(html)
83-
84-
85-
class RelatedLiaisonWidget(TextInput):
86-
87-
def render(self, name, value, attrs=None):
88-
if not value:
89-
value = ''
90-
title = ''
91-
noliaison = 'inline'
92-
deselect = 'none'
93-
else:
94-
liaison = LiaisonStatement.objects.get(pk=value)
95-
title = liaison.title
96-
if not title:
97-
attachments = liaison.attachments.all()
98-
if attachments:
99-
title = attachments[0].title
100-
else:
101-
title = 'Liaison #%s' % liaison.pk
102-
noliaison = 'none'
103-
deselect = 'inline'
104-
html = u'<span class="noRelated" style="display: %s;">No liaison selected</span>' % conditional_escape(noliaison)
105-
html += u'<span class="relatedLiaisonWidgetTitle">%s</span>' % conditional_escape(title)
106-
html += u'<input type="hidden" name="%s" class="relatedLiaisonWidgetValue" value="%s" /> ' % (conditional_escape(name), conditional_escape(value))
107-
html += u'<span style="display: none;" class="listURL">%s</span> ' % urlreverse('ajax_liaison_list')
108-
html += u'<div style="display: none;" class="relatedLiaisonWidgetDialog" id="related-dialog" title="Select a liaison"></div> '
109-
html += '<input type="button" id="id_%s" value="Select liaison" /> ' % conditional_escape(name)
110-
html += '<input type="button" style="display: %s;" id="id_no_%s" value="Deselect liaison" />' % (conditional_escape(deselect), conditional_escape(name))
111-
return mark_safe(html)

ietf/templates/liaisons/edit.html

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,65 @@
1-
{% extends "base.html" %}
2-
{# Copyright The IETF Trust 2007, All Rights Reserved #}
3-
{% load ietf_filters %}
1+
{% extends "ietf.html" %}
2+
3+
{% load bootstrap3 %}
4+
45
{% block title %}{% if liaison %}Edit liaison: {{ liaison }}{% else %}Send Liaison Statement{% endif %}{% endblock %}
56

67
{% block pagehead %}
7-
<link rel="stylesheet" type="text/css" href="/css/liaisons.css"></link>
8-
<link rel="stylesheet" type="text/css" href="/css/jquery-ui-themes/jquery-ui-1.8.11.custom.css"></link>
8+
<link rel="stylesheet" href="/facelift/css/lib/datepicker3.css"></link>
9+
<link rel="stylesheet" href="/facelift/css/lib/select2.css">
10+
<link rel="stylesheet" href="/facelift/css/lib/select2-bootstrap.css">
911
{% endblock %}
1012

1113
{% block content %}
14+
1215
<h1>{% if liaison %}Edit liaison: {{ liaison }}{% else %}Send Liaison Statement{% endif %}</h1>
1316

17+
{% bootstrap_messages %}
18+
19+
{% if form.errors %}
20+
<div class="alert alert-danger">
21+
<p>There were errors in the submitted form -- see below. Please correct these and resubmit.</p>
22+
</div>
23+
{% endif %}
24+
25+
{% bootstrap_form_errors form %}
26+
1427
<noscript class="js-info">
15-
This page depends on Javascript being enabled to work properly. Please enable Javascript and reload the page.
28+
This page depends on Javascript being enabled to work properly. Please enable Javascript and reload the page.
1629
</noscript>
1730

1831
{% if not liaison %}
19-
<ul>
20-
<li>If you wish to submit your liaison statement by e-mail, then please send it to <a href="mailto:statements@ietf.org">statements@ietf.org</a></li>
21-
<li>Fields marked with <span class="fieldRequired">*</span> are required. For detailed descriptions of the fields see <a href="/liaison/help/fields/">Field help</a></li>
22-
</ul>
32+
<p class="help-block">If you wish to submit your liaison statement by e-mail, then please send it to <a href="mailto:statements@ietf.org">statements@ietf.org</a></p>
33+
34+
<p class="help-block">Fields marked with <label class="required"></label> are required. For detailed descriptions of the fields see the <a href="{% url "liaisons_field_help" %}">field help</a>.</p>
2335
{% endif %}
2436

25-
{# Replace the form expansion with an include as a temporary workaround for missing CSRF tokens #}
26-
{# {{ form }} #}
27-
{% include "liaisons/liaisonform.html" %}
37+
<form role="form" class="liaisons form-horizontal" method="post" enctype="multipart/form-data" data-ajax-info-url="{% url "ietf.liaisons.views.ajax_get_liaison_info" %}">{% csrf_token %}
38+
39+
{% for fieldset in form.get_fieldsets %}
40+
<h2>{{ fieldset.name }}</h2>
41+
42+
{% for field in fieldset.fields %}
43+
{% bootstrap_field field layout="horizontal" %}
44+
{% endfor %}
45+
{% endfor %}
46+
47+
{% buttons %}
48+
<a class="btn btn-danger pull-right" href="{% url "liaison_list" %}">Cancel</a>
49+
50+
{% if not liaison %}
51+
<button name="send" type="submit" class="btn btn-primary">Send and post</button>
52+
<button name="post_only" type="submit" class="btn btn-default">Post only</button>
53+
{% else %}
54+
<button name="save" type="submit" class="btn btn-primary">Save</button>
55+
{% endif %}
56+
{% endbuttons %}
57+
</form>
2858

2959
{% endblock %}
3060

3161
{% block js %}
32-
<script type="text/javascript" src="/js/jquery-ui-1.9.0.custom/minified/jquery-ui.custom.min.js"></script>
33-
<script type="text/javascript" src="/js/liaisons.js"></script>
62+
<script src="/facelift/js/lib/bootstrap-datepicker.js"></script>
63+
<script src="/facelift/js/lib/select2-3.5.2.min.js"></script>
64+
<script src="/facelift/js/liaisons.js"></script>
3465
{% endblock %}

ietf/templates/liaisons/liaison_table.html

Lines changed: 0 additions & 49 deletions
This file was deleted.

0 commit comments

Comments
 (0)