Skip to content

Commit 0ccbe91

Browse files
committed
Allow multiple related liaisons. Close ietf-tools#739
- Legacy-Id: 8103
1 parent 439a3b1 commit 0ccbe91

7 files changed

Lines changed: 129 additions & 61 deletions

File tree

ietf/liaisons/forms.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@
1212
from django.utils.encoding import force_text
1313
from django.utils.safestring import mark_safe
1414

15-
15+
from ietf.name.models import DocRelationshipName
1616
from ietf.liaisons.accounts import (can_add_outgoing_liaison, can_add_incoming_liaison,
1717
get_person_for_user, is_secretariat, is_sdo_liaison_manager)
1818
from ietf.liaisons.utils import IETFHM
1919
from ietf.liaisons.widgets import (FromWidget, ReadOnlyWidget, ButtonWidget,
2020
ShowAttachmentsWidget, RelatedLiaisonWidget)
21-
from ietf.liaisons.models import LiaisonStatement, LiaisonStatementPurposeName, LiaisonStatementEvent
21+
from ietf.liaisons.models import (LiaisonStatement, LiaisonStatementPurposeName, LiaisonStatementEvent,
22+
RelatedLiaisonStatement)
2223
from ietf.group.models import Group, Role
2324
from ietf.person.models import Person, Email
2425
from ietf.doc.models import Document
@@ -45,7 +46,7 @@ class LiaisonForm(forms.Form):
4546
require=['id_attach_title', 'id_attach_file'],
4647
required_label='title and file'),
4748
required=False)
48-
related_to = forms.ModelChoiceField(LiaisonStatement.objects.all(), label=u'Related Liaison', widget=RelatedLiaisonWidget, required=False)
49+
related_to = forms.ModelMultipleChoiceField(LiaisonStatement.objects.all(), label=u'Related Liaison', widget=RelatedLiaisonWidget, required=False)
4950

5051
fieldsets = [('From', ('from_field', 'response_contacts')),
5152
('To', ('organization', 'to_poc')),
@@ -162,6 +163,10 @@ def check_email(self, value):
162163
except UnicodeEncodeError as e:
163164
raise forms.ValidationError('Invalid email address: %s (check character %d)' % (addr,e.start))
164165

166+
def clean_related_to(self):
167+
related_list = self.data.getlist('related_to')
168+
return [i for i in related_list if i]
169+
165170
def clean_technical_contact(self):
166171
value = self.cleaned_data.get('technical_contact', None)
167172
self.check_email(value)
@@ -218,21 +223,16 @@ def save(self, *args, **kwargs):
218223
l.purpose = LiaisonStatementPurposeName.objects.get(order=self.cleaned_data["purpose"])
219224
l.body = self.cleaned_data["body"].strip()
220225
l.deadline = self.cleaned_data["deadline_date"]
221-
l.related_to = self.cleaned_data["related_to"]
222226
l.response_contacts = self.cleaned_data["response_contacts"]
223227
l.technical_contact = self.cleaned_data["technical_contact"]
224228

225229
now = datetime.datetime.now()
226230

227-
l.modified = now
228-
l.submitted = datetime.datetime.combine(self.cleaned_data["submitted_date"], now.time())
229-
if not l.approved:
230-
l.approved = now
231-
232231
self.save_extra_fields(l)
233232

234233
l.save() # we have to save here to make sure we get an id for the attachments
235234
self.save_attachments(l)
235+
self.save_related_liaisons(l)
236236

237237
return l
238238

@@ -278,6 +278,16 @@ def save_attachments(self, instance):
278278
attach_file.write(attached_file.read())
279279
attach_file.close()
280280

281+
def save_related_liaisons(self, instance):
282+
rel = DocRelationshipName.objects.get(slug='refold')
283+
for i in self.cleaned_data.get('related_to', []):
284+
try:
285+
instance.source_of_set.get_or_create(
286+
target=LiaisonStatement.objects.get(id=i),
287+
relationship=rel)
288+
except LiaisonStatement.DoesNotExist:
289+
continue
290+
281291
def clean_title(self):
282292
title = self.cleaned_data.get('title', None)
283293
if self.instance and self.instance.pk:
@@ -389,10 +399,10 @@ def save_extra_fields(self, liaison):
389399
super(OutgoingLiaisonForm, self).save_extra_fields(liaison)
390400
from_entity = self.get_from_entity()
391401
needs_approval = from_entity.needs_approval(self.person)
392-
if not needs_approval or self.cleaned_data.get('approved', False):
393-
liaison.approved = datetime.datetime.now()
394-
else:
395-
liaison.approved = None
402+
#if not needs_approval or self.cleaned_data.get('approved', False):
403+
#liaison.approved = datetime.datetime.now()
404+
#else:
405+
#liaison.approved = None
396406

397407
def clean_to_poc(self):
398408
value = self.cleaned_data.get('to_poc', None)

ietf/liaisons/models.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ class LiaisonStatement(models.Model):
3939
state = models.ForeignKey(LiaisonStatementState, default='pending')
4040

4141
def name(self):
42-
if self.from_group:
43-
frm = self.from_group.acronym or self.from_group.name
42+
if self.from_groups.count():
43+
frm = ', '.join([i.acronym or i.name for i in self.from_groups.all()])
4444
else:
4545
frm = self.from_name
46-
if self.to_group:
47-
to = self.to_group.acronym or self.to_group.name
46+
if self.to_groups.count():
47+
to = ', '.join([i.acronym or i.name for i in self.to_groups.all()])
4848
else:
4949
to = self.to_name
5050
return slugify("liaison" + " " + self.submitted.strftime("%Y-%m-%d") + " " + frm[:50] + " " + to[:50] + " " + self.title[:115])

ietf/liaisons/views.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,10 @@ def liaison_list(request):
119119
}, context_instance=RequestContext(request))
120120

121121
def ajax_liaison_list(request):
122-
sort, order_by = normalize_sort(request)
123-
liaisons = LiaisonStatement.objects.exclude(approved=None).order_by(order_by)
122+
liaisons = SearchLiaisonForm().get_results()
124123

125124
return render_to_response('liaisons/liaison_table.html', {
126125
"liaisons": liaisons,
127-
"sort": sort,
128126
}, context_instance=RequestContext(request))
129127

130128
@can_submit_liaison_required
@@ -147,8 +145,13 @@ def liaison_approval_detail(request, object_id):
147145
send_liaison_by_email(request, liaison)
148146
return redirect('liaison_list')
149147

148+
relations_by = [i.target for i in liaison.source_of_set.filter(target__state__slug='approved')]
149+
relations_to = [i.source for i in liaison.target_of_set.filter(source__state__slug='approved')]
150+
150151
return render_to_response('liaisons/approval_detail.html', {
151152
"liaison": liaison,
153+
"relations_to": relations_to,
154+
"relations_by": relations_by,
152155
"is_approving": True,
153156
}, context_instance=RequestContext(request))
154157

@@ -201,13 +204,15 @@ def liaison_detail(request, object_id):
201204
liaison.save()
202205
can_take_care = False
203206

204-
relations = liaison.source_of_set.filter(target__state__slug='approved')
207+
relations_by = [i.target for i in liaison.source_of_set.filter(target__state__slug='approved')]
208+
relations_to = [i.source for i in liaison.target_of_set.filter(source__state__slug='approved')]
205209

206210
return render_to_response("liaisons/detail.html", {
207211
"liaison": liaison,
208212
"can_edit": can_edit,
209213
"can_take_care": can_take_care,
210-
"relations": relations,
214+
"relations_to": relations_to,
215+
"relations_by": relations_by,
211216
}, context_instance=RequestContext(request))
212217

213218
def liaison_edit(request, object_id):

ietf/liaisons/widgets.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,13 @@ def render(self, name, value, attrs=None):
8484

8585
class RelatedLiaisonWidget(TextInput):
8686

87+
def value_from_datadict(self, data, files, name):
88+
return [i for i in data.getlist(name) if i]
89+
8790
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)
91+
html = u''
92+
for liaison_id in value:
93+
liaison = LiaisonStatement.objects.get(pk=liaison_id)
9594
title = liaison.title
9695
if not title:
9796
attachments = liaison.attachments.all()
@@ -101,11 +100,21 @@ def render(self, name, value, attrs=None):
101100
title = 'Liaison #%s' % liaison.pk
102101
noliaison = 'none'
103102
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))
103+
html += u'<div>'
104+
html += u'<span class="noRelated" style="display: %s;"></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(liaison_id))
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 statement"></div> '
109+
html += '<input type="button" style="display: %s;" class="id_no_%s" id="id_no_%s" value="Deselect liaison statement" />' % (conditional_escape(deselect), conditional_escape(name), conditional_escape(name))
110+
html += u'</div>'
111+
html += u'<div>'
112+
html += u'<span class="noRelated" style="display: inline;"></span>'
113+
html += u'<span class="relatedLiaisonWidgetTitle"></span>'
114+
html += u'<input type="hidden" name="%s" class="relatedLiaisonWidgetValue" value="" /> ' % conditional_escape(name)
107115
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))
116+
html += u'<div style="display: none;" class="relatedLiaisonWidgetDialog" id="related-dialog" title="Select a liaison statement"></div> '
117+
html += '<input type="button" class="id_%s" id="id_%s" value="Select liaison statement" /> ' % (conditional_escape(name), conditional_escape(name))
118+
html += '<input type="button" style="display: none;" class="id_no_%s" id="id_no_%s" value="Deselect liaison statement" />' % (conditional_escape(name), conditional_escape(name))
119+
html += u'</div>'
111120
return mark_safe(html)

ietf/templates/liaisons/detail.html

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,37 +21,47 @@ <h1>Liaison Statement: {% include 'liaisons/liaison_title.html' %}</h1>
2121
<tr>
2222
<td style="width:18ex;">Submission Date:</td>
2323
<td>{{ liaison.submitted|date:"Y-m-d" }}</td></tr>
24+
{% if liaison.from_contact %}<tr>
25+
<td>Sender:</td>
26+
<td>
27+
<a href="mailto:{{ liaison.from_contact.address }}">{{ liaison.from_contact.person }}</a>
28+
</td>
29+
</tr>{% endif %}
2430
<tr>
2531
<td>From:</td>
26-
<td>{{ liaison.from_name }}
27-
{% if liaison.from_contact %}(<a href="mailto:{{ liaison.from_contact.address }}">{{ liaison.from_contact.person }}</a>){% endif %}
32+
<td>
33+
{% for group in liaison.from_groups.all %}
34+
{{ group.name }}
35+
{% empty %}
36+
{{ liaison.from_name }}
37+
{% endfor %}
2838
</td>
2939
</tr>
3040

3141
<tr>
3242
<td>To:</td>
3343
<td>
34-
{% if liaison.from_contact %}
35-
{{ liaison.to_name }} ({{ liaison.to_contact|parse_email_list }})
36-
{% else %}
37-
{{ liaison.to_name|urlize }}
38-
{% endif %}
44+
{% for group in liaison.to_groups.all %}
45+
{{ group.name }}
46+
{% empty %}
47+
{{ liaison.to_name }}
48+
{% endfor %}
3949
</td>
4050
</tr>
4151

4252
{% if liaison.from_contact %}
4353
<tr>
44-
<td>Cc:</td><td>{{ liaison.cc|parse_email_list|make_one_per_line|safe|linebreaksbr }}</td>
54+
<td>Cc:</td><td>{{ liaison.cc_contacts|parse_email_list|make_one_per_line|safe|linebreaksbr }}</td>
4555
</tr>
4656

4757
<tr>
4858
<td>Response Contact:</td>
49-
<td>{{ liaison.response_contact|parse_email_list|make_one_per_line|safe|linebreaksbr }}</td>
59+
<td>{{ liaison.response_contacts|parse_email_list|make_one_per_line|safe|linebreaksbr }}</td>
5060
</tr>
5161

5262
<tr>
5363
<td>Technical Contact:</td>
54-
<td>{{ liaison.technical_contact|parse_email_list|make_one_per_line|safe|linebreaksbr }}</td>
64+
<td>{{ liaison.technical_contacts|parse_email_list|make_one_per_line|safe|linebreaksbr }}</td>
5565
</tr>
5666

5767
<tr>
@@ -74,11 +84,22 @@ <h1>Liaison Statement: {% include 'liaisons/liaison_title.html' %}</h1>
7484
{% endif %}
7585
{% endif %}
7686

77-
{% if relations %}
87+
{% if relations_by %}
88+
<tr>
89+
<td>Liaisons referred by this one:</td>
90+
<td>
91+
{% for rel in relations_by %}
92+
<a href="{% url "liaison_detail" rel.pk %}">{% if rel.title %}{{ rel.title }}{% else %}Liaison #{{ rel.pk }}{% endif %}</a><br />
93+
{% endfor %}
94+
</td>
95+
</tr>
96+
{% endif %}
97+
98+
{% if relations_to %}
7899
<tr>
79100
<td>Liaisons referring to this one:</td>
80101
<td>
81-
{% for rel in relations %}
102+
{% for rel in relations_to %}
82103
<a href="{% url "liaison_detail" rel.pk %}">{% if rel.title %}{{ rel.title }}{% else %}Liaison #{{ rel.pk }}{% endif %}</a><br />
83104
{% endfor %}
84105
</td>

ietf/templates/liaisons/edit.html

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,27 @@ <h1>{% if liaison %}Edit liaison: {{ liaison }}{% else %}Send Liaison Statement{
3030

3131
{% block js %}
3232
<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/jquery.tablesorter.min.js"></script>
3334
<script type="text/javascript" src="/js/liaisons.js"></script>
35+
<script type="text/javascript">
36+
$(document).ready(function()
37+
{
38+
var widgetIETF = {css: ["evenrow", "oddrow"]};
39+
$.tablesorter.addWidget({
40+
id: "ietf",
41+
format: function(table) {
42+
var $tr, row = -1, odd;
43+
$("tr:visible", table.tBodies[0]).each(function (i) {
44+
$tr = $(this);
45+
if (!$tr.hasClass(table.config.cssChildRow)) row++;
46+
odd = (row % 2 == 0);
47+
$tr.removeClass(
48+
widgetIETF.css[odd ? 0 : 1]).addClass(
49+
widgetIETF.css[odd ? 1 : 0])
50+
});
51+
}
52+
});
53+
}
54+
);
55+
</script>
3456
{% endblock %}

static/js/liaisons.js

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,10 @@ jQuery(function () {
138138
var cancel = form.find('#id_cancel');
139139
var cancel_dialog = form.find('#cancel-dialog');
140140
var config = {};
141-
var related_trigger = form.find('#id_related_to');
141+
var related_trigger = form.find('.id_related_to');
142142
var related_url = form.find('#id_related_to').parent().find('.listURL').text();
143143
var related_dialog = form.find('#related-dialog');
144-
var unrelate_trigger = form.find('#id_no_related_to');
144+
var unrelate_trigger = form.find('.id_no_related_to');
145145

146146
var readConfig = function() {
147147
var confcontainer = form.find('.formconfig');
@@ -283,20 +283,22 @@ jQuery(function () {
283283
var link = jQuery(this).text();;
284284
var pk = jQuery(this).nextAll('.liaisonPK').text();
285285
var widget = related_trigger.parent();
286-
widget.find('.relatedLiaisonWidgetTitle').text(link);
287-
widget.find('.relatedLiaisonWidgetValue').val(pk);
288-
widget.find('.noRelated').hide();
289-
unrelate_trigger.show();
286+
var newwidget = widget.clone()
290287
related_dialog.dialog('close');
288+
newwidget.find('.id_related_to').click(selectRelated);
289+
newwidget.find('.id_no_related_to').click(selectNoRelated);
290+
newwidget.find('.relatedLiaisonWidgetTitle').text(link);
291+
newwidget.find('.relatedLiaisonWidgetValue').val(pk);
292+
newwidget.find('.noRelated').hide();
293+
newwidget.find('.id_related_to').hide();
294+
newwidget.find('.id_no_related_to').show();
295+
widget.before(newwidget);
291296
return false;
292297
};
293298

294299
var selectNoRelated = function() {
295300
var widget = jQuery(this).parent();
296-
widget.find('.relatedLiaisonWidgetTitle').text('');
297-
widget.find('.noRelated').show();
298-
widget.find('.relatedLiaisonWidgetValue').val('');
299-
jQuery(this).hide();
301+
widget.remove();
300302
return false;
301303
};
302304

@@ -315,10 +317,9 @@ jQuery(function () {
315317
dataType: 'html',
316318
success: function(response){
317319
related_dialog.html(response);
318-
related_dialog.find('th a').click(function() {
319-
widget.find('.listURL').text(related_url + jQuery(this).attr('href'));
320-
trigger.click();
321-
return false;
320+
related_dialog.find("#LiaisonListTable").tablesorter({
321+
sortList: [[0, 1]],
322+
widgets: ["ietf"]
322323
});
323324
related_dialog.find('td a').click(getRelatedLink);
324325
}

0 commit comments

Comments
 (0)