Skip to content

Commit 173e438

Browse files
Add 'closed' session purpose, assign purposes for nomcom groups, and update schedule editor to enforce timeslot type and allow blurring sessions by purpose
- Legacy-Id: 19427
1 parent 5318081 commit 173e438

7 files changed

Lines changed: 131 additions & 51 deletions

File tree

ietf/group/migrations/0052_populate_groupfeatures_session_purposes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
default_purposes = dict(
99
dir=['presentation', 'social', 'tutorial'],
1010
ietf=['admin', 'presentation', 'social'],
11+
nomcom=['closed', 'officehours'],
1112
rg=['session'],
1213
team=['coding', 'presentation', 'social', 'tutorial'],
1314
wg=['session'],

ietf/meeting/models.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,11 @@ def that_can_meet(self):
11461146
type__slug='regular'
11471147
)
11481148

1149+
def requests(self):
1150+
"""Queryset containing sessions that may be handled as requests"""
1151+
return self.exclude(
1152+
type__in=('offagenda', 'reserved', 'unavail')
1153+
)
11491154

11501155
class Session(models.Model):
11511156
"""Session records that a group should have a session on the

ietf/meeting/views.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,9 @@ def new_meeting_schedule(request, num, owner=None, name=None):
500500

501501
@ensure_csrf_cookie
502502
def edit_meeting_schedule(request, num=None, owner=None, name=None):
503+
# Need to coordinate this list with types of session requests
504+
# that can be created (see, e.g., SessionQuerySet.requests())
505+
IGNORE_TIMESLOT_TYPES = ('offagenda', 'reserved', 'unavail')
503506
meeting = get_meeting(num)
504507
if name is None:
505508
schedule = meeting.schedule
@@ -544,20 +547,22 @@ def timeslot_locked(ts):
544547
sessions = add_event_info_to_session_qs(
545548
Session.objects.filter(
546549
meeting=meeting,
547-
# type='regular',
550+
).exclude(
551+
type__in=IGNORE_TIMESLOT_TYPES,
548552
).order_by('pk'),
549553
requested_time=True,
550554
requested_by=True,
551555
).filter(
552556
Q(current_status__in=['appr', 'schedw', 'scheda', 'sched'])
553557
| Q(current_status__in=tombstone_states, pk__in={a.session_id for a in assignments})
554558
).prefetch_related(
555-
'resources', 'group', 'group__parent', 'group__type', 'joint_with_groups',
559+
'resources', 'group', 'group__parent', 'group__type', 'joint_with_groups', 'purpose',
556560
)
557561

558562
timeslots_qs = TimeSlot.objects.filter(
559563
meeting=meeting,
560-
# type='regular',
564+
).exclude(
565+
type__in=IGNORE_TIMESLOT_TYPES,
561566
).prefetch_related('type').order_by('location', 'time', 'name')
562567

563568
min_duration = min(t.duration for t in timeslots_qs)
@@ -591,10 +596,14 @@ def prepare_sessions_for_display(sessions):
591596
s.requested_by_person = requested_by_lookup.get(s.requested_by)
592597

593598
s.scheduling_label = "???"
594-
if (s.purpose is None or s.purpose.slug == 'regular') and s.group:
599+
s.purpose_label = None
600+
if (s.purpose is None or s.purpose.slug == 'session') and s.group:
595601
s.scheduling_label = s.group.acronym
596-
elif s.name:
597-
s.scheduling_label = s.name
602+
s.purpose_label = 'BoF' if s.group.is_bof() else s.group.type.name
603+
else:
604+
s.purpose_label = s.purpose.name
605+
if s.name:
606+
s.scheduling_label = s.name
598607

599608
s.requested_duration_in_hours = round(s.requested_duration.seconds / 60.0 / 60.0, 1)
600609

@@ -981,6 +990,8 @@ def cubehelix(i, total, hue=1.2, start_angle=0.5):
981990
p.scheduling_color = "rgb({}, {}, {})".format(*tuple(int(round(x * 255)) for x in rgb_color))
982991
p.light_scheduling_color = "rgb({}, {}, {})".format(*tuple(int(round((0.9 + 0.1 * x) * 255)) for x in rgb_color))
983992

993+
session_purposes = sorted(set(s.purpose for s in sessions if s.purpose), key=lambda p: p.name)
994+
984995
return render(request, "meeting/edit_meeting_schedule.html", {
985996
'meeting': meeting,
986997
'schedule': schedule,
@@ -991,6 +1002,7 @@ def cubehelix(i, total, hue=1.2, start_angle=0.5):
9911002
'timeslot_groups': sorted((d, list(sorted(t_groups))) for d, t_groups in timeslot_groups.items()),
9921003
'unassigned_sessions': unassigned_sessions,
9931004
'session_parents': session_parents,
1005+
'session_purposes': session_purposes,
9941006
'hide_menu': True,
9951007
'lock_time': lock_time,
9961008
})
@@ -2261,14 +2273,10 @@ def agenda_json(request, num=None):
22612273

22622274
def meeting_requests(request, num=None):
22632275
meeting = get_meeting(num)
2264-
sessions = add_event_info_to_session_qs(
2265-
Session.objects.filter(
2266-
meeting__number=meeting.number,
2267-
# type__slug='regular',
2268-
group__parent__isnull=False
2269-
),
2270-
requested_by=True,
2271-
).exclude(
2276+
sessions = Session.objects.requests().filter(
2277+
meeting__number=meeting.number,
2278+
group__parent__isnull=False
2279+
).with_current_status().with_requested_by().exclude(
22722280
requested_by=0
22732281
).order_by(
22742282
"group__parent__acronym", "current_status", "group__acronym"

ietf/name/migrations/0036_populate_sessionpurposename.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ def forward(apps, schema_editor):
1616
('coding', 'Coding', 'Coding session', ['other']),
1717
('admin', 'Administrative', 'Meeting administration', ['other', 'reg']),
1818
('social', 'Social', 'Social event or activity', ['other']),
19-
('presentation', 'Presentation', 'Presentation session', ['other', 'regular'])
19+
('presentation', 'Presentation', 'Presentation session', ['other', 'regular']),
20+
('closed', 'Closed meeting', 'Closed meeting', ['other',]),
2021
)):
2122
# verify that we're not about to use an invalid purpose
2223
for ts_type in tstypes:

ietf/static/ietf/js/edit-meeting-schedule.js

Lines changed: 75 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ jQuery(document).ready(function () {
8282
jQuery(element).addClass("selected");
8383

8484
showConstraintHints(element);
85+
showTimeSlotTypeIndicators(element.dataset.type);
8586

8687
let sessionInfoContainer = content.find(".scheduling-panel .session-info-container");
8788
sessionInfoContainer.html(jQuery(element).find(".session-info").html());
@@ -105,6 +106,7 @@ jQuery(document).ready(function () {
105106
else {
106107
sessions.removeClass("selected");
107108
showConstraintHints();
109+
resetTimeSlotTypeIndicators();
108110
content.find(".scheduling-panel .session-info-container").html("");
109111
}
110112
}
@@ -203,6 +205,23 @@ jQuery(document).ready(function () {
203205
}
204206
}
205207

208+
/**
209+
* Remove timeslot classes indicating timeslot type disagreement
210+
*/
211+
function resetTimeSlotTypeIndicators() {
212+
timeslots.removeClass('wrong-timeslot-type');
213+
}
214+
215+
/**
216+
* Add timeslot classes indicating timeslot type disagreement
217+
*
218+
* @param timeslot_type
219+
*/
220+
function showTimeSlotTypeIndicators(timeslot_type) {
221+
timeslots.removeClass('wrong-timeslot-type');
222+
timeslots.filter('[data-type!="' + timeslot_type + '"]').addClass('wrong-timeslot-type');
223+
}
224+
206225
/**
207226
* Should this timeslot be treated as a future timeslot?
208227
*
@@ -277,19 +296,42 @@ jQuery(document).ready(function () {
277296
return Boolean(event.originalEvent.dataTransfer.getData(dnd_mime_type));
278297
}
279298

299+
/**
300+
* Get the session element being dragged
301+
*
302+
* @param event drag-related event
303+
*/
304+
function getDraggedSession(event) {
305+
if (!isSessionDragEvent(event)) {
306+
return null;
307+
}
308+
const sessionId = event.originalEvent.dataTransfer.getData(dnd_mime_type);
309+
const sessionElements = sessions.filter("#" + sessionId);
310+
if (sessionElements.length > 0) {
311+
return sessionElements[0];
312+
}
313+
return null;
314+
}
315+
280316
/**
281317
* Can a session be dropped in this element?
282318
*
283319
* Drop is allowed in drop-zones that are in unassigned-session or timeslot containers
284320
* not marked as 'past'.
285321
*/
286-
function sessionDropAllowed(elt) {
287-
if (!officialSchedule) {
288-
return true;
322+
function sessionDropAllowed(dropElement, sessionElement) {
323+
const relevant_parent = dropElement.closest('.timeslot, .unassigned-sessions');
324+
if (!relevant_parent || !sessionElement) {
325+
return false;
326+
}
327+
328+
if (officialSchedule && relevant_parent.classList.contains('past')) {
329+
return false;
289330
}
290331

291-
const relevant_parent = elt.closest('.timeslot, .unassigned-sessions');
292-
return relevant_parent && !(relevant_parent.classList.contains('past'));
332+
return !relevant_parent.dataset.type || (
333+
relevant_parent.dataset.type === sessionElement.dataset.type
334+
);
293335
}
294336

295337
if (!content.find(".edit-grid").hasClass("read-only")) {
@@ -314,7 +356,7 @@ jQuery(document).ready(function () {
314356
// dropping
315357
let dropElements = content.find(".timeslot .drop-target,.unassigned-sessions .drop-target");
316358
dropElements.on('dragenter', function (event) {
317-
if (sessionDropAllowed(this)) {
359+
if (sessionDropAllowed(this, getDraggedSession(event))) {
318360
event.preventDefault(); // default action is signalling that this is not a valid target
319361
jQuery(this).parent().addClass("dropping");
320362
}
@@ -324,46 +366,37 @@ jQuery(document).ready(function () {
324366
// we don't actually need this event, except we need to signal
325367
// that this is a valid drop target, by cancelling the default
326368
// action
327-
if (sessionDropAllowed(this)) {
369+
if (sessionDropAllowed(this, getDraggedSession(event))) {
328370
event.preventDefault();
329371
}
330372
});
331373

332374
dropElements.on('dragleave', function (event) {
333375
// skip dragleave events if they are to children
334376
const leaving_child = event.originalEvent.currentTarget.contains(event.originalEvent.relatedTarget);
335-
if (!leaving_child && sessionDropAllowed(this)) {
377+
if (!leaving_child && sessionDropAllowed(this, getDraggedSession(event))) {
336378
jQuery(this).parent().removeClass('dropping');
337379
}
338380
});
339381

340382
dropElements.on('drop', function (event) {
341383
let dropElement = jQuery(this);
342384

343-
if (!isSessionDragEvent(event)) {
344-
// event is result of something other than a session drag
345-
dropElement.parent().removeClass("dropping");
346-
return;
347-
}
348-
349-
const sessionId = event.originalEvent.dataTransfer.getData(dnd_mime_type);
350-
let sessionElement = sessions.filter("#" + sessionId);
351-
if (sessionElement.length === 0) {
352-
// drag event is not from a session we recognize
385+
const sessionElement = getDraggedSession(event);
386+
if (!sessionElement) {
387+
// not drag event or not from a session we recognize
353388
dropElement.parent().removeClass("dropping");
354389
return;
355390
}
356391

357-
// We now know this is a drop of a recognized session
358-
359-
if (!sessionDropAllowed(this)) {
392+
if (!sessionDropAllowed(this, sessionElement)) {
360393
dropElement.parent().removeClass("dropping"); // just in case
361394
return; // drop not allowed
362395
}
363396

364397
event.preventDefault(); // prevent opening as link
365398

366-
let dragParent = sessionElement.parent();
399+
let dragParent = jQuery(sessionElement).parent();
367400
if (dragParent.is(this)) {
368401
dropElement.parent().removeClass("dropping");
369402
return;
@@ -400,7 +433,7 @@ jQuery(document).ready(function () {
400433
timeout: 5 * 1000,
401434
data: {
402435
action: "unassign",
403-
session: sessionId.slice("session".length)
436+
session: sessionElement.id.slice("session".length)
404437
}
405438
}).fail(failHandler).done(done);
406439
}
@@ -410,7 +443,7 @@ jQuery(document).ready(function () {
410443
method: "post",
411444
data: {
412445
action: "assign",
413-
session: sessionId.slice("session".length),
446+
session: sessionElement.id.slice("session".length),
414447
timeslot: dropParent.attr("id").slice("timeslot".length)
415448
},
416449
timeout: 5 * 1000
@@ -673,7 +706,7 @@ jQuery(document).ready(function () {
673706
// toggling visible sessions by session parents
674707
let sessionParentInputs = content.find(".session-parent-toggles input");
675708

676-
function setSessionHidden(sess, hide) {
709+
function setSessionHiddenParent(sess, hide) {
677710
sess.toggleClass('hidden-parent', hide);
678711
sess.prop('draggable', !hide);
679712
}
@@ -684,13 +717,28 @@ jQuery(document).ready(function () {
684717
checked.push(".parent-" + this.value);
685718
});
686719

687-
setSessionHidden(sessions.not(".untoggleable").filter(checked.join(",")), false);
688-
setSessionHidden(sessions.not(".untoggleable").not(checked.join(",")), true);
720+
setSessionHiddenParent(sessions.not(".untoggleable-by-parent").filter(checked.join(",")), false);
721+
setSessionHiddenParent(sessions.not(".untoggleable-by-parent").not(checked.join(",")), true);
689722
}
690723

691724
sessionParentInputs.on("click", updateSessionParentToggling);
692725
updateSessionParentToggling();
693726

727+
// Toggling session purposes
728+
let sessionPurposeInputs = content.find('.session-purpose-toggles input');
729+
function updateSessionPurposeToggling() {
730+
let checked = [];
731+
sessionPurposeInputs.filter(":checked").each(function () {
732+
checked.push(".purpose-" + this.value);
733+
});
734+
735+
sessions.filter(checked.join(",")).removeClass('hidden-purpose');
736+
sessions.not(checked.join(",")).addClass('hidden-purpose');
737+
}
738+
739+
sessionPurposeInputs.on("click", updateSessionPurposeToggling);
740+
updateSessionPurposeToggling();
741+
694742
// toggling visible timeslots
695743
let timeslotGroupInputs = content.find("#timeslot-group-toggles-modal .modal-body input");
696744
function updateTimeslotGroupToggling() {

ietf/templates/meeting/edit_meeting_schedule.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
.edit-meeting-schedule .edit-grid .timeslot.past-hint { filter: brightness(0.9); }
1717
.edit-meeting-schedule .past-flag { visibility: hidden; font-size: smaller; }
1818
.edit-meeting-schedule .edit-grid .timeslot.past .past-flag { visibility: visible; color: #aaaaaa; }
19+
{# type and purpose styling #}
20+
.edit-meeting-schedule .edit-grid .timeslot.wrong-timeslot-type { background-color: transparent; ); }
21+
.edit-meeting-schedule .edit-grid .timeslot.wrong-timeslot-type .time-label { color: transparent; ); }
22+
.edit-meeting-schedule .session.hidden-purpose { filter: blur(3px); }
1923
{% endblock morecss %}
2024

2125
{% block title %}{{ schedule.name }}: IETF {{ meeting.number }} meeting agenda{% endblock %}
@@ -133,6 +137,7 @@
133137
data-end="{{ t.utc_end_time.isoformat }}"
134138
data-duration="{{ t.duration.total_seconds }}"
135139
data-scheduledatlabel="{{ t.time|date:"l G:i" }}-{{ t.end_time|date:"G:i" }}"
140+
data-type="{{ t.type.slug }}"
136141
style="width: {{ t.layout_width }}rem;">
137142
<div class="time-label">
138143
<div class="past-flag">&nbsp;{# blank div keeps time centered vertically #}</div>
@@ -184,6 +189,18 @@
184189
{% endfor %}
185190
</span>
186191

192+
<span class="session-purpose-toggles">
193+
{% for purpose in session_purposes %}
194+
<label class="purpose-{{ purpose.slug }}"><input type="checkbox" checked value="{{ purpose.slug }}"> {{ purpose }}</label>
195+
{% endfor %}
196+
</span>
197+
198+
<span class="timeslot-type-toggles">
199+
{% for purpose in session_purposes %}
200+
<label class="purpose-{{ purpose.slug }}"><input type="checkbox" checked value="{{ purpose.slug }}"> {{ purpose }}</label>
201+
{% endfor %}
202+
</span>
203+
187204
<span class="timeslot-group-toggles">
188205
<button class="btn btn-default" data-toggle="modal" data-target="#timeslot-group-toggles-modal"><input type="checkbox" checked="checked" disabled> Timeslots</button>
189206
</span>

ietf/templates/meeting/edit_meeting_schedule_session.html

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
<div id="session{{ session.pk }}" class="session {% if not session.group.parent.scheduling_color %}untoggleable{% endif %} {% if session.parent_acronym %}parent-{{ session.parent_acronym }}{% endif %} {% if session.readonly %}readonly{% endif %}" style="width:{{ session.layout_width }}em;" data-duration="{{ session.requested_duration.total_seconds }}" {% if session.attendees != None %}data-attendees="{{ session.attendees }}"{% endif %}>
1+
<div id="session{{ session.pk }}"
2+
class="session {% if not session.group.parent.scheduling_color %}untoggleable-by-parent{% endif %} {% if session.parent_acronym %}parent-{{ session.parent_acronym }} {% endif %}{% if session.purpose %}purpose-{{ session.purpose.slug }} {% else %} purpose-session {% endif %}{% if session.readonly %}readonly {% endif %}"
3+
style="width:{{ session.layout_width }}em;"
4+
data-duration="{{ session.requested_duration.total_seconds }}" {% if session.attendees != None %}
5+
data-attendees="{{ session.attendees }}"{% endif %}
6+
data-type="{{ session.type.slug }}">
27
<div class="session-label {% if session.group and session.group.is_bof %}bof-session{% endif %}">
38
{{ session.scheduling_label }}
49
{% if session.group and session.group.is_bof %}<span class="bof-tag">BOF</span>{% endif %}
@@ -30,14 +35,9 @@
3035
<div class="title">
3136
<strong>
3237
<span class="time pull-right"></span>
33-
{{ session.scheduling_label }}
34-
&middot; {{ session.requested_duration_in_hours }}h
35-
{% if session.group %}
36-
&middot; {% if session.group.is_bof %}BOF{% else %}{{ session.group.type.name }}{% endif %}
37-
{% endif %}
38-
{% if session.attendees != None %}
39-
&middot; {{ session.attendees }} <i class="fa fa-user-o"></i>
40-
{% endif %}
38+
{{ session.scheduling_label }} &middot; {{ session.requested_duration_in_hours }}h
39+
{% if session.purpose_label %} &middot; {{ session.purpose_label }} {% endif %}
40+
{% if session.attendees != None %} &middot; {{ session.attendees }} <i class="fa fa-user-o"></i> {% endif %}
4141
</strong>
4242
</div>
4343

0 commit comments

Comments
 (0)