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
This commit is contained in:
parent
5318081608
commit
173e438aee
|
@ -8,6 +8,7 @@ from django.db import migrations
|
|||
default_purposes = dict(
|
||||
dir=['presentation', 'social', 'tutorial'],
|
||||
ietf=['admin', 'presentation', 'social'],
|
||||
nomcom=['closed', 'officehours'],
|
||||
rg=['session'],
|
||||
team=['coding', 'presentation', 'social', 'tutorial'],
|
||||
wg=['session'],
|
||||
|
|
|
@ -1146,6 +1146,11 @@ class SessionQuerySet(models.QuerySet):
|
|||
type__slug='regular'
|
||||
)
|
||||
|
||||
def requests(self):
|
||||
"""Queryset containing sessions that may be handled as requests"""
|
||||
return self.exclude(
|
||||
type__in=('offagenda', 'reserved', 'unavail')
|
||||
)
|
||||
|
||||
class Session(models.Model):
|
||||
"""Session records that a group should have a session on the
|
||||
|
|
|
@ -500,6 +500,9 @@ def new_meeting_schedule(request, num, owner=None, name=None):
|
|||
|
||||
@ensure_csrf_cookie
|
||||
def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
||||
# Need to coordinate this list with types of session requests
|
||||
# that can be created (see, e.g., SessionQuerySet.requests())
|
||||
IGNORE_TIMESLOT_TYPES = ('offagenda', 'reserved', 'unavail')
|
||||
meeting = get_meeting(num)
|
||||
if name is None:
|
||||
schedule = meeting.schedule
|
||||
|
@ -544,7 +547,8 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
sessions = add_event_info_to_session_qs(
|
||||
Session.objects.filter(
|
||||
meeting=meeting,
|
||||
# type='regular',
|
||||
).exclude(
|
||||
type__in=IGNORE_TIMESLOT_TYPES,
|
||||
).order_by('pk'),
|
||||
requested_time=True,
|
||||
requested_by=True,
|
||||
|
@ -552,12 +556,13 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
Q(current_status__in=['appr', 'schedw', 'scheda', 'sched'])
|
||||
| Q(current_status__in=tombstone_states, pk__in={a.session_id for a in assignments})
|
||||
).prefetch_related(
|
||||
'resources', 'group', 'group__parent', 'group__type', 'joint_with_groups',
|
||||
'resources', 'group', 'group__parent', 'group__type', 'joint_with_groups', 'purpose',
|
||||
)
|
||||
|
||||
timeslots_qs = TimeSlot.objects.filter(
|
||||
meeting=meeting,
|
||||
# type='regular',
|
||||
).exclude(
|
||||
type__in=IGNORE_TIMESLOT_TYPES,
|
||||
).prefetch_related('type').order_by('location', 'time', 'name')
|
||||
|
||||
min_duration = min(t.duration for t in timeslots_qs)
|
||||
|
@ -591,9 +596,13 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
s.requested_by_person = requested_by_lookup.get(s.requested_by)
|
||||
|
||||
s.scheduling_label = "???"
|
||||
if (s.purpose is None or s.purpose.slug == 'regular') and s.group:
|
||||
s.purpose_label = None
|
||||
if (s.purpose is None or s.purpose.slug == 'session') and s.group:
|
||||
s.scheduling_label = s.group.acronym
|
||||
elif s.name:
|
||||
s.purpose_label = 'BoF' if s.group.is_bof() else s.group.type.name
|
||||
else:
|
||||
s.purpose_label = s.purpose.name
|
||||
if s.name:
|
||||
s.scheduling_label = s.name
|
||||
|
||||
s.requested_duration_in_hours = round(s.requested_duration.seconds / 60.0 / 60.0, 1)
|
||||
|
@ -981,6 +990,8 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
p.scheduling_color = "rgb({}, {}, {})".format(*tuple(int(round(x * 255)) for x in rgb_color))
|
||||
p.light_scheduling_color = "rgb({}, {}, {})".format(*tuple(int(round((0.9 + 0.1 * x) * 255)) for x in rgb_color))
|
||||
|
||||
session_purposes = sorted(set(s.purpose for s in sessions if s.purpose), key=lambda p: p.name)
|
||||
|
||||
return render(request, "meeting/edit_meeting_schedule.html", {
|
||||
'meeting': meeting,
|
||||
'schedule': schedule,
|
||||
|
@ -991,6 +1002,7 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
|
|||
'timeslot_groups': sorted((d, list(sorted(t_groups))) for d, t_groups in timeslot_groups.items()),
|
||||
'unassigned_sessions': unassigned_sessions,
|
||||
'session_parents': session_parents,
|
||||
'session_purposes': session_purposes,
|
||||
'hide_menu': True,
|
||||
'lock_time': lock_time,
|
||||
})
|
||||
|
@ -2261,14 +2273,10 @@ def agenda_json(request, num=None):
|
|||
|
||||
def meeting_requests(request, num=None):
|
||||
meeting = get_meeting(num)
|
||||
sessions = add_event_info_to_session_qs(
|
||||
Session.objects.filter(
|
||||
sessions = Session.objects.requests().filter(
|
||||
meeting__number=meeting.number,
|
||||
# type__slug='regular',
|
||||
group__parent__isnull=False
|
||||
),
|
||||
requested_by=True,
|
||||
).exclude(
|
||||
).with_current_status().with_requested_by().exclude(
|
||||
requested_by=0
|
||||
).order_by(
|
||||
"group__parent__acronym", "current_status", "group__acronym"
|
||||
|
|
|
@ -16,7 +16,8 @@ def forward(apps, schema_editor):
|
|||
('coding', 'Coding', 'Coding session', ['other']),
|
||||
('admin', 'Administrative', 'Meeting administration', ['other', 'reg']),
|
||||
('social', 'Social', 'Social event or activity', ['other']),
|
||||
('presentation', 'Presentation', 'Presentation session', ['other', 'regular'])
|
||||
('presentation', 'Presentation', 'Presentation session', ['other', 'regular']),
|
||||
('closed', 'Closed meeting', 'Closed meeting', ['other',]),
|
||||
)):
|
||||
# verify that we're not about to use an invalid purpose
|
||||
for ts_type in tstypes:
|
||||
|
|
|
@ -82,6 +82,7 @@ jQuery(document).ready(function () {
|
|||
jQuery(element).addClass("selected");
|
||||
|
||||
showConstraintHints(element);
|
||||
showTimeSlotTypeIndicators(element.dataset.type);
|
||||
|
||||
let sessionInfoContainer = content.find(".scheduling-panel .session-info-container");
|
||||
sessionInfoContainer.html(jQuery(element).find(".session-info").html());
|
||||
|
@ -105,6 +106,7 @@ jQuery(document).ready(function () {
|
|||
else {
|
||||
sessions.removeClass("selected");
|
||||
showConstraintHints();
|
||||
resetTimeSlotTypeIndicators();
|
||||
content.find(".scheduling-panel .session-info-container").html("");
|
||||
}
|
||||
}
|
||||
|
@ -203,6 +205,23 @@ jQuery(document).ready(function () {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove timeslot classes indicating timeslot type disagreement
|
||||
*/
|
||||
function resetTimeSlotTypeIndicators() {
|
||||
timeslots.removeClass('wrong-timeslot-type');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add timeslot classes indicating timeslot type disagreement
|
||||
*
|
||||
* @param timeslot_type
|
||||
*/
|
||||
function showTimeSlotTypeIndicators(timeslot_type) {
|
||||
timeslots.removeClass('wrong-timeslot-type');
|
||||
timeslots.filter('[data-type!="' + timeslot_type + '"]').addClass('wrong-timeslot-type');
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this timeslot be treated as a future timeslot?
|
||||
*
|
||||
|
@ -277,19 +296,42 @@ jQuery(document).ready(function () {
|
|||
return Boolean(event.originalEvent.dataTransfer.getData(dnd_mime_type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the session element being dragged
|
||||
*
|
||||
* @param event drag-related event
|
||||
*/
|
||||
function getDraggedSession(event) {
|
||||
if (!isSessionDragEvent(event)) {
|
||||
return null;
|
||||
}
|
||||
const sessionId = event.originalEvent.dataTransfer.getData(dnd_mime_type);
|
||||
const sessionElements = sessions.filter("#" + sessionId);
|
||||
if (sessionElements.length > 0) {
|
||||
return sessionElements[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a session be dropped in this element?
|
||||
*
|
||||
* Drop is allowed in drop-zones that are in unassigned-session or timeslot containers
|
||||
* not marked as 'past'.
|
||||
*/
|
||||
function sessionDropAllowed(elt) {
|
||||
if (!officialSchedule) {
|
||||
return true;
|
||||
function sessionDropAllowed(dropElement, sessionElement) {
|
||||
const relevant_parent = dropElement.closest('.timeslot, .unassigned-sessions');
|
||||
if (!relevant_parent || !sessionElement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const relevant_parent = elt.closest('.timeslot, .unassigned-sessions');
|
||||
return relevant_parent && !(relevant_parent.classList.contains('past'));
|
||||
if (officialSchedule && relevant_parent.classList.contains('past')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !relevant_parent.dataset.type || (
|
||||
relevant_parent.dataset.type === sessionElement.dataset.type
|
||||
);
|
||||
}
|
||||
|
||||
if (!content.find(".edit-grid").hasClass("read-only")) {
|
||||
|
@ -314,7 +356,7 @@ jQuery(document).ready(function () {
|
|||
// dropping
|
||||
let dropElements = content.find(".timeslot .drop-target,.unassigned-sessions .drop-target");
|
||||
dropElements.on('dragenter', function (event) {
|
||||
if (sessionDropAllowed(this)) {
|
||||
if (sessionDropAllowed(this, getDraggedSession(event))) {
|
||||
event.preventDefault(); // default action is signalling that this is not a valid target
|
||||
jQuery(this).parent().addClass("dropping");
|
||||
}
|
||||
|
@ -324,7 +366,7 @@ jQuery(document).ready(function () {
|
|||
// we don't actually need this event, except we need to signal
|
||||
// that this is a valid drop target, by cancelling the default
|
||||
// action
|
||||
if (sessionDropAllowed(this)) {
|
||||
if (sessionDropAllowed(this, getDraggedSession(event))) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
@ -332,7 +374,7 @@ jQuery(document).ready(function () {
|
|||
dropElements.on('dragleave', function (event) {
|
||||
// skip dragleave events if they are to children
|
||||
const leaving_child = event.originalEvent.currentTarget.contains(event.originalEvent.relatedTarget);
|
||||
if (!leaving_child && sessionDropAllowed(this)) {
|
||||
if (!leaving_child && sessionDropAllowed(this, getDraggedSession(event))) {
|
||||
jQuery(this).parent().removeClass('dropping');
|
||||
}
|
||||
});
|
||||
|
@ -340,30 +382,21 @@ jQuery(document).ready(function () {
|
|||
dropElements.on('drop', function (event) {
|
||||
let dropElement = jQuery(this);
|
||||
|
||||
if (!isSessionDragEvent(event)) {
|
||||
// event is result of something other than a session drag
|
||||
const sessionElement = getDraggedSession(event);
|
||||
if (!sessionElement) {
|
||||
// not drag event or not from a session we recognize
|
||||
dropElement.parent().removeClass("dropping");
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionId = event.originalEvent.dataTransfer.getData(dnd_mime_type);
|
||||
let sessionElement = sessions.filter("#" + sessionId);
|
||||
if (sessionElement.length === 0) {
|
||||
// drag event is not from a session we recognize
|
||||
dropElement.parent().removeClass("dropping");
|
||||
return;
|
||||
}
|
||||
|
||||
// We now know this is a drop of a recognized session
|
||||
|
||||
if (!sessionDropAllowed(this)) {
|
||||
if (!sessionDropAllowed(this, sessionElement)) {
|
||||
dropElement.parent().removeClass("dropping"); // just in case
|
||||
return; // drop not allowed
|
||||
}
|
||||
|
||||
event.preventDefault(); // prevent opening as link
|
||||
|
||||
let dragParent = sessionElement.parent();
|
||||
let dragParent = jQuery(sessionElement).parent();
|
||||
if (dragParent.is(this)) {
|
||||
dropElement.parent().removeClass("dropping");
|
||||
return;
|
||||
|
@ -400,7 +433,7 @@ jQuery(document).ready(function () {
|
|||
timeout: 5 * 1000,
|
||||
data: {
|
||||
action: "unassign",
|
||||
session: sessionId.slice("session".length)
|
||||
session: sessionElement.id.slice("session".length)
|
||||
}
|
||||
}).fail(failHandler).done(done);
|
||||
}
|
||||
|
@ -410,7 +443,7 @@ jQuery(document).ready(function () {
|
|||
method: "post",
|
||||
data: {
|
||||
action: "assign",
|
||||
session: sessionId.slice("session".length),
|
||||
session: sessionElement.id.slice("session".length),
|
||||
timeslot: dropParent.attr("id").slice("timeslot".length)
|
||||
},
|
||||
timeout: 5 * 1000
|
||||
|
@ -673,7 +706,7 @@ jQuery(document).ready(function () {
|
|||
// toggling visible sessions by session parents
|
||||
let sessionParentInputs = content.find(".session-parent-toggles input");
|
||||
|
||||
function setSessionHidden(sess, hide) {
|
||||
function setSessionHiddenParent(sess, hide) {
|
||||
sess.toggleClass('hidden-parent', hide);
|
||||
sess.prop('draggable', !hide);
|
||||
}
|
||||
|
@ -684,13 +717,28 @@ jQuery(document).ready(function () {
|
|||
checked.push(".parent-" + this.value);
|
||||
});
|
||||
|
||||
setSessionHidden(sessions.not(".untoggleable").filter(checked.join(",")), false);
|
||||
setSessionHidden(sessions.not(".untoggleable").not(checked.join(",")), true);
|
||||
setSessionHiddenParent(sessions.not(".untoggleable-by-parent").filter(checked.join(",")), false);
|
||||
setSessionHiddenParent(sessions.not(".untoggleable-by-parent").not(checked.join(",")), true);
|
||||
}
|
||||
|
||||
sessionParentInputs.on("click", updateSessionParentToggling);
|
||||
updateSessionParentToggling();
|
||||
|
||||
// Toggling session purposes
|
||||
let sessionPurposeInputs = content.find('.session-purpose-toggles input');
|
||||
function updateSessionPurposeToggling() {
|
||||
let checked = [];
|
||||
sessionPurposeInputs.filter(":checked").each(function () {
|
||||
checked.push(".purpose-" + this.value);
|
||||
});
|
||||
|
||||
sessions.filter(checked.join(",")).removeClass('hidden-purpose');
|
||||
sessions.not(checked.join(",")).addClass('hidden-purpose');
|
||||
}
|
||||
|
||||
sessionPurposeInputs.on("click", updateSessionPurposeToggling);
|
||||
updateSessionPurposeToggling();
|
||||
|
||||
// toggling visible timeslots
|
||||
let timeslotGroupInputs = content.find("#timeslot-group-toggles-modal .modal-body input");
|
||||
function updateTimeslotGroupToggling() {
|
||||
|
|
|
@ -16,6 +16,10 @@
|
|||
.edit-meeting-schedule .edit-grid .timeslot.past-hint { filter: brightness(0.9); }
|
||||
.edit-meeting-schedule .past-flag { visibility: hidden; font-size: smaller; }
|
||||
.edit-meeting-schedule .edit-grid .timeslot.past .past-flag { visibility: visible; color: #aaaaaa; }
|
||||
{# type and purpose styling #}
|
||||
.edit-meeting-schedule .edit-grid .timeslot.wrong-timeslot-type { background-color: transparent; ); }
|
||||
.edit-meeting-schedule .edit-grid .timeslot.wrong-timeslot-type .time-label { color: transparent; ); }
|
||||
.edit-meeting-schedule .session.hidden-purpose { filter: blur(3px); }
|
||||
{% endblock morecss %}
|
||||
|
||||
{% block title %}{{ schedule.name }}: IETF {{ meeting.number }} meeting agenda{% endblock %}
|
||||
|
@ -133,6 +137,7 @@
|
|||
data-end="{{ t.utc_end_time.isoformat }}"
|
||||
data-duration="{{ t.duration.total_seconds }}"
|
||||
data-scheduledatlabel="{{ t.time|date:"l G:i" }}-{{ t.end_time|date:"G:i" }}"
|
||||
data-type="{{ t.type.slug }}"
|
||||
style="width: {{ t.layout_width }}rem;">
|
||||
<div class="time-label">
|
||||
<div class="past-flag"> {# blank div keeps time centered vertically #}</div>
|
||||
|
@ -184,6 +189,18 @@
|
|||
{% endfor %}
|
||||
</span>
|
||||
|
||||
<span class="session-purpose-toggles">
|
||||
{% for purpose in session_purposes %}
|
||||
<label class="purpose-{{ purpose.slug }}"><input type="checkbox" checked value="{{ purpose.slug }}"> {{ purpose }}</label>
|
||||
{% endfor %}
|
||||
</span>
|
||||
|
||||
<span class="timeslot-type-toggles">
|
||||
{% for purpose in session_purposes %}
|
||||
<label class="purpose-{{ purpose.slug }}"><input type="checkbox" checked value="{{ purpose.slug }}"> {{ purpose }}</label>
|
||||
{% endfor %}
|
||||
</span>
|
||||
|
||||
<span class="timeslot-group-toggles">
|
||||
<button class="btn btn-default" data-toggle="modal" data-target="#timeslot-group-toggles-modal"><input type="checkbox" checked="checked" disabled> Timeslots</button>
|
||||
</span>
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
<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 %}>
|
||||
<div id="session{{ session.pk }}"
|
||||
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 %}"
|
||||
style="width:{{ session.layout_width }}em;"
|
||||
data-duration="{{ session.requested_duration.total_seconds }}" {% if session.attendees != None %}
|
||||
data-attendees="{{ session.attendees }}"{% endif %}
|
||||
data-type="{{ session.type.slug }}">
|
||||
<div class="session-label {% if session.group and session.group.is_bof %}bof-session{% endif %}">
|
||||
{{ session.scheduling_label }}
|
||||
{% if session.group and session.group.is_bof %}<span class="bof-tag">BOF</span>{% endif %}
|
||||
|
@ -30,14 +35,9 @@
|
|||
<div class="title">
|
||||
<strong>
|
||||
<span class="time pull-right"></span>
|
||||
{{ session.scheduling_label }}
|
||||
· {{ session.requested_duration_in_hours }}h
|
||||
{% if session.group %}
|
||||
· {% if session.group.is_bof %}BOF{% else %}{{ session.group.type.name }}{% endif %}
|
||||
{% endif %}
|
||||
{% if session.attendees != None %}
|
||||
· {{ session.attendees }} <i class="fa fa-user-o"></i>
|
||||
{% endif %}
|
||||
{{ session.scheduling_label }} · {{ session.requested_duration_in_hours }}h
|
||||
{% if session.purpose_label %} · {{ session.purpose_label }} {% endif %}
|
||||
{% if session.attendees != None %} · {{ session.attendees }} <i class="fa fa-user-o"></i> {% endif %}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
|
|
Loading…
Reference in a new issue