Merged /branch/iola/meeting-improvement-r16992@17152 from olau@iola.dk:
* Renamed Meeting.agenda to Meeting.schedule together with a bunch of related internal things * Moved Session.status, .requested, and .requested_by to a new SchedulingEvent. * Turned sessions into regular sessions and non-sessions into misc. sessions in the UI and code to avoid ambiguity. This doesn't change the data in the DB except for uses of TimeSlotTypeName where 'session' is now 'regular'. - Legacy-Id: 17153
This commit is contained in:
commit
06fe583351
|
@ -69,7 +69,7 @@ class PersonalInformationExportView(DetailView, JsonExportMixin):
|
|||
person = get_object_or_404(self.model, user=request.user)
|
||||
expand = ['searchrule', 'documentauthor', 'ad_document_set', 'ad_dochistory_set', 'docevent',
|
||||
'ballotpositiondocevent', 'deletedevent', 'email_set', 'groupevent', 'role', 'rolehistory', 'iprdisclosurebase',
|
||||
'iprevent', 'liaisonstatementevent', 'whitelisted', 'schedule', 'constraint', 'session', 'message',
|
||||
'iprevent', 'liaisonstatementevent', 'whitelisted', 'schedule', 'constraint', 'schedulingevent', 'message',
|
||||
'sendqueue', 'nominee', 'topicfeedbacklastseen', 'alias', 'email', 'apikeys', 'personevent',
|
||||
'reviewersettings', 'reviewsecretarysettings', 'unavailableperiod', 'reviewwish',
|
||||
'nextreviewerinteam', 'reviewrequest', 'meetingregistration', 'submissionevent', 'preapproval',
|
||||
|
|
|
@ -39,7 +39,7 @@ from ietf.doc.utils import create_ballot_if_not_open
|
|||
from ietf.group.models import Group
|
||||
from ietf.group.factories import GroupFactory, RoleFactory
|
||||
from ietf.ipr.factories import HolderIprDisclosureFactory
|
||||
from ietf.meeting.models import Meeting, Session, SessionPresentation
|
||||
from ietf.meeting.models import Meeting, Session, SessionPresentation, SchedulingEvent
|
||||
from ietf.meeting.factories import MeetingFactory, SessionFactory
|
||||
from ietf.name.models import SessionStatusName, BallotPositionName
|
||||
from ietf.person.models import Person
|
||||
|
@ -712,11 +712,14 @@ class DocTestCase(TestCase):
|
|||
name = "session-72-mars-1",
|
||||
meeting = Meeting.objects.get(number='72'),
|
||||
group = Group.objects.get(acronym='mars'),
|
||||
status = SessionStatusName.objects.create(slug='scheduled', name='Scheduled'),
|
||||
modified = datetime.datetime.now(),
|
||||
requested_by = Person.objects.get(user__username="marschairman"),
|
||||
type_id = "session",
|
||||
)
|
||||
type_id = 'regular',
|
||||
)
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.create(slug='scheduled'),
|
||||
by = Person.objects.get(user__username="marschairman"),
|
||||
)
|
||||
SessionPresentation.objects.create(session=session, document=doc, rev=doc.rev)
|
||||
|
||||
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=doc.name)))
|
||||
|
|
|
@ -20,7 +20,7 @@ from ietf.doc.models import Document, State, DocAlias, NewRevisionDocEvent
|
|||
from ietf.group.factories import RoleFactory
|
||||
from ietf.group.models import Group
|
||||
from ietf.meeting.factories import MeetingFactory
|
||||
from ietf.meeting.models import Meeting, Session, SessionPresentation
|
||||
from ietf.meeting.models import Meeting, Session, SessionPresentation, SchedulingEvent
|
||||
from ietf.name.models import SessionStatusName
|
||||
from ietf.person.models import Person
|
||||
from ietf.utils.test_utils import TestCase, login_testing_unauthorized
|
||||
|
@ -158,11 +158,14 @@ class GroupMaterialTests(TestCase):
|
|||
name = "session-42-mars-1",
|
||||
meeting = Meeting.objects.get(number='42'),
|
||||
group = Group.objects.get(acronym='mars'),
|
||||
status = SessionStatusName.objects.create(slug='scheduled', name='Scheduled'),
|
||||
modified = datetime.datetime.now(),
|
||||
requested_by = Person.objects.get(user__username="marschairman"),
|
||||
type_id="session",
|
||||
)
|
||||
type_id='regular',
|
||||
)
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.create(slug='scheduled'),
|
||||
by = Person.objects.get(user__username="marschairman"),
|
||||
)
|
||||
SessionPresentation.objects.create(session=session, document=doc, rev=doc.rev)
|
||||
|
||||
url = urlreverse('ietf.doc.views_material.edit_material', kwargs=dict(name=doc.name, action="revise"))
|
||||
|
|
|
@ -74,7 +74,7 @@ from ietf.doc.forms import TelechatForm, NotifyForm
|
|||
from ietf.doc.mails import email_comment
|
||||
from ietf.mailtrigger.utils import gather_relevant_expansions
|
||||
from ietf.meeting.models import Session
|
||||
from ietf.meeting.utils import group_sessions, get_upcoming_manageable_sessions, sort_sessions
|
||||
from ietf.meeting.utils import group_sessions, get_upcoming_manageable_sessions, sort_sessions, add_event_info_to_session_qs
|
||||
from ietf.review.models import ReviewAssignment
|
||||
from ietf.review.utils import can_request_review_of_doc, review_assignments_to_list_for_docs
|
||||
from ietf.review.utils import no_review_from_teams_on_doc
|
||||
|
@ -1353,9 +1353,9 @@ def add_sessionpresentation(request,name):
|
|||
def all_presentations(request, name):
|
||||
doc = get_object_or_404(Document, name=name)
|
||||
|
||||
|
||||
sessions = doc.session_set.filter(status__in=['sched','schedw','appr','canceled'],
|
||||
type__in=['session','plenary','other'])
|
||||
sessions = add_event_info_to_session_qs(
|
||||
doc.session_set.filter(type__in=['regular','plenary','other'])
|
||||
).filter(current_status__in=['sched','schedw','appr','canceled'])
|
||||
|
||||
future, in_progress, recent, past = group_sessions(sessions)
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ class GroupInfo(models.Model):
|
|||
return list(set([ role for role in self.parent.role_set.filter(name__in=['ad', 'chair']) ]))
|
||||
|
||||
def is_bof(self):
|
||||
return (self.state.slug in ["bof", "bof-conc"])
|
||||
return self.state_id in ["bof", "bof-conc"]
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
|
|
@ -88,7 +88,7 @@ from ietf.group.utils import (get_charter_text, can_manage_group_type,
|
|||
from ietf.ietfauth.utils import has_role, is_authorized_in_group
|
||||
from ietf.mailtrigger.utils import gather_relevant_expansions
|
||||
from ietf.meeting.helpers import get_meeting
|
||||
from ietf.meeting.utils import group_sessions
|
||||
from ietf.meeting.utils import group_sessions, add_event_info_to_session_qs
|
||||
from ietf.name.models import GroupTypeName, StreamName
|
||||
from ietf.person.models import Email
|
||||
from ietf.review.models import (ReviewRequest, ReviewAssignment, ReviewerSettings,
|
||||
|
@ -750,9 +750,14 @@ def meetings(request, acronym=None, group_type=None):
|
|||
|
||||
four_years_ago = datetime.datetime.now()-datetime.timedelta(days=4*365)
|
||||
|
||||
sessions = group.session_set.filter(status__in=['sched','schedw','appr','canceled'],
|
||||
meeting__date__gt=four_years_ago,
|
||||
type__in=['session','plenary','other'])
|
||||
sessions = add_event_info_to_session_qs(
|
||||
group.session_set.filter(
|
||||
meeting__date__gt=four_years_ago,
|
||||
type__in=['regular','plenary','other']
|
||||
)
|
||||
).filter(
|
||||
current_status__in=['sched','schedw','appr','canceled'],
|
||||
)
|
||||
|
||||
future, in_progress, recent, past = group_sessions(sessions)
|
||||
|
||||
|
|
|
@ -356,7 +356,10 @@ class Recipient(models.Model):
|
|||
addrs=[]
|
||||
if 'session' in kwargs:
|
||||
session = kwargs['session']
|
||||
addrs.append(session.requested_by.role_email('chair').address)
|
||||
from ietf.meeting.models import SchedulingEvent
|
||||
first_event = SchedulingEvent.objects.filter(session=session).select_related('by').order_by('time', 'id').first()
|
||||
if first_event and first_event.status_id in ['appw', 'schedw']:
|
||||
addrs.append(first_event.by.role_email('chair').address)
|
||||
return addrs
|
||||
|
||||
def gather_review_team_ads(self, **kwargs):
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.contrib import admin
|
|||
|
||||
from ietf.meeting.models import (Meeting, Room, Session, TimeSlot, Constraint, Schedule,
|
||||
SchedTimeSessAssignment, ResourceAssociation, FloorPlan, UrlResource,
|
||||
SessionPresentation, ImportantDate, SlideSubmission, )
|
||||
SessionPresentation, ImportantDate, SlideSubmission, SchedulingEvent)
|
||||
|
||||
|
||||
class UrlResourceAdmin(admin.ModelAdmin):
|
||||
|
@ -80,13 +80,37 @@ class ConstraintAdmin(admin.ModelAdmin):
|
|||
|
||||
admin.site.register(Constraint, ConstraintAdmin)
|
||||
|
||||
class SessionAdmin(admin.ModelAdmin):
|
||||
list_display = ["meeting", "name", "group", "attendees", "requested", "status"]
|
||||
list_filter = ["meeting", ]
|
||||
raw_id_fields = ["meeting", "group", "requested_by", "materials"]
|
||||
search_fields = ["meeting__number", "name", "group__name", "group__acronym", ]
|
||||
ordering = ["-requested"]
|
||||
class SchedulingEventInline(admin.TabularInline):
|
||||
model = SchedulingEvent
|
||||
raw_id_fields = ["by"]
|
||||
|
||||
class SessionAdmin(admin.ModelAdmin):
|
||||
list_display = ["meeting", "name", "group", "attendees", "requested", "current_status"]
|
||||
list_filter = ["meeting", ]
|
||||
raw_id_fields = ["meeting", "group", "materials"]
|
||||
search_fields = ["meeting__number", "name", "group__name", "group__acronym", ]
|
||||
ordering = ["-id"]
|
||||
inlines = [SchedulingEventInline]
|
||||
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super(SessionAdmin, self).get_queryset(request)
|
||||
return qs.prefetch_related('schedulingevent_set')
|
||||
|
||||
def current_status(self, instance):
|
||||
events = sorted(instance.schedulingevent_set.all(), key=lambda e: (e.time, e.id))
|
||||
if events:
|
||||
return events[-1].time
|
||||
else:
|
||||
return None
|
||||
|
||||
def requested(self, instance):
|
||||
events = sorted(instance.schedulingevent_set.all(), key=lambda e: (e.time, e.id))
|
||||
if events:
|
||||
return events[0].time
|
||||
else:
|
||||
return None
|
||||
|
||||
def name_lower(self, instance):
|
||||
return instance.name.name.lower()
|
||||
|
||||
|
@ -94,6 +118,13 @@ class SessionAdmin(admin.ModelAdmin):
|
|||
|
||||
admin.site.register(Session, SessionAdmin)
|
||||
|
||||
class SchedulingEventAdmin(admin.ModelAdmin):
|
||||
list_display = ["session", "status", "time", "by"]
|
||||
raw_id_fields = ["session", "by"]
|
||||
ordering = ["-id"]
|
||||
|
||||
admin.site.register(SchedulingEvent, SchedulingEventAdmin)
|
||||
|
||||
class ScheduleAdmin(admin.ModelAdmin):
|
||||
list_display = ["name", "meeting", "owner", "visible", "public", "badness"]
|
||||
list_filter = ["meeting", ]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# Copyright The IETF Trust 2013-2019, All Rights Reserved
|
||||
import json
|
||||
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
|
@ -7,9 +8,11 @@ from django.http import Http404
|
|||
from django.views.decorators.http import require_POST
|
||||
|
||||
from ietf.ietfauth.utils import role_required, has_role
|
||||
from ietf.meeting.helpers import get_meeting, get_schedule, agenda_permissions, get_person_by_email, get_schedule_by_name
|
||||
from ietf.meeting.helpers import get_meeting, get_schedule, schedule_permissions, get_person_by_email, get_schedule_by_name
|
||||
from ietf.meeting.models import TimeSlot, Session, Schedule, Room, Constraint, SchedTimeSessAssignment, ResourceAssociation
|
||||
from ietf.meeting.views import edit_timeslots, edit_agenda
|
||||
from ietf.meeting.views import edit_timeslots, edit_schedule
|
||||
from ietf.meeting.utils import only_sessions_that_can_meet
|
||||
from ietf.meeting.utils import add_event_info_to_session_qs
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
|
@ -43,8 +46,8 @@ def get_meeting_schedule(num, owner, name):
|
|||
|
||||
|
||||
|
||||
# should asking if an agenda is read-only require any kind of permission?
|
||||
def agenda_permission_api(request, num, owner, name):
|
||||
# should asking if an schedule is read-only require any kind of permission?
|
||||
def schedule_permission_api(request, num, owner, name):
|
||||
meeting = get_meeting(num)
|
||||
person = get_person_by_email(owner)
|
||||
schedule = get_schedule_by_name(meeting, person, name)
|
||||
|
@ -56,7 +59,7 @@ def agenda_permission_api(request, num, owner, name):
|
|||
owner_href = ""
|
||||
|
||||
if schedule is not None:
|
||||
cansee,canedit,secretariat = agenda_permissions(meeting, schedule, request.user)
|
||||
cansee,canedit,secretariat = schedule_permissions(meeting, schedule, request.user)
|
||||
owner_href = request.build_absolute_uri(schedule.owner.json_url())
|
||||
|
||||
if has_role(request.user, "Area Director") or secretariat:
|
||||
|
@ -158,8 +161,8 @@ AddSlotForm = modelform_factory(TimeSlot, exclude=('meeting','name','location','
|
|||
# no authorization required to list.
|
||||
def timeslot_slotlist(request, mtg):
|
||||
slots = mtg.timeslot_set.all()
|
||||
# Restrict graphical editing to slots of type 'session' for now
|
||||
slots = slots.filter(type__slug='session')
|
||||
# Restrict graphical editing to slots of type 'regular' for now
|
||||
slots = slots.filter(type__slug='regular')
|
||||
json_array=[]
|
||||
for slot in slots:
|
||||
json_array.append(slot.json_dict(request.build_absolute_uri('/')))
|
||||
|
@ -206,14 +209,14 @@ def timeslot_updslot(request, meeting, slotid):
|
|||
slot.save()
|
||||
|
||||
# WORKAROUND: Right now, if there are sessions scheduled in this timeslot
|
||||
# when it is marked unavailable (or any other value besides "session") they
|
||||
# when it is marked unavailable (or any other value besides 'regular') they
|
||||
# become unreachable from the editing screen. The session is listed in the
|
||||
# "unscheduled" block incorrectly, and drag-dropping it onto the a new
|
||||
# timeslot produces erroneous results. To avoid this, we will silently
|
||||
# unschedule any sessions in the timeslot that has just been made
|
||||
# unavailable.
|
||||
|
||||
if slot.type_id != 'session':
|
||||
if slot.type_id != 'regular':
|
||||
slot.sessionassignments.all().delete()
|
||||
|
||||
# ENDWORKAROUND
|
||||
|
@ -256,39 +259,39 @@ def timeslot_sloturl(request, num=None, slotid=None):
|
|||
return timeslot_delslot(request, meeting, slotid)
|
||||
|
||||
#############################################################################
|
||||
## Agenda List API
|
||||
## Schedule List API
|
||||
#############################################################################
|
||||
AgendaEntryForm = modelform_factory(Schedule, exclude=('meeting','owner'))
|
||||
EditAgendaEntryForm = modelform_factory(Schedule, exclude=('meeting','owner', 'name'))
|
||||
ScheduleEntryForm = modelform_factory(Schedule, exclude=('meeting','owner'))
|
||||
EditScheduleEntryForm = modelform_factory(Schedule, exclude=('meeting','owner', 'name'))
|
||||
|
||||
@role_required('Area Director','Secretariat')
|
||||
def agenda_list(request, mtg):
|
||||
agendas = mtg.schedule_set.all()
|
||||
def schedule_list(request, mtg):
|
||||
schedules = mtg.schedule_set.all()
|
||||
json_array=[]
|
||||
for agenda in agendas:
|
||||
json_array.append(agenda.json_dict(request.build_absolute_uri('/')))
|
||||
for schedule in schedules:
|
||||
json_array.append(schedule.json_dict(request.build_absolute_uri('/')))
|
||||
return HttpResponse(json.dumps(json_array),
|
||||
content_type="application/json")
|
||||
|
||||
# duplicates save-as functionality below.
|
||||
@role_required('Area Director','Secretariat')
|
||||
def agenda_add(request, meeting):
|
||||
newagendaform = AgendaEntryForm(request.POST)
|
||||
if not newagendaform.is_valid():
|
||||
def schedule_add(request, meeting):
|
||||
newscheduleform = ScheduleEntryForm(request.POST)
|
||||
if not newscheduleform.is_valid():
|
||||
return HttpResponse(status=404)
|
||||
|
||||
newagenda = newagendaform.save(commit=False)
|
||||
newagenda.meeting = meeting
|
||||
newagenda.owner = request.user.person
|
||||
newagenda.save()
|
||||
newschedule = newscheduleform.save(commit=False)
|
||||
newschedule.meeting = meeting
|
||||
newschedule.owner = request.user.person
|
||||
newschedule.save()
|
||||
|
||||
if "HTTP_ACCEPT" in request.META and "application/json" in request.META['HTTP_ACCEPT']:
|
||||
return redirect(agenda_infourl, meeting.number, newagenda.owner_email(), newagenda.name)
|
||||
return redirect(schedule_infourl, meeting.number, newschedule.owner_email(), newschedule.name)
|
||||
else:
|
||||
return redirect(edit_agenda, meeting.number, newagenda.owner_email(), newagenda.name)
|
||||
return redirect(edit_schedule, meeting.number, newschedule.owner_email(), newschedule.name)
|
||||
|
||||
@require_POST
|
||||
def agenda_update(request, meeting, schedule):
|
||||
def schedule_update(request, meeting, schedule):
|
||||
# forms are completely useless for update actions that want to
|
||||
# accept a subset of values. (huh? we could use required=False)
|
||||
|
||||
|
@ -297,7 +300,7 @@ def agenda_update(request, meeting, schedule):
|
|||
if not user.is_authenticated:
|
||||
return HttpResponse({'error':'no permission'}, status=403)
|
||||
|
||||
cansee,canedit,secretariat = agenda_permissions(meeting, schedule, request.user)
|
||||
cansee,canedit,secretariat = schedule_permissions(meeting, schedule, request.user)
|
||||
#read_only = not canedit ## not used
|
||||
|
||||
# TODO: Secretariat should always get canedit
|
||||
|
@ -316,53 +319,53 @@ def agenda_update(request, meeting, schedule):
|
|||
schedule.save()
|
||||
|
||||
# enforce that a non-public schedule can not be the public one.
|
||||
if meeting.agenda == schedule and not schedule.public:
|
||||
meeting.agenda = None
|
||||
if meeting.schedule == schedule and not schedule.public:
|
||||
meeting.schedule = None
|
||||
meeting.save()
|
||||
|
||||
if "HTTP_ACCEPT" in request.META and "application/json" in request.META['HTTP_ACCEPT']:
|
||||
return HttpResponse(json.dumps(schedule.json_dict(request.build_absolute_uri('/'))),
|
||||
content_type="application/json")
|
||||
else:
|
||||
return redirect(edit_agenda, meeting.number, schedule.owner_email(), schedule.name)
|
||||
return redirect(edit_schedule, meeting.number, schedule.owner_email(), schedule.name)
|
||||
|
||||
@role_required('Secretariat')
|
||||
def agenda_del(request, meeting, schedule):
|
||||
def schedule_del(request, meeting, schedule):
|
||||
schedule.delete_assignments()
|
||||
#debug.log("deleting meeting: %s agenda: %s" % (meeting, meeting.agenda))
|
||||
if meeting.agenda == schedule:
|
||||
meeting.agenda = None
|
||||
#debug.log("deleting meeting: %s schedule: %s" % (meeting, meeting.schedule))
|
||||
if meeting.schedule == schedule:
|
||||
meeting.schedule = None
|
||||
meeting.save()
|
||||
schedule.delete()
|
||||
return HttpResponse('{"error":"none"}', status = 200)
|
||||
|
||||
def agenda_infosurl(request, num=None):
|
||||
def schedule_infosurl(request, num=None):
|
||||
meeting = get_meeting(num)
|
||||
|
||||
if request.method == 'GET':
|
||||
return agenda_list(request, meeting)
|
||||
return schedule_list(request, meeting)
|
||||
elif request.method == 'POST':
|
||||
return agenda_add(request, meeting)
|
||||
return schedule_add(request, meeting)
|
||||
|
||||
# unacceptable action
|
||||
return HttpResponse(status=406)
|
||||
|
||||
def agenda_infourl(request, num=None, owner=None, name=None):
|
||||
def schedule_infourl(request, num=None, owner=None, name=None):
|
||||
meeting = get_meeting(num)
|
||||
person = get_person_by_email(owner)
|
||||
schedule = get_schedule_by_name(meeting, person, name)
|
||||
if schedule is None:
|
||||
raise Http404("No meeting information for meeting %s schedule %s available" % (num,name))
|
||||
|
||||
#debug.log("results in agenda: %u / %s" % (schedule.id, request.method))
|
||||
#debug.log("results in schedule: %u / %s" % (schedule.id, request.method))
|
||||
|
||||
if request.method == 'GET':
|
||||
return HttpResponse(json.dumps(schedule.json_dict(request.build_absolute_uri('/'))),
|
||||
content_type="application/json")
|
||||
elif request.method == 'POST':
|
||||
return agenda_update(request, meeting, schedule)
|
||||
return schedule_update(request, meeting, schedule)
|
||||
elif request.method == 'DELETE':
|
||||
return agenda_del(request, meeting, schedule)
|
||||
return schedule_del(request, meeting, schedule)
|
||||
else:
|
||||
return HttpResponse(status=406)
|
||||
|
||||
|
@ -377,22 +380,22 @@ def meeting_get(request, meeting):
|
|||
|
||||
@role_required('Secretariat')
|
||||
def meeting_update(request, meeting):
|
||||
# at present, only the official agenda can be updated from this interface.
|
||||
# at present, only the official schedule can be updated from this interface.
|
||||
|
||||
#debug.log("1 meeting.agenda: %s / %s / %s" % (meeting.agenda, update_dict, request.body))
|
||||
if "agenda" in request.POST:
|
||||
value = request.POST["agenda"]
|
||||
#debug.log("4 meeting.agenda: %s" % (value))
|
||||
#debug.log("1 meeting.schedule: %s / %s / %s" % (meeting.schedule, update_dict, request.body))
|
||||
if "schedule" in request.POST:
|
||||
value = request.POST["schedule"]
|
||||
#debug.log("4 meeting.schedule: %s" % (value))
|
||||
if not value or value == "None": # value == "None" is just weird, better with empty string
|
||||
meeting.set_official_agenda(None)
|
||||
meeting.set_official_schedule(None)
|
||||
else:
|
||||
schedule = get_schedule(meeting, value)
|
||||
if not schedule.public:
|
||||
return HttpResponse(status = 406)
|
||||
#debug.log("3 meeting.agenda: %s" % (schedule))
|
||||
meeting.set_official_agenda(schedule)
|
||||
#debug.log("3 meeting.schedule: %s" % (schedule))
|
||||
meeting.set_official_schedule(schedule)
|
||||
|
||||
#debug.log("2 meeting.agenda: %s" % (meeting.agenda))
|
||||
#debug.log("2 meeting.schedule: %s" % (meeting.schedule))
|
||||
meeting.save()
|
||||
return meeting_get(request, meeting)
|
||||
|
||||
|
@ -430,7 +433,11 @@ def session_json(request, num, sessionid):
|
|||
def sessions_json(request, num):
|
||||
meeting = get_meeting(num)
|
||||
|
||||
sessions = meeting.sessions_that_can_meet.all()
|
||||
sessions = add_event_info_to_session_qs(
|
||||
only_sessions_that_can_meet(meeting.session_set),
|
||||
requested_time=True,
|
||||
requested_by=True,
|
||||
)
|
||||
|
||||
sess1_dict = [ x.json_dict(request.build_absolute_uri('/')) for x in sessions ]
|
||||
return HttpResponse(json.dumps(sess1_dict, sort_keys=True, indent=2),
|
||||
|
@ -442,9 +449,9 @@ def sessions_json(request, num):
|
|||
|
||||
# this creates an entirely *NEW* schedtimesessassignment
|
||||
def assignments_post(request, meeting, schedule):
|
||||
cansee,canedit,secretariat = agenda_permissions(meeting, schedule, request.user)
|
||||
cansee,canedit,secretariat = schedule_permissions(meeting, schedule, request.user)
|
||||
if not canedit:
|
||||
return HttpResponse(json.dumps({'error':'no permission to modify this agenda'}),
|
||||
return HttpResponse(json.dumps({'error':'no permission to modify this schedule'}),
|
||||
status = 403,
|
||||
content_type="application/json")
|
||||
|
||||
|
@ -491,7 +498,7 @@ def assignments_get(request, num, schedule):
|
|||
return HttpResponse(json.dumps(sess1_dict, sort_keys=True, indent=2),
|
||||
content_type="application/json")
|
||||
|
||||
# this returns the list of scheduled sessions for the given named agenda
|
||||
# this returns the list of scheduled sessions for the given named schedule
|
||||
def assignments_json(request, num, owner, name):
|
||||
info = get_meeting_schedule(num, owner, name)
|
||||
# The return values from get_meeting_schedule() are silly, in that it
|
||||
|
@ -512,9 +519,9 @@ def assignments_json(request, num, owner, name):
|
|||
|
||||
# accepts both POST and PUT in order to implement Postel Doctrine.
|
||||
def assignment_update(request, meeting, schedule, ss):
|
||||
cansee,canedit,secretariat = agenda_permissions(meeting, schedule, request.user)
|
||||
cansee,canedit,secretariat = schedule_permissions(meeting, schedule, request.user)
|
||||
if not canedit:
|
||||
return HttpResponse(json.dumps({'error':'no permission to update this agenda'}),
|
||||
return HttpResponse(json.dumps({'error':'no permission to update this schedule'}),
|
||||
status = 403,
|
||||
content_type="application/json")
|
||||
|
||||
|
@ -530,9 +537,9 @@ def assignment_update(request, meeting, schedule, ss):
|
|||
content_type="application/json")
|
||||
|
||||
def assignment_delete(request, meeting, schedule, ss):
|
||||
cansee,canedit,secretariat = agenda_permissions(meeting, schedule, request.user)
|
||||
cansee,canedit,secretariat = schedule_permissions(meeting, schedule, request.user)
|
||||
if not canedit:
|
||||
return HttpResponse(json.dumps({'error':'no permission to update this agenda'}),
|
||||
return HttpResponse(json.dumps({'error':'no permission to update this schedule'}),
|
||||
status = 403,
|
||||
content_type="application/json")
|
||||
|
||||
|
@ -552,10 +559,10 @@ def assignment_delete(request, meeting, schedule, ss):
|
|||
content_type="application/json")
|
||||
|
||||
def assignment_get(request, meeting, schedule, ss):
|
||||
cansee,canedit,secretariat = agenda_permissions(meeting, schedule, request.user)
|
||||
cansee,canedit,secretariat = schedule_permissions(meeting, schedule, request.user)
|
||||
|
||||
if not cansee:
|
||||
return HttpResponse(json.dumps({'error':'no permission to see this agenda'}),
|
||||
return HttpResponse(json.dumps({'error':'no permission to see this schedule'}),
|
||||
status = 403,
|
||||
content_type="application/json")
|
||||
|
||||
|
|
|
@ -10,7 +10,8 @@ import datetime
|
|||
|
||||
from django.core.files.base import ContentFile
|
||||
|
||||
from ietf.meeting.models import Meeting, Session, Schedule, TimeSlot, SessionPresentation, FloorPlan, Room, SlideSubmission
|
||||
from ietf.meeting.models import Meeting, Session, SchedulingEvent, Schedule, TimeSlot, SessionPresentation, FloorPlan, Room, SlideSubmission
|
||||
from ietf.name.models import SessionStatusName
|
||||
from ietf.group.factories import GroupFactory
|
||||
from ietf.person.factories import PersonFactory
|
||||
|
||||
|
@ -63,9 +64,9 @@ class MeetingFactory(factory.DjangoModelFactory):
|
|||
|
||||
|
||||
@factory.post_generation
|
||||
def populate_agenda(obj, create, extracted, **kwargs): # pylint: disable=no-self-argument
|
||||
def populate_schedule(obj, create, extracted, **kwargs): # pylint: disable=no-self-argument
|
||||
'''
|
||||
Create a default agenda, unless the factory is called
|
||||
Create a default schedule, unless the factory is called
|
||||
with populate_agenda=False
|
||||
'''
|
||||
if extracted is None:
|
||||
|
@ -73,7 +74,7 @@ class MeetingFactory(factory.DjangoModelFactory):
|
|||
if create and extracted:
|
||||
for x in range(3):
|
||||
TimeSlotFactory(meeting=obj)
|
||||
obj.agenda = ScheduleFactory(meeting=obj)
|
||||
obj.schedule = ScheduleFactory(meeting=obj)
|
||||
obj.save()
|
||||
|
||||
class SessionFactory(factory.DjangoModelFactory):
|
||||
|
@ -81,11 +82,30 @@ class SessionFactory(factory.DjangoModelFactory):
|
|||
model = Session
|
||||
|
||||
meeting = factory.SubFactory(MeetingFactory)
|
||||
type_id='session'
|
||||
type_id='regular'
|
||||
group = factory.SubFactory(GroupFactory)
|
||||
requested_by = factory.SubFactory(PersonFactory)
|
||||
status_id='sched'
|
||||
|
||||
@factory.post_generation
|
||||
def status_id(obj, create, extracted, **kwargs):
|
||||
if create:
|
||||
if not extracted:
|
||||
extracted = 'sched'
|
||||
|
||||
if extracted not in ['apprw', 'schedw']:
|
||||
# requested event
|
||||
SchedulingEvent.objects.create(
|
||||
session=obj,
|
||||
status=SessionStatusName.objects.get(slug='schedw'),
|
||||
by=PersonFactory(),
|
||||
)
|
||||
|
||||
# actual state event
|
||||
SchedulingEvent.objects.create(
|
||||
session=obj,
|
||||
status=SessionStatusName.objects.get(slug=extracted),
|
||||
by=PersonFactory(),
|
||||
)
|
||||
|
||||
@factory.post_generation
|
||||
def add_to_schedule(obj, create, extracted, **kwargs): # pylint: disable=no-self-argument
|
||||
'''
|
||||
|
@ -96,7 +116,7 @@ class SessionFactory(factory.DjangoModelFactory):
|
|||
extracted = True
|
||||
if create and extracted:
|
||||
ts = obj.meeting.timeslot_set.all()
|
||||
obj.timeslotassignments.create(timeslot=ts[random.randrange(len(ts))],schedule=obj.meeting.agenda)
|
||||
obj.timeslotassignments.create(timeslot=ts[random.randrange(len(ts))],schedule=obj.meeting.schedule)
|
||||
|
||||
class ScheduleFactory(factory.DjangoModelFactory):
|
||||
class Meta:
|
||||
|
@ -119,7 +139,7 @@ class TimeSlotFactory(factory.DjangoModelFactory):
|
|||
model = TimeSlot
|
||||
|
||||
meeting = factory.SubFactory(MeetingFactory)
|
||||
type_id = 'session'
|
||||
type_id = 'regular'
|
||||
|
||||
@factory.post_generation
|
||||
def location(obj, create, extracted, **kwargs): # pylint: disable=no-self-argument
|
||||
|
@ -175,4 +195,4 @@ class SlideSubmissionFactory(factory.DjangoModelFactory):
|
|||
|
||||
make_file = factory.PostGeneration(
|
||||
lambda obj, create, extracted, **kwargs: open(obj.staged_filepath(),'a').close()
|
||||
)
|
||||
)
|
||||
|
|
|
@ -21,7 +21,7 @@ from ietf.group.models import Group
|
|||
from ietf.ietfauth.utils import has_role
|
||||
from ietf.meeting.models import Session, Meeting, Schedule, countries, timezones
|
||||
from ietf.meeting.helpers import get_next_interim_number, make_materials_directories
|
||||
from ietf.meeting.helpers import is_meeting_approved, get_next_agenda_name
|
||||
from ietf.meeting.helpers import is_interim_meeting_approved, get_next_agenda_name
|
||||
from ietf.message.models import Message
|
||||
from ietf.person.models import Person
|
||||
from ietf.utils.fields import DatepickerDateField, DurationField, MultiEmailField
|
||||
|
@ -132,7 +132,7 @@ class InterimMeetingModelForm(forms.ModelForm):
|
|||
self.fields['group'].widget.attrs['disabled'] = True
|
||||
if self.instance.city or self.instance.country:
|
||||
self.fields['in_person'].initial = True
|
||||
if is_meeting_approved(self.instance):
|
||||
if is_interim_meeting_approved(self.instance):
|
||||
self.fields['approved'].initial = True
|
||||
else:
|
||||
self.fields['approved'].initial = False
|
||||
|
@ -187,11 +187,11 @@ class InterimMeetingModelForm(forms.ModelForm):
|
|||
if kwargs.get('commit', True):
|
||||
# create schedule with meeting
|
||||
meeting.save() # pre-save so we have meeting.pk for schedule
|
||||
if not meeting.agenda:
|
||||
meeting.agenda = Schedule.objects.create(
|
||||
if not meeting.schedule:
|
||||
meeting.schedule = Schedule.objects.create(
|
||||
meeting=meeting,
|
||||
owner=Person.objects.get(name='(System)'))
|
||||
meeting.save() # save with agenda
|
||||
meeting.save() # save with schedule
|
||||
|
||||
# create directories
|
||||
make_materials_directories(meeting)
|
||||
|
@ -244,15 +244,8 @@ class InterimSessionModelForm(forms.ModelForm):
|
|||
"""NOTE: as the baseform of an inlineformset self.save(commit=True)
|
||||
never gets called"""
|
||||
session = super(InterimSessionModelForm, self).save(commit=kwargs.get('commit', True))
|
||||
if self.is_approved_or_virtual:
|
||||
session.status_id = 'scheda'
|
||||
else:
|
||||
session.status_id = 'apprw'
|
||||
session.group = self.group
|
||||
session.type_id = 'session'
|
||||
if not self.instance.pk:
|
||||
session.requested_by = self.user.person
|
||||
|
||||
session.type_id = 'regular'
|
||||
return session
|
||||
|
||||
def save_agenda(self):
|
||||
|
|
|
@ -27,7 +27,8 @@ from ietf.ietfauth.utils import has_role, user_is_person
|
|||
from ietf.liaisons.utils import get_person_for_user
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
from ietf.person.models import Person
|
||||
from ietf.meeting.models import Meeting, Schedule, TimeSlot, SchedTimeSessAssignment, ImportantDate
|
||||
from ietf.meeting.models import Meeting, Schedule, TimeSlot, SchedTimeSessAssignment, ImportantDate, SchedulingEvent
|
||||
from ietf.meeting.utils import session_requested_by, add_event_info_to_session_qs
|
||||
from ietf.name.models import ImportantDateName
|
||||
from ietf.utils.history import find_history_active_at, find_history_replacements_active_at
|
||||
from ietf.utils.mail import send_mail
|
||||
|
@ -72,7 +73,7 @@ def get_areas():
|
|||
|
||||
# get list of areas that are referenced.
|
||||
def get_area_list_from_sessions(assignments, num):
|
||||
return assignments.filter(timeslot__type = 'Session',
|
||||
return assignments.filter(timeslot__type = 'regular',
|
||||
session__group__parent__isnull = False).order_by(
|
||||
'session__group__parent__acronym').distinct().values_list(
|
||||
'session__group__parent__acronym',flat=True)
|
||||
|
@ -81,7 +82,7 @@ def build_all_agenda_slices(meeting):
|
|||
time_slices = []
|
||||
date_slices = {}
|
||||
|
||||
for ts in meeting.timeslot_set.filter(type__in=['session',]).order_by('time','name'):
|
||||
for ts in meeting.timeslot_set.filter(type__in=['regular',]).order_by('time','name'):
|
||||
ymd = ts.time.date()
|
||||
|
||||
if ymd not in date_slices and ts.location != None:
|
||||
|
@ -97,7 +98,7 @@ def build_all_agenda_slices(meeting):
|
|||
|
||||
def get_all_assignments_from_schedule(schedule):
|
||||
ss = schedule.assignments.filter(timeslot__location__isnull = False)
|
||||
ss = ss.filter(session__type__slug='session')
|
||||
ss = ss.filter(session__type__slug='regular')
|
||||
ss = ss.order_by('timeslot__time','timeslot__name')
|
||||
|
||||
return ss
|
||||
|
@ -106,7 +107,7 @@ def get_modified_from_assignments(assignments):
|
|||
return assignments.aggregate(Max('timeslot__modified'))['timeslot__modified__max']
|
||||
|
||||
def get_wg_name_list(assignments):
|
||||
return assignments.filter(timeslot__type = 'Session',
|
||||
return assignments.filter(timeslot__type = 'regular',
|
||||
session__group__isnull = False,
|
||||
session__group__parent__isnull = False).order_by(
|
||||
'session__group__acronym').distinct().values_list(
|
||||
|
@ -138,14 +139,14 @@ def get_ietf_meeting(num=None):
|
|||
|
||||
def get_schedule(meeting, name=None):
|
||||
if name is None:
|
||||
schedule = meeting.agenda
|
||||
schedule = meeting.schedule
|
||||
else:
|
||||
schedule = get_object_or_404(meeting.schedule_set, name=name)
|
||||
return schedule
|
||||
|
||||
def get_schedule_by_id(meeting, schedid):
|
||||
if schedid is None:
|
||||
schedule = meeting.agenda
|
||||
schedule = meeting.schedule
|
||||
else:
|
||||
schedule = get_object_or_404(meeting.schedule_set, id=int(schedid))
|
||||
return schedule
|
||||
|
@ -216,6 +217,11 @@ def preprocess_assignments_for_agenda(assignments_queryset, meeting):
|
|||
if a.session and a.session.historic_group and a.session.historic_group.parent_id:
|
||||
a.session.historic_group.historic_parent = parent_replacements.get(a.session.historic_group.parent_id)
|
||||
|
||||
# add current session status
|
||||
sessions = {a.session_id: a.session for a in assignments if a.session}
|
||||
for e in SchedulingEvent.objects.filter(session__in=sessions.keys()).order_by('time', 'id').iterator():
|
||||
sessions[e.session_id].current_status = e.status_id
|
||||
|
||||
return assignments
|
||||
|
||||
def read_session_file(type, num, doc):
|
||||
|
@ -282,7 +288,7 @@ def convert_draft_to_pdf(doc_name):
|
|||
pipe("ps2pdf "+psname+" "+outpath)
|
||||
os.unlink(psname)
|
||||
|
||||
def agenda_permissions(meeting, schedule, user):
|
||||
def schedule_permissions(meeting, schedule, user):
|
||||
# do this in positive logic.
|
||||
cansee = False
|
||||
canedit = False
|
||||
|
@ -400,7 +406,7 @@ def create_interim_meeting(group, date, city='', country='', timezone='UTC',
|
|||
owner=person,
|
||||
visible=True,
|
||||
public=True)
|
||||
meeting.agenda = schedule
|
||||
meeting.schedule = schedule
|
||||
meeting.save()
|
||||
return meeting
|
||||
|
||||
|
@ -446,12 +452,8 @@ def get_earliest_session_date(formset):
|
|||
return sorted([f.cleaned_data['date'] for f in formset.forms if f.cleaned_data.get('date')])[0]
|
||||
|
||||
|
||||
def is_meeting_approved(meeting):
|
||||
"""Returns True if the meeting is approved"""
|
||||
if meeting.session_set.first().status.slug == 'apprw':
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
def is_interim_meeting_approved(meeting):
|
||||
return add_event_info_to_session_qs(meeting.session_set.all()).first().current_status == 'apprw'
|
||||
|
||||
def get_next_interim_number(acronym,date):
|
||||
'''
|
||||
|
@ -504,8 +506,9 @@ def send_interim_approval_request(meetings):
|
|||
"""Sends an email to the secretariat, group chairs, and responsible area
|
||||
director or the IRTF chair noting that approval has been requested for a
|
||||
new interim meeting. Takes a list of one or more meetings."""
|
||||
group = meetings[0].session_set.first().group
|
||||
requester = meetings[0].session_set.first().requested_by
|
||||
first_session = meetings[0].session_set.first()
|
||||
group = first_session.group
|
||||
requester = session_requested_by(first_session)
|
||||
(to_email, cc_list) = gather_address_lists('session_requested',group=group,person=requester)
|
||||
from_email = (settings.SESSION_REQUEST_FROM_EMAIL)
|
||||
subject = '{group} - New Interim Meeting Request'.format(group=group.acronym)
|
||||
|
@ -535,8 +538,9 @@ def send_interim_approval_request(meetings):
|
|||
def send_interim_announcement_request(meeting):
|
||||
"""Sends an email to the secretariat that an interim meeting is ready for
|
||||
announcement, includes the link to send the official announcement"""
|
||||
group = meeting.session_set.first().group
|
||||
requester = meeting.session_set.first().requested_by
|
||||
first_session = meeting.session_set.first()
|
||||
group = first_session.group
|
||||
requester = session_requested_by(first_session)
|
||||
(to_email, cc_list) = gather_address_lists('interim_approved')
|
||||
from_email = (settings.SESSION_REQUEST_FROM_EMAIL)
|
||||
subject = '{group} - interim meeting ready for announcement'.format(group=group.acronym)
|
||||
|
@ -564,10 +568,8 @@ def send_interim_cancellation_notice(meeting):
|
|||
date=meeting.date.strftime('%Y-%m-%d'))
|
||||
start_time = session.official_timeslotassignment().timeslot.time
|
||||
end_time = start_time + session.requested_duration
|
||||
if meeting.session_set.filter(status='sched').count() > 1:
|
||||
is_multi_day = True
|
||||
else:
|
||||
is_multi_day = False
|
||||
from ietf.meeting.utils import add_event_info_to_session_qs
|
||||
is_multi_day = add_event_info_to_session_qs(meeting.session_set.all()).filter(current_status='sched').count() > 1
|
||||
template = 'meeting/interim_cancellation_notice.txt'
|
||||
context = locals()
|
||||
send_mail(None,
|
||||
|
@ -601,12 +603,24 @@ def send_interim_minutes_reminder(meeting):
|
|||
cc=cc_list)
|
||||
|
||||
|
||||
def sessions_post_save(forms):
|
||||
def sessions_post_save(request, forms):
|
||||
"""Helper function to perform various post save operations on each form of a
|
||||
InterimSessionModelForm formset"""
|
||||
for form in forms:
|
||||
if not form.has_changed():
|
||||
continue
|
||||
|
||||
if form.instance.pk is not None and not SchedulingEvent.objects.filter(session=form.instance).exists():
|
||||
if form.is_approved_or_virtual:
|
||||
status_id = 'scheda'
|
||||
else:
|
||||
status_id = 'apprw'
|
||||
SchedulingEvent.objects.create(
|
||||
session=form.instance,
|
||||
status_id=status_id,
|
||||
by=request.user.person,
|
||||
)
|
||||
|
||||
if ('date' in form.changed_data) or ('time' in form.changed_data):
|
||||
update_interim_session_assignment(form)
|
||||
if 'agenda' in form.changed_data:
|
||||
|
@ -627,13 +641,13 @@ def update_interim_session_assignment(form):
|
|||
else:
|
||||
slot = TimeSlot.objects.create(
|
||||
meeting=session.meeting,
|
||||
type_id="session",
|
||||
type_id='regular',
|
||||
duration=session.requested_duration,
|
||||
time=time)
|
||||
SchedTimeSessAssignment.objects.create(
|
||||
timeslot=slot,
|
||||
session=session,
|
||||
schedule=session.meeting.agenda)
|
||||
schedule=session.meeting.schedule)
|
||||
|
||||
def populate_important_dates(meeting):
|
||||
assert ImportantDate.objects.filter(meeting=meeting).exists() is False
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# Copyright The IETF Trust 2019, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.26 on 2019-11-18 04:01
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
import django.db.models.deletion
|
||||
import ietf.utils.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('meeting', '0020_remove_future_break_sessions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='meeting',
|
||||
old_name='agenda',
|
||||
new_name='schedule',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='meeting',
|
||||
field=ietf.utils.models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='schedule_set', to='meeting.Meeting'),
|
||||
),
|
||||
]
|
31
ietf/meeting/migrations/0022_schedulingevent.py
Normal file
31
ietf/meeting/migrations/0022_schedulingevent.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Copyright The IETF Trust 2019, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.26 on 2019-11-19 02:41
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import ietf.utils.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('person', '0009_auto_20190118_0725'),
|
||||
('name', '0007_fix_m2m_slug_id_length'),
|
||||
('meeting', '0021_rename_meeting_agenda_to_schedule'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SchedulingEvent',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('time', models.DateTimeField(default=datetime.datetime.now, help_text='When the event happened')),
|
||||
('by', ietf.utils.models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='person.Person')),
|
||||
('session', ietf.utils.models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='meeting.Session')),
|
||||
('status', ietf.utils.models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='name.SessionStatusName')),
|
||||
],
|
||||
),
|
||||
]
|
67
ietf/meeting/migrations/0023_create_scheduling_events.py
Normal file
67
ietf/meeting/migrations/0023_create_scheduling_events.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
# Copyright The IETF Trust 2019, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.26 on 2019-11-19 02:42
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
import datetime
|
||||
|
||||
def create_scheduling_events(apps, schema_editor):
|
||||
Session = apps.get_model('meeting', 'Session')
|
||||
SchedulingEvent = apps.get_model('meeting', 'SchedulingEvent')
|
||||
Person = apps.get_model('person', 'Person')
|
||||
SessionStatusName = apps.get_model('name', 'SessionStatusName')
|
||||
|
||||
system_person = Person.objects.get(name='(System)')
|
||||
session_status_names = { n.slug: n for n in SessionStatusName.objects.all() }
|
||||
|
||||
epoch_time = datetime.datetime(1970, 1, 1, 0, 0, 0)
|
||||
|
||||
for s in Session.objects.select_related('requested_by').filter(schedulingevent=None).iterator():
|
||||
# temporarily fix up weird timestamps for the migration
|
||||
if s.requested == epoch_time:
|
||||
s.requested = s.modified
|
||||
|
||||
requested_event = SchedulingEvent()
|
||||
requested_event.session = s
|
||||
requested_event.time = s.requested
|
||||
requested_event.by = s.requested_by
|
||||
requested_event.status = session_status_names[s.status_id if s.status_id == 'apprw' or (s.status_id == 'notmeet' and not s.scheduled) else 'schedw']
|
||||
requested_event.save()
|
||||
|
||||
scheduled_event = None
|
||||
if s.status_id != requested_event.status_id:
|
||||
if s.scheduled or s.status_id in ['sched', 'scheda']:
|
||||
scheduled_event = SchedulingEvent()
|
||||
scheduled_event.session = s
|
||||
if s.scheduled:
|
||||
scheduled_event.time = s.scheduled
|
||||
else:
|
||||
# we don't know when this happened
|
||||
scheduled_event.time = s.modified
|
||||
scheduled_event.by = system_person # we don't know who did it
|
||||
scheduled_event.status = session_status_names[s.status_id if s.status_id == 'scheda' else 'sched']
|
||||
scheduled_event.save()
|
||||
|
||||
final_event = None
|
||||
if s.status_id not in ['apprw', 'schedw', 'notmeet', 'sched', 'scheda']:
|
||||
final_event = SchedulingEvent()
|
||||
final_event.session = s
|
||||
final_event.time = s.modified
|
||||
final_event.by = system_person # we don't know who did it
|
||||
final_event.status = session_status_names[s.status_id]
|
||||
final_event.save()
|
||||
|
||||
def noop(apps, schema_editor):
|
||||
pass
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('meeting', '0022_schedulingevent'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_scheduling_events, noop),
|
||||
]
|
28
ietf/meeting/migrations/0024_auto_20191204_1731.py
Normal file
28
ietf/meeting/migrations/0024_auto_20191204_1731.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Copyright The IETF Trust 2019, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.26 on 2019-12-04 17:31
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('meeting', '0023_create_scheduling_events'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='session',
|
||||
name='requested',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='session',
|
||||
name='requested_by',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='session',
|
||||
name='status',
|
||||
),
|
||||
]
|
|
@ -0,0 +1,38 @@
|
|||
# Copyright The IETF Trust 2019, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.26 on 2019-12-06 11:13
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
def rename_session_to_regular(apps, schema_editor):
|
||||
Session = apps.get_model('meeting', 'Session')
|
||||
TimeSlot = apps.get_model('meeting', 'TimeSlot')
|
||||
Room = apps.get_model('meeting', 'Room')
|
||||
TimeSlotTypeName = apps.get_model('name', 'TimeSlotTypeName')
|
||||
|
||||
TimeSlotTypeName.objects.create(
|
||||
slug='regular',
|
||||
name='Regular',
|
||||
used=True,
|
||||
order=0,
|
||||
)
|
||||
|
||||
Session.objects.filter(type='session').update(type='regular')
|
||||
TimeSlot.objects.filter(type='session').update(type='regular')
|
||||
Room.session_types.through.objects.filter(timeslottypename='session').update(timeslottypename='regular')
|
||||
|
||||
TimeSlotTypeName.objects.filter(slug='session').delete()
|
||||
|
||||
def noop(apps, schema_editor):
|
||||
pass
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('meeting', '0024_auto_20191204_1731'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(rename_session_to_regular, noop),
|
||||
]
|
|
@ -102,7 +102,7 @@ class Meeting(models.Model):
|
|||
reg_area = models.CharField(blank=True, max_length=255)
|
||||
agenda_info_note = models.TextField(blank=True, help_text="Text in this field will be placed at the top of the html agenda page for the meeting. HTML can be used, but will not be validated.")
|
||||
agenda_warning_note = models.TextField(blank=True, help_text="Text in this field will be placed more prominently at the top of the html agenda page for the meeting. HTML can be used, but will not be validated.")
|
||||
agenda = ForeignKey('Schedule',null=True,blank=True, related_name='+')
|
||||
schedule = ForeignKey('Schedule',null=True,blank=True, related_name='+')
|
||||
session_request_lock_message = models.CharField(blank=True,max_length=255) # locked if not empty
|
||||
proceedings_final = models.BooleanField(default=False, help_text="Are the proceedings for this meeting complete?")
|
||||
acknowledgements = models.TextField(blank=True, help_text="Acknowledgements for use in meeting proceedings. Use ReStructuredText markup.")
|
||||
|
@ -200,13 +200,6 @@ class Meeting(models.Model):
|
|||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def sessions_that_can_meet(self):
|
||||
qs = self.session_set.exclude(status__slug='notmeet').exclude(status__slug='disappr').exclude(status__slug='deleted').exclude(status__slug='apprw')
|
||||
# Restrict graphical scheduling to meeting requests (Sessions) of type 'session' for now
|
||||
qs = qs.filter(type__slug='session')
|
||||
return qs
|
||||
|
||||
def json_url(self):
|
||||
return "/meeting/%s/json" % (self.number, )
|
||||
|
||||
|
@ -217,8 +210,8 @@ class Meeting(models.Model):
|
|||
# unfortunately, using the datetime aware json encoder seems impossible,
|
||||
# so the dates are formatted as strings here.
|
||||
agenda_url = ""
|
||||
if self.agenda:
|
||||
agenda_url = urljoin(host_scheme, self.agenda.base_url())
|
||||
if self.schedule:
|
||||
agenda_url = urljoin(host_scheme, self.schedule.base_url())
|
||||
return {
|
||||
'href': urljoin(host_scheme, self.json_url()),
|
||||
'name': self.number,
|
||||
|
@ -290,16 +283,16 @@ class Meeting(models.Model):
|
|||
pass
|
||||
return ''
|
||||
|
||||
def set_official_agenda(self, agenda):
|
||||
if self.agenda != agenda:
|
||||
self.agenda = agenda
|
||||
def set_official_schedule(self, schedule):
|
||||
if self.schedule != schedule:
|
||||
self.schedule = schedule
|
||||
self.save()
|
||||
|
||||
def updated(self):
|
||||
min_time = datetime.datetime(1970, 1, 1, 0, 0, 0) # should be Meeting.modified, but we don't have that
|
||||
timeslots_updated = self.timeslot_set.aggregate(Max('modified'))["modified__max"] or min_time
|
||||
sessions_updated = self.session_set.aggregate(Max('modified'))["modified__max"] or min_time
|
||||
assignments_updated = (self.agenda.assignments.aggregate(Max('modified'))["modified__max"] or min_time) if self.agenda else min_time
|
||||
assignments_updated = (self.schedule.assignments.aggregate(Max('modified'))["modified__max"] or min_time) if self.schedule else min_time
|
||||
ts = max(timeslots_updated, sessions_updated, assignments_updated)
|
||||
tz = pytz.timezone(settings.PRODUCTION_TIMEZONE)
|
||||
ts = tz.localize(ts)
|
||||
|
@ -459,7 +452,7 @@ class TimeSlot(models.Model):
|
|||
@property
|
||||
def session(self):
|
||||
if not hasattr(self, "_session_cache"):
|
||||
self._session_cache = self.sessions.filter(timeslotassignments__schedule=self.meeting.agenda).first()
|
||||
self._session_cache = self.sessions.filter(timeslotassignments__schedule=self.meeting.schedule).first()
|
||||
return self._session_cache
|
||||
|
||||
@property
|
||||
|
@ -609,15 +602,15 @@ class TimeSlot(models.Model):
|
|||
@python_2_unicode_compatible
|
||||
class Schedule(models.Model):
|
||||
"""
|
||||
Each person may have multiple agendas saved.
|
||||
An Agenda may be made visible, which means that it will show up in
|
||||
Each person may have multiple schedules saved.
|
||||
A Schedule may be made visible, which means that it will show up in
|
||||
public drop down menus, etc. It may also be made public, which means
|
||||
that someone who knows about it by name/id would be able to reference
|
||||
it. A non-visible, public agenda might be passed around by the
|
||||
it. A non-visible, public schedule might be passed around by the
|
||||
Secretariat to IESG members for review. Only the owner may edit the
|
||||
agenda, others may copy it
|
||||
schedule, others may copy it
|
||||
"""
|
||||
meeting = ForeignKey(Meeting, null=True)
|
||||
meeting = ForeignKey(Meeting, null=True, related_name='schedule_set')
|
||||
name = models.CharField(max_length=16, blank=False)
|
||||
owner = ForeignKey(Person)
|
||||
visible = models.BooleanField(default=True, help_text="Make this agenda available to those who know about it.")
|
||||
|
@ -666,7 +659,7 @@ class Schedule(models.Model):
|
|||
|
||||
@property
|
||||
def is_official(self):
|
||||
return (self.meeting.agenda == self)
|
||||
return (self.meeting.schedule == self)
|
||||
|
||||
# returns a dictionary {group -> [schedtimesessassignment+]}
|
||||
# and it has [] if the session is not placed.
|
||||
|
@ -705,12 +698,6 @@ class Schedule(models.Model):
|
|||
def qs_assignments_with_sessions(self):
|
||||
return self.assignments.filter(session__isnull=False)
|
||||
|
||||
@property
|
||||
def sessions_that_can_meet(self):
|
||||
if not hasattr(self, "_cached_sessions_that_can_meet"):
|
||||
self._cached_sessions_that_can_meet = self.meeting.sessions_that_can_meet.all()
|
||||
return self._cached_sessions_that_can_meet
|
||||
|
||||
def delete_schedule(self):
|
||||
self.assignments.all().delete()
|
||||
self.delete()
|
||||
|
@ -720,7 +707,7 @@ class Schedule(models.Model):
|
|||
class SchedTimeSessAssignment(models.Model):
|
||||
"""
|
||||
This model provides an N:M relationship between Session and TimeSlot.
|
||||
Each relationship is attached to the named agenda, which is owned by
|
||||
Each relationship is attached to the named schedule, which is owned by
|
||||
a specific person/user.
|
||||
"""
|
||||
timeslot = ForeignKey('TimeSlot', null=False, blank=False, related_name='sessionassignments')
|
||||
|
@ -807,7 +794,7 @@ class SchedTimeSessAssignment(models.Model):
|
|||
components.append(g.acronym)
|
||||
components.append(slugify(self.session.name))
|
||||
|
||||
if self.timeslot.type_id in ('session', 'plenary'):
|
||||
if self.timeslot.type_id in ('regular', 'plenary'):
|
||||
if self.timeslot.type_id == "plenary":
|
||||
components.append("1plenary")
|
||||
else:
|
||||
|
@ -900,11 +887,8 @@ class Session(models.Model):
|
|||
group = ForeignKey(Group) # The group type historically determined the session type. BOFs also need to be added as a group. Note that not all meeting requests have a natural group to associate with.
|
||||
attendees = models.IntegerField(null=True, blank=True)
|
||||
agenda_note = models.CharField(blank=True, max_length=255)
|
||||
requested = models.DateTimeField(default=datetime.datetime.now)
|
||||
requested_by = ForeignKey(Person)
|
||||
requested_duration = models.DurationField(default=datetime.timedelta(0))
|
||||
comments = models.TextField(blank=True)
|
||||
status = ForeignKey(SessionStatusName)
|
||||
scheduled = models.DateTimeField(null=True, blank=True)
|
||||
modified = models.DateTimeField(auto_now=True)
|
||||
remote_instructions = models.CharField(blank=True,max_length=1024)
|
||||
|
@ -914,9 +898,6 @@ class Session(models.Model):
|
|||
|
||||
unique_constraints_dict = None
|
||||
|
||||
def not_meeting(self):
|
||||
return self.status_id == 'notmeet'
|
||||
|
||||
# Should work on how materials are captured so that deleted things are no longer associated with the session
|
||||
# (We can keep the information about something being added to and removed from a session in the document's history)
|
||||
def get_material(self, material_type, only_one):
|
||||
|
@ -959,18 +940,22 @@ class Session(models.Model):
|
|||
def drafts(self):
|
||||
return list(self.materials.filter(type='draft'))
|
||||
|
||||
# The utilities below are used in the proceedings and materials
|
||||
# templates, and should be moved there - then we could also query
|
||||
# out the needed information in a few passes and speed up those
|
||||
# pages.
|
||||
def all_meeting_sessions_for_group(self):
|
||||
from ietf.meeting.utils import add_event_info_to_session_qs
|
||||
if self.group.type_id in ['wg','rg','ag']:
|
||||
if not hasattr(self, "_all_meeting_sessions_for_group_cache"):
|
||||
sessions = [s for s in self.meeting.session_set.filter(group=self.group,type=self.type) if s.official_timeslotassignment()]
|
||||
sessions = [s for s in add_event_info_to_session_qs(self.meeting.session_set.filter(group=self.group,type=self.type)) if s.official_timeslotassignment()]
|
||||
self._all_meeting_sessions_for_group_cache = sorted(sessions, key = lambda x: x.official_timeslotassignment().timeslot.time)
|
||||
return self._all_meeting_sessions_for_group_cache
|
||||
else:
|
||||
return [self]
|
||||
|
||||
def all_meeting_sessions_cancelled(self):
|
||||
states = set([s.status_id for s in self.all_meeting_sessions_for_group()])
|
||||
return 'canceled' in states and len(states) == 1
|
||||
return set(s.current_status for s in self.all_meeting_sessions_for_group()) == {'canceled'}
|
||||
|
||||
def all_meeting_recordings(self):
|
||||
recordings = [] # These are not sets because we need to preserve relative ordering or redo the ordering work later
|
||||
|
@ -1028,13 +1013,21 @@ class Session(models.Model):
|
|||
if self.meeting.type_id == "interim":
|
||||
return self.meeting.number
|
||||
|
||||
if self.status.slug in ('canceled','disappr','notmeet','deleted'):
|
||||
ss0name = "(%s)" % self.status.name
|
||||
status_id = None
|
||||
if hasattr(self, 'current_status'):
|
||||
status_id = self.current_status
|
||||
elif self.pk is not None:
|
||||
latest_event = SchedulingEvent.objects.filter(session=self.pk).order_by('-time', '-id').first()
|
||||
if latest_event:
|
||||
status_id = latest_event.status_id
|
||||
|
||||
if status_id in ('canceled','disappr','notmeet','deleted'):
|
||||
ss0name = "(%s)" % SessionStatusName.objects.get(slug=status_id).name
|
||||
else:
|
||||
ss0name = "(unscheduled)"
|
||||
ss = self.timeslotassignments.filter(schedule=self.meeting.agenda).order_by('timeslot__time')
|
||||
ss = self.timeslotassignments.filter(schedule=self.meeting.schedule).order_by('timeslot__time')
|
||||
if ss:
|
||||
ss0name = ','.join([x.timeslot.time.strftime("%a-%H%M") for x in ss])
|
||||
ss0name = ','.join(x.timeslot.time.strftime("%a-%H%M") for x in ss)
|
||||
return "%s: %s %s %s" % (self.meeting, self.group.acronym, self.name, ss0name)
|
||||
|
||||
@property
|
||||
|
@ -1065,11 +1058,11 @@ class Session(models.Model):
|
|||
def reverse_constraints(self):
|
||||
return Constraint.objects.filter(target=self.group, meeting=self.meeting).order_by('name__name')
|
||||
|
||||
def timeslotassignment_for_agenda(self, schedule):
|
||||
def timeslotassignment_for_schedule(self, schedule):
|
||||
return self.timeslotassignments.filter(schedule=schedule).first()
|
||||
|
||||
def official_timeslotassignment(self):
|
||||
return self.timeslotassignment_for_agenda(self.meeting.agenda)
|
||||
return self.timeslotassignment_for_schedule(self.meeting.schedule)
|
||||
|
||||
def constraints_dict(self, host_scheme):
|
||||
constraint_list = []
|
||||
|
@ -1110,16 +1103,47 @@ class Session(models.Model):
|
|||
sess1['bof'] = str(self.group.is_bof())
|
||||
sess1['agenda_note'] = self.agenda_note
|
||||
sess1['attendees'] = str(self.attendees)
|
||||
sess1['status'] = self.status.name
|
||||
|
||||
# fish out scheduling information - eventually, we should pick
|
||||
# this out in the caller instead
|
||||
latest_event = None
|
||||
first_event = None
|
||||
|
||||
if self.pk is not None:
|
||||
if not hasattr(self, 'current_status') or not hasattr(self, 'requested_time'):
|
||||
events = list(SchedulingEvent.objects.filter(session=self.pk).order_by('time', 'id'))
|
||||
if events:
|
||||
first_event = events[0]
|
||||
latest_event = events[-1]
|
||||
|
||||
status_id = None
|
||||
if hasattr(self, 'current_status'):
|
||||
status_id = self.current_status
|
||||
elif latest_event:
|
||||
status_id = latest_event.status_id
|
||||
|
||||
sess1['status'] = SessionStatusName.objects.get(slug=status_id).name if status_id else None
|
||||
if self.comments is not None:
|
||||
sess1['comments'] = self.comments
|
||||
sess1['requested_time'] = self.requested.strftime("%Y-%m-%d")
|
||||
# the related person object sometimes does not exist in the dataset.
|
||||
try:
|
||||
if self.requested_by is not None:
|
||||
sess1['requested_by'] = str(self.requested_by)
|
||||
except Person.DoesNotExist:
|
||||
pass
|
||||
|
||||
requested_time = None
|
||||
if hasattr(self, 'requested_time'):
|
||||
requested_time = self.requested_time
|
||||
elif first_event:
|
||||
requested_time = first_event.time
|
||||
sess1['requested_time'] = requested_time.strftime("%Y-%m-%d") if requested_time else None
|
||||
|
||||
|
||||
requested_by = None
|
||||
if hasattr(self, 'requested_by'):
|
||||
requested_by = self.requested_by
|
||||
elif first_event:
|
||||
requested_by = first_event.by_id
|
||||
|
||||
if requested_by is not None:
|
||||
requested_by_person = Person.objects.filter(pk=requested_by).first()
|
||||
if requested_by_person:
|
||||
sess1['requested_by'] = str(requested_by_person)
|
||||
|
||||
sess1['requested_duration']= "%.1f" % (float(self.requested_duration.seconds) / 3600)
|
||||
sess1['special_request'] = str(self.special_request_token)
|
||||
|
@ -1137,12 +1161,6 @@ class Session(models.Model):
|
|||
else:
|
||||
return "The agenda has not been uploaded yet."
|
||||
|
||||
def ical_status(self):
|
||||
if self.status.slug == 'canceled': # sic
|
||||
return "CANCELLED"
|
||||
else:
|
||||
return "CONFIRMED"
|
||||
|
||||
def agenda_file(self):
|
||||
if not hasattr(self, '_agenda_file'):
|
||||
self._agenda_file = ""
|
||||
|
@ -1164,6 +1182,16 @@ class Session(models.Model):
|
|||
else:
|
||||
return self.group.acronym
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class SchedulingEvent(models.Model):
|
||||
session = ForeignKey(Session)
|
||||
time = models.DateTimeField(default=datetime.datetime.now, help_text="When the event happened")
|
||||
status = ForeignKey(SessionStatusName)
|
||||
by = ForeignKey(Person)
|
||||
|
||||
def __str__(self):
|
||||
return u'%s : %s : %s : %s' % (self.session, self.status, self.time, self.by)
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ImportantDate(models.Model):
|
||||
meeting = ForeignKey(Meeting)
|
||||
|
|
|
@ -13,12 +13,14 @@ from ietf import api
|
|||
|
||||
from ietf.meeting.models import ( Meeting, ResourceAssociation, Constraint, Room, Schedule, Session,
|
||||
TimeSlot, SchedTimeSessAssignment, SessionPresentation, FloorPlan,
|
||||
UrlResource, ImportantDate, SlideSubmission )
|
||||
UrlResource, ImportantDate, SlideSubmission, SchedulingEvent )
|
||||
|
||||
from ietf.name.resources import MeetingTypeNameResource
|
||||
class MeetingResource(ModelResource):
|
||||
type = ToOneField(MeetingTypeNameResource, 'type')
|
||||
agenda = ToOneField('ietf.meeting.resources.ScheduleResource', 'agenda', null=True)
|
||||
schedule = ToOneField('ietf.meeting.resources.ScheduleResource', 'schedule', null=True)
|
||||
# for backward compatibility:
|
||||
agenda = ToOneField('ietf.meeting.resources.ScheduleResource', 'schedule', null=True)
|
||||
updated = DateTimeField(attribute='updated')
|
||||
class Meta:
|
||||
cache = SimpleCache()
|
||||
|
@ -48,6 +50,7 @@ class MeetingResource(ModelResource):
|
|||
"agenda_warning_note": ALL,
|
||||
"session_request_lock_message": ALL,
|
||||
"type": ALL_WITH_RELATIONS,
|
||||
"schedule": ALL_WITH_RELATIONS,
|
||||
"agenda": ALL_WITH_RELATIONS,
|
||||
"proceedings_final": ALL,
|
||||
}
|
||||
|
@ -163,14 +166,12 @@ api.meeting.register(ScheduleResource())
|
|||
|
||||
from ietf.group.resources import GroupResource
|
||||
from ietf.doc.resources import DocumentResource
|
||||
from ietf.name.resources import TimeSlotTypeNameResource, SessionStatusNameResource
|
||||
from ietf.name.resources import TimeSlotTypeNameResource
|
||||
from ietf.person.resources import PersonResource
|
||||
class SessionResource(ModelResource):
|
||||
meeting = ToOneField(MeetingResource, 'meeting')
|
||||
type = ToOneField(TimeSlotTypeNameResource, 'type')
|
||||
group = ToOneField(GroupResource, 'group')
|
||||
requested_by = ToOneField(PersonResource, 'requested_by')
|
||||
status = ToOneField(SessionStatusNameResource, 'status')
|
||||
materials = ToManyField(DocumentResource, 'materials', null=True)
|
||||
resources = ToManyField(ResourceAssociationResource, 'resources', null=True)
|
||||
assignments = ToManyField('ietf.meeting.resources.SchedTimeSessAssignmentResource', 'timeslotassignments', null=True)
|
||||
|
@ -203,6 +204,26 @@ class SessionResource(ModelResource):
|
|||
}
|
||||
api.meeting.register(SessionResource())
|
||||
|
||||
from ietf.name.resources import SessionStatusNameResource
|
||||
class SchedulingEventResource(ModelResource):
|
||||
session = ToOneField(SessionResource, 'session')
|
||||
status = ToOneField(SessionStatusNameResource, 'status')
|
||||
by = ToOneField(PersonResource, 'location')
|
||||
class Meta:
|
||||
cache = SimpleCache()
|
||||
queryset = SchedulingEvent.objects.all()
|
||||
serializer = api.Serializer()
|
||||
ordering = ['id', 'time', 'modified', 'meeting',]
|
||||
filtering = {
|
||||
"id": ALL,
|
||||
"time": ALL,
|
||||
"session": ALL_WITH_RELATIONS,
|
||||
"by": ALL_WITH_RELATIONS,
|
||||
}
|
||||
api.meeting.register(SchedulingEventResource())
|
||||
|
||||
|
||||
|
||||
from ietf.name.resources import TimeSlotTypeNameResource
|
||||
class TimeSlotResource(ModelResource):
|
||||
meeting = ToOneField(MeetingResource, 'meeting')
|
||||
|
@ -234,6 +255,8 @@ class SchedTimeSessAssignmentResource(ModelResource):
|
|||
timeslot = ToOneField(TimeSlotResource, 'timeslot')
|
||||
session = ToOneField(SessionResource, 'session', null=True)
|
||||
schedule = ToOneField(ScheduleResource, 'schedule')
|
||||
# for backward compatibility:
|
||||
agenda = ToOneField(ScheduleResource, 'schedule')
|
||||
extendedfrom = ToOneField('ietf.meeting.resources.SchedTimeSessAssignmentResource', 'extendedfrom', null=True)
|
||||
class Meta:
|
||||
cache = SimpleCache()
|
||||
|
@ -250,6 +273,7 @@ class SchedTimeSessAssignmentResource(ModelResource):
|
|||
"timeslot": ALL_WITH_RELATIONS,
|
||||
"session": ALL_WITH_RELATIONS,
|
||||
"schedule": ALL_WITH_RELATIONS,
|
||||
"agenda": ALL_WITH_RELATIONS,
|
||||
"extendedfrom": ALL_WITH_RELATIONS,
|
||||
}
|
||||
api.meeting.register(SchedTimeSessAssignmentResource())
|
||||
|
|
|
@ -14,7 +14,7 @@ import debug # pyflakes:ignore
|
|||
from ietf.doc.factories import DocumentFactory
|
||||
from ietf.group.models import Group
|
||||
from ietf.meeting.models import (Meeting, Room, TimeSlot, Session, Schedule, SchedTimeSessAssignment,
|
||||
ResourceAssociation, SessionPresentation, UrlResource)
|
||||
ResourceAssociation, SessionPresentation, UrlResource, SchedulingEvent)
|
||||
from ietf.meeting.helpers import create_interim_meeting
|
||||
from ietf.name.models import RoomResourceName
|
||||
from ietf.person.models import Person
|
||||
|
@ -25,19 +25,20 @@ def make_interim_meeting(group,date,status='sched'):
|
|||
time = datetime.datetime.combine(date, datetime.time(9))
|
||||
meeting = create_interim_meeting(group=group,date=date)
|
||||
session = Session.objects.create(meeting=meeting, group=group,
|
||||
attendees=10, requested_by=system_person, status_id=status,
|
||||
attendees=10,
|
||||
requested_duration=datetime.timedelta(minutes=20),
|
||||
remote_instructions='http://webex.com',
|
||||
scheduled=datetime.datetime.now(),type_id="session")
|
||||
type_id='regular')
|
||||
SchedulingEvent.objects.create(session=session, status_id=status, by=system_person)
|
||||
slot = TimeSlot.objects.create(
|
||||
meeting=meeting,
|
||||
type_id="session",
|
||||
type_id='regular',
|
||||
duration=session.requested_duration,
|
||||
time=time)
|
||||
SchedTimeSessAssignment.objects.create(
|
||||
timeslot=slot,
|
||||
session=session,
|
||||
schedule=session.meeting.agenda)
|
||||
schedule=session.meeting.schedule)
|
||||
# agenda
|
||||
name = "agenda-%s-%s-%s" % (meeting.number, group.acronym, "01")
|
||||
rev = '00'
|
||||
|
@ -77,14 +78,14 @@ def make_meeting_test_data(meeting=None):
|
|||
|
||||
if not meeting:
|
||||
meeting = Meeting.objects.get(number="72", type="ietf")
|
||||
schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="test-agenda", visible=True, public=True)
|
||||
unofficial_schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="test-unofficial-agenda", visible=True, public=True)
|
||||
schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="test-schedule", visible=True, public=True)
|
||||
unofficial_schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="test-unofficial-schedule", visible=True, public=True)
|
||||
|
||||
# test room
|
||||
pname = RoomResourceName.objects.create(name='projector',slug='proj')
|
||||
projector = ResourceAssociation.objects.create(name=pname,icon="notfound.png",desc="Basic projector")
|
||||
room = Room.objects.create(meeting=meeting, name="Test Room", capacity=123, functional_name="Testing Ground")
|
||||
room.session_types.add("session")
|
||||
room.session_types.add('regular')
|
||||
room.resources.add(projector)
|
||||
asname = RoomResourceName.objects.get(slug='audiostream')
|
||||
UrlResource.objects.create(name=asname, room=room, url='http://ietf{number}streaming.dnsalias.net/ietf/ietf{number}1.m3u'.format(number=meeting.number))
|
||||
|
@ -99,10 +100,10 @@ def make_meeting_test_data(meeting=None):
|
|||
|
||||
# slots
|
||||
session_date = meeting.date + datetime.timedelta(days=1)
|
||||
slot1 = TimeSlot.objects.create(meeting=meeting, type_id="session", location=room,
|
||||
slot1 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
|
||||
duration=datetime.timedelta(minutes=30),
|
||||
time=datetime.datetime.combine(session_date, datetime.time(9, 30)))
|
||||
slot2 = TimeSlot.objects.create(meeting=meeting, type_id="session", location=room,
|
||||
slot2 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room,
|
||||
duration=datetime.timedelta(minutes=30),
|
||||
time=datetime.datetime.combine(session_date, datetime.time(10, 30)))
|
||||
breakfast_slot = TimeSlot.objects.create(meeting=meeting, type_id="lead", location=breakfast_room,
|
||||
|
@ -117,46 +118,47 @@ def make_meeting_test_data(meeting=None):
|
|||
# mars WG
|
||||
mars = Group.objects.get(acronym='mars')
|
||||
mars_session = Session.objects.create(meeting=meeting, group=mars,
|
||||
attendees=10, requested_by=system_person, status_id="schedw",
|
||||
requested_duration=datetime.timedelta(minutes=20),
|
||||
scheduled=datetime.datetime.now(),type_id="session")
|
||||
attendees=10, requested_duration=datetime.timedelta(minutes=20),
|
||||
type_id='regular')
|
||||
SchedulingEvent.objects.create(session=mars_session, status_id='schedw', by=system_person)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=slot1, session=mars_session, schedule=schedule)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=slot2, session=mars_session, schedule=unofficial_schedule)
|
||||
|
||||
# ames WG
|
||||
ames_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="ames"),
|
||||
attendees=10, requested_by=system_person, status_id="schedw",
|
||||
attendees=10,
|
||||
requested_duration=datetime.timedelta(minutes=20),
|
||||
scheduled=datetime.datetime.now(),type_id="session")
|
||||
type_id='regular')
|
||||
SchedulingEvent.objects.create(session=ames_session, status_id='schedw', by=system_person)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=slot2, session=ames_session, schedule=schedule)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=slot1, session=ames_session, schedule=unofficial_schedule)
|
||||
|
||||
# IESG breakfast
|
||||
iesg_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="iesg"),
|
||||
name="IESG Breakfast", attendees=25,
|
||||
requested_by=system_person, status_id="schedw",
|
||||
requested_duration=datetime.timedelta(minutes=20),
|
||||
scheduled=datetime.datetime.now(),type_id="lead")
|
||||
type_id="lead")
|
||||
SchedulingEvent.objects.create(session=iesg_session, status_id='schedw', by=system_person)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=breakfast_slot, session=iesg_session, schedule=schedule)
|
||||
# No breakfast on unofficial schedule
|
||||
|
||||
# Registration
|
||||
reg_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="secretariat"),
|
||||
name="Registration", attendees=250,
|
||||
requested_by=system_person, status_id="schedw",
|
||||
requested_duration=datetime.timedelta(minutes=480),
|
||||
scheduled=datetime.datetime.now(),type_id="reg")
|
||||
type_id="reg")
|
||||
SchedulingEvent.objects.create(session=reg_session, status_id='schedw', by=system_person)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=reg_slot, session=reg_session, schedule=schedule)
|
||||
|
||||
# Break
|
||||
break_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="secretariat"),
|
||||
name="Morning Break", attendees=250,
|
||||
requested_by=system_person, status_id="schedw",
|
||||
requested_duration=datetime.timedelta(minutes=30),
|
||||
scheduled=datetime.datetime.now(),type_id="break")
|
||||
type_id="break")
|
||||
SchedulingEvent.objects.create(session=break_session, status_id='schedw', by=system_person)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=break_slot, session=break_session, schedule=schedule)
|
||||
|
||||
meeting.agenda = schedule
|
||||
meeting.schedule = schedule
|
||||
meeting.save()
|
||||
|
||||
# Convenience for the tests
|
||||
|
|
|
@ -21,16 +21,16 @@ from ietf.utils.mail import outbox
|
|||
|
||||
|
||||
class ApiTests(TestCase):
|
||||
def test_update_agenda(self):
|
||||
def test_update_schedule(self):
|
||||
meeting = make_meeting_test_data()
|
||||
schedule = Schedule.objects.get(meeting__number=72,name="test-agenda")
|
||||
schedule = Schedule.objects.get(meeting__number=72,name="test-schedule")
|
||||
mars_session = Session.objects.filter(meeting=meeting, group__acronym="mars").first()
|
||||
ames_session = Session.objects.filter(meeting=meeting, group__acronym="ames").first()
|
||||
|
||||
mars_scheduled = SchedTimeSessAssignment.objects.get(session=mars_session,schedule__name='test-agenda')
|
||||
mars_scheduled = SchedTimeSessAssignment.objects.get(session=mars_session,schedule__name='test-schedule')
|
||||
mars_slot = mars_scheduled.timeslot
|
||||
|
||||
ames_scheduled = SchedTimeSessAssignment.objects.get(session=ames_session,schedule__name='test-agenda')
|
||||
ames_scheduled = SchedTimeSessAssignment.objects.get(session=ames_session,schedule__name='test-schedule')
|
||||
ames_slot = ames_scheduled.timeslot
|
||||
|
||||
def do_unschedule(assignment):
|
||||
|
@ -96,16 +96,16 @@ class ApiTests(TestCase):
|
|||
r = do_extend(schedule,mars_scheduled)
|
||||
self.assertEqual(r.status_code, 201)
|
||||
self.assertTrue("error" not in r.json())
|
||||
self.assertEqual(mars_session.timeslotassignments.filter(schedule__name='test-agenda').count(),2)
|
||||
self.assertEqual(mars_session.timeslotassignments.filter(schedule__name='test-schedule').count(),2)
|
||||
|
||||
# Unschedule mars
|
||||
r = do_unschedule(mars_scheduled)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertNotIn("error", r.json())
|
||||
# Make sure it got both the original and extended session
|
||||
self.assertEqual(mars_session.timeslotassignments.filter(schedule__name='test-agenda').count(),0)
|
||||
self.assertEqual(mars_session.timeslotassignments.filter(schedule__name='test-schedule').count(),0)
|
||||
|
||||
self.assertEqual(SchedTimeSessAssignment.objects.get(session=ames_session,schedule__name='test-agenda').timeslot, mars_slot)
|
||||
self.assertEqual(SchedTimeSessAssignment.objects.get(session=ames_session,schedule__name='test-schedule').timeslot, mars_slot)
|
||||
|
||||
|
||||
def test_constraints_json(self):
|
||||
|
@ -143,10 +143,10 @@ class ApiTests(TestCase):
|
|||
|
||||
def test_create_new_room(self):
|
||||
meeting = make_meeting_test_data()
|
||||
timeslots_before = meeting.timeslot_set.filter(type='session').count()
|
||||
timeslots_before = meeting.timeslot_set.filter(type='regular').count()
|
||||
url = urlreverse("ietf.meeting.ajax.timeslot_roomsurl", kwargs=dict(num=meeting.number))
|
||||
|
||||
post_data = { "name": "new room", "capacity": "50" , "resources": [], "session_types":["session"]}
|
||||
post_data = { "name": "new room", "capacity": "50" , "resources": [], "session_types":['regular']}
|
||||
|
||||
# unauthorized post
|
||||
r = self.client.post(url, post_data)
|
||||
|
@ -159,7 +159,7 @@ class ApiTests(TestCase):
|
|||
self.assertEqual(r.status_code, 302)
|
||||
self.assertTrue(meeting.room_set.filter(name="new room"))
|
||||
|
||||
timeslots_after = meeting.timeslot_set.filter(type='session').count()
|
||||
timeslots_after = meeting.timeslot_set.filter(type='regular').count()
|
||||
# It's not clear that what that ajax function is doing is the right thing to do,
|
||||
# but it currently makes a new timeslot for any existing timeslot.
|
||||
# The condition tested below relies on the timeslots before this test all having different start and end times
|
||||
|
@ -212,9 +212,9 @@ class ApiTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
info = r.json()
|
||||
self.assertEqual(set([x['short_name'] for x in info]),set([s.session.short_name for s in meeting.agenda.assignments.filter(session__type_id='session')]))
|
||||
self.assertEqual(set([x['short_name'] for x in info]),set([s.session.short_name for s in meeting.schedule.assignments.filter(session__type_id='regular')]))
|
||||
|
||||
schedule = meeting.agenda
|
||||
schedule = meeting.schedule
|
||||
url = urlreverse("ietf.meeting.ajax.assignments_json",
|
||||
kwargs=dict(num=meeting.number,owner=schedule.owner_email(),name=schedule.name))
|
||||
r = self.client.get(url)
|
||||
|
@ -281,45 +281,45 @@ class ApiTests(TestCase):
|
|||
def test_schedule_json(self):
|
||||
meeting = make_meeting_test_data()
|
||||
|
||||
url = urlreverse("ietf.meeting.ajax.agenda_infourl",
|
||||
url = urlreverse("ietf.meeting.ajax.schedule_infourl",
|
||||
kwargs=dict(num=meeting.number,
|
||||
owner=meeting.agenda.owner_email(),
|
||||
name=meeting.agenda.name))
|
||||
owner=meeting.schedule.owner_email(),
|
||||
name=meeting.schedule.name))
|
||||
|
||||
r = self.client.get(url)
|
||||
info = r.json()
|
||||
self.assertEqual(info["schedule_id"], meeting.agenda.pk)
|
||||
self.assertEqual(info["schedule_id"], meeting.schedule.pk)
|
||||
|
||||
def test_create_new_schedule(self):
|
||||
meeting = make_meeting_test_data()
|
||||
|
||||
url = urlreverse("ietf.meeting.ajax.agenda_infosurl",
|
||||
url = urlreverse("ietf.meeting.ajax.schedule_infosurl",
|
||||
kwargs=dict(num=meeting.number))
|
||||
post_data = {
|
||||
'name': 'new-agenda',
|
||||
'name': 'new-schedule',
|
||||
}
|
||||
|
||||
# unauthorized post
|
||||
self.client.login(username="plain", password="plain+password")
|
||||
r = self.client.post(url, post_data)
|
||||
self.assertEqual(r.status_code, 403)
|
||||
self.assertTrue(not meeting.schedule_set.filter(name='new-agenda'))
|
||||
self.assertTrue(not meeting.schedule_set.filter(name='new-schedule'))
|
||||
|
||||
# create new agenda
|
||||
# create new schedule
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
r = self.client.post(url, post_data)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertTrue(meeting.schedule_set.filter(name='new-agenda'))
|
||||
self.assertTrue(meeting.schedule_set.filter(name='new-schedule'))
|
||||
|
||||
def test_update_schedule(self):
|
||||
def test_update_meeting_schedule(self):
|
||||
meeting = make_meeting_test_data()
|
||||
|
||||
self.assertTrue(meeting.agenda.visible)
|
||||
self.assertTrue(meeting.schedule.visible)
|
||||
|
||||
url = urlreverse("ietf.meeting.ajax.agenda_infourl",
|
||||
url = urlreverse("ietf.meeting.ajax.schedule_infourl",
|
||||
kwargs=dict(num=meeting.number,
|
||||
owner=meeting.agenda.owner_email(),
|
||||
name=meeting.agenda.name))
|
||||
owner=meeting.schedule.owner_email(),
|
||||
name=meeting.schedule.name))
|
||||
|
||||
post_data = {
|
||||
'visible': 'false',
|
||||
|
@ -334,21 +334,21 @@ class ApiTests(TestCase):
|
|||
r = self.client.post(url, post_data)
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
# change agenda
|
||||
# change schedule
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
r = self.client.post(url, post_data)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
changed_schedule = Schedule.objects.get(pk=meeting.agenda.pk)
|
||||
changed_schedule = Schedule.objects.get(pk=meeting.schedule.pk)
|
||||
self.assertTrue(not changed_schedule.visible)
|
||||
self.assertEqual(changed_schedule.name, "new-test-name")
|
||||
|
||||
def test_delete_schedule(self):
|
||||
meeting = make_meeting_test_data()
|
||||
|
||||
url = urlreverse("ietf.meeting.ajax.agenda_infourl",
|
||||
url = urlreverse("ietf.meeting.ajax.schedule_infourl",
|
||||
kwargs=dict(num=meeting.number,
|
||||
owner=meeting.agenda.owner_email(),
|
||||
name=meeting.agenda.name))
|
||||
owner=meeting.schedule.owner_email(),
|
||||
name=meeting.schedule.name))
|
||||
# unauthorized delete
|
||||
self.client.login(username="plain", password="plain+password")
|
||||
r = self.client.delete(url)
|
||||
|
@ -358,16 +358,16 @@ class ApiTests(TestCase):
|
|||
self.client.login(username="secretary", password="secretary+password")
|
||||
r = self.client.delete(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(not Schedule.objects.filter(pk=meeting.agenda.pk))
|
||||
self.assertTrue(not Schedule.objects.filter(pk=meeting.schedule.pk))
|
||||
|
||||
def test_set_meeting_agenda(self):
|
||||
def test_set_meeting_schedule(self):
|
||||
meeting = make_meeting_test_data()
|
||||
schedule = meeting.agenda
|
||||
schedule = meeting.schedule
|
||||
|
||||
url = urlreverse("ietf.meeting.ajax.meeting_json",
|
||||
kwargs=dict(num=meeting.number))
|
||||
post_data = {
|
||||
"agenda": "",
|
||||
"schedule": "",
|
||||
}
|
||||
# unauthorized post
|
||||
self.client.login(username="ad", password="ad+password")
|
||||
|
@ -378,18 +378,18 @@ class ApiTests(TestCase):
|
|||
self.client.login(username="secretary", password="secretary+password")
|
||||
r = self.client.post(url, post_data)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(not Meeting.objects.get(pk=meeting.pk).agenda)
|
||||
self.assertTrue(not Meeting.objects.get(pk=meeting.pk).schedule)
|
||||
|
||||
# set agenda - first fail with non-public
|
||||
# set schedule - first fail with non-public
|
||||
post_data = {
|
||||
"agenda": schedule.name,
|
||||
"schedule": schedule.name,
|
||||
}
|
||||
schedule.public = False
|
||||
schedule.save()
|
||||
|
||||
r = self.client.post(url, post_data)
|
||||
self.assertTrue(r.status_code != 200)
|
||||
self.assertTrue(not Meeting.objects.get(pk=meeting.pk).agenda)
|
||||
self.assertTrue(not Meeting.objects.get(pk=meeting.pk).schedule)
|
||||
|
||||
# then go through with public
|
||||
schedule.public = True
|
||||
|
@ -399,7 +399,7 @@ class ApiTests(TestCase):
|
|||
prior_length= len(outbox)
|
||||
r = self.client.post(url, post_data)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(Meeting.objects.get(pk=meeting.pk).agenda, schedule)
|
||||
self.assertEqual(Meeting.objects.get(pk=meeting.pk).schedule, schedule)
|
||||
self.assertEqual(len(outbox),prior_length)
|
||||
|
||||
def test_read_only(self):
|
||||
|
@ -407,20 +407,20 @@ class ApiTests(TestCase):
|
|||
|
||||
# Secretariat
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
url = '/meeting/%s/agenda/%s/%s/permissions' % (meeting.number, meeting.agenda.owner.email_address(), meeting.agenda.name);
|
||||
url = '/meeting/%s/agenda/%s/%s/permissions' % (meeting.number, meeting.schedule.owner.email_address(), meeting.schedule.name);
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
info = r.json()
|
||||
self.assertEqual(info['secretariat'], True)
|
||||
self.assertEqual(urlsplit(info['owner_href'])[2], "/person/%s.json" % meeting.agenda.owner_id)
|
||||
self.assertEqual(urlsplit(info['owner_href'])[2], "/person/%s.json" % meeting.schedule.owner_id)
|
||||
self.assertEqual(info['read_only'], True)
|
||||
self.assertEqual(info['save_perm'], True)
|
||||
|
||||
# owner
|
||||
self.client.login(username=meeting.agenda.owner.user.username,
|
||||
password=meeting.agenda.owner.user.username+"+password")
|
||||
url = '/meeting/%s/agenda/%s/%s/permissions' % (meeting.number, meeting.agenda.owner.email_address(), meeting.agenda.name);
|
||||
self.client.login(username=meeting.schedule.owner.user.username,
|
||||
password=meeting.schedule.owner.user.username+"+password")
|
||||
url = '/meeting/%s/agenda/%s/%s/permissions' % (meeting.number, meeting.schedule.owner.email_address(), meeting.schedule.name);
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
@ -434,7 +434,7 @@ class ApiTests(TestCase):
|
|||
scheduled = SchedTimeSessAssignment.objects.filter(
|
||||
session__meeting=meeting, session__group__acronym="mars").first()
|
||||
|
||||
url = '/meeting/%s/agenda/%s/%s/session/%u.json' % (meeting.number, meeting.agenda.owner_email(), meeting.agenda.name, scheduled.pk)
|
||||
url = '/meeting/%s/agenda/%s/%s/session/%u.json' % (meeting.number, meeting.schedule.owner_email(), meeting.schedule.name, scheduled.pk)
|
||||
|
||||
post_data = {
|
||||
"pinned": True
|
||||
|
@ -448,11 +448,11 @@ class ApiTests(TestCase):
|
|||
self.assertTrue(not SchedTimeSessAssignment.objects.get(pk=scheduled.pk).pinned)
|
||||
|
||||
# set pinned
|
||||
meeting.agenda.owner = Person.objects.get(user__username="secretary")
|
||||
meeting.agenda.save()
|
||||
meeting.schedule.owner = Person.objects.get(user__username="secretary")
|
||||
meeting.schedule.save()
|
||||
|
||||
# need to rebuild URL, since the agenda owner has changed.
|
||||
url = '/meeting/%s/agenda/%s/%s/session/%u.json' % (meeting.number, meeting.agenda.owner_email(), meeting.agenda.name, scheduled.pk)
|
||||
# need to rebuild URL, since the schedule owner has changed.
|
||||
url = '/meeting/%s/agenda/%s/%s/session/%u.json' % (meeting.number, meeting.schedule.owner_email(), meeting.schedule.name, scheduled.pk)
|
||||
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
r = self.client.put(url, post_data)
|
||||
|
@ -466,7 +466,7 @@ class TimeSlotEditingApiTests(TestCase):
|
|||
def test_manipulate_timeslot(self):
|
||||
meeting = make_meeting_test_data()
|
||||
slot = meeting.timeslot_set.all()[0]
|
||||
self.assertEqual(TimeSlot.objects.get(pk=slot.pk).type.name,'Session')
|
||||
self.assertEqual(TimeSlot.objects.get(pk=slot.pk).type_id,'regular')
|
||||
|
||||
url = urlreverse("ietf.meeting.ajax.timeslot_sloturl",
|
||||
kwargs=dict(num=meeting.number, slotid=slot.pk))
|
||||
|
@ -479,10 +479,12 @@ class TimeSlotEditingApiTests(TestCase):
|
|||
self.client.login(username="plain", password="plain+password")
|
||||
r = self.client.post(url, modify_post_data)
|
||||
self.assertEqual(r.status_code, 403)
|
||||
self.assertEqual(TimeSlot.objects.get(pk=slot.pk).type.name,'Session')
|
||||
slot.refresh_from_db()
|
||||
self.assertEqual(slot.type_id, 'regular')
|
||||
|
||||
# Successful change of purpose
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
r = self.client.post(url, modify_post_data)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(TimeSlot.objects.get(pk=slot.pk).type.name,'Plenary')
|
||||
slot.refresh_from_db()
|
||||
self.assertEqual(slot.type_id, 'plenary')
|
||||
|
|
|
@ -20,6 +20,7 @@ from ietf.group import colors
|
|||
from ietf.meeting.factories import SessionFactory
|
||||
from ietf.meeting.test_data import make_meeting_test_data
|
||||
from ietf.meeting.models import SchedTimeSessAssignment
|
||||
from ietf.name.models import SessionStatusName
|
||||
from ietf.utils.test_runner import set_coverage_checking
|
||||
from ietf.utils.pipe import pipe
|
||||
from ietf import settings
|
||||
|
@ -84,10 +85,10 @@ class ScheduleEditTests(StaticLiveServerTestCase):
|
|||
|
||||
def testUnschedule(self):
|
||||
|
||||
self.assertEqual(SchedTimeSessAssignment.objects.filter(session__meeting__number=72,session__group__acronym='mars',schedule__name='test-agenda').count(),1)
|
||||
self.assertEqual(SchedTimeSessAssignment.objects.filter(session__meeting__number=72,session__group__acronym='mars',schedule__name='test-schedule').count(),1)
|
||||
|
||||
self.login()
|
||||
url = self.absreverse('ietf.meeting.views.edit_agenda',kwargs=dict(num='72',name='test-agenda',owner='plain@example.com'))
|
||||
url = self.absreverse('ietf.meeting.views.edit_schedule',kwargs=dict(num='72',name='test-schedule',owner='plain@example.com'))
|
||||
self.driver.get(url)
|
||||
|
||||
q = PyQuery(self.driver.page_source)
|
||||
|
@ -101,7 +102,7 @@ class ScheduleEditTests(StaticLiveServerTestCase):
|
|||
self.assertTrue(len(q('#sortable-list #session_1'))>0)
|
||||
|
||||
time.sleep(0.1) # The API that modifies the database runs async
|
||||
self.assertEqual(SchedTimeSessAssignment.objects.filter(session__meeting__number=72,session__group__acronym='mars',schedule__name='test-agenda').count(),0)
|
||||
self.assertEqual(SchedTimeSessAssignment.objects.filter(session__meeting__number=72,session__group__acronym='mars',schedule__name='test-schedule').count(),0)
|
||||
|
||||
@skipIf(skip_selenium, skip_message)
|
||||
class SlideReorderTests(StaticLiveServerTestCase):
|
||||
|
@ -118,7 +119,11 @@ class SlideReorderTests(StaticLiveServerTestCase):
|
|||
def setUp(self):
|
||||
self.driver = webdriver.PhantomJS(port=0, service_log_path=settings.TEST_GHOSTDRIVER_LOG_PATH)
|
||||
self.driver.set_window_size(1024,768)
|
||||
self.session = SessionFactory(meeting__type_id='ietf')
|
||||
# this is a temporary fix - we should have these name in the
|
||||
# database already at this point
|
||||
SessionStatusName.objects.get_or_create(slug='schedw')
|
||||
SessionStatusName.objects.get_or_create(slug='sched')
|
||||
self.session = SessionFactory(meeting__type_id='ietf', status_id='sched')
|
||||
self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='one'),order=1)
|
||||
self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='two'),order=2)
|
||||
self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='three'),order=3)
|
||||
|
@ -173,5 +178,5 @@ class SlideReorderTests(StaticLiveServerTestCase):
|
|||
# condition_data()
|
||||
#
|
||||
# def testOpenSchedule(self):
|
||||
# url = urlreverse('ietf.meeting.views.edit_agenda', kwargs=dict(num='72',name='test-agenda'))
|
||||
# url = urlreverse('ietf.meeting.views.edit_schedule', kwargs=dict(num='72',name='test-schedule'))
|
||||
# r = self.client.get(url)
|
||||
|
|
|
@ -29,13 +29,16 @@ import debug # pyflakes:ignore
|
|||
|
||||
from ietf.doc.models import Document
|
||||
from ietf.group.models import Group, Role
|
||||
from ietf.person.models import Person
|
||||
from ietf.meeting.helpers import can_approve_interim_request, can_view_interim_request
|
||||
from ietf.meeting.helpers import send_interim_approval_request
|
||||
from ietf.meeting.helpers import send_interim_cancellation_notice
|
||||
from ietf.meeting.helpers import send_interim_minutes_reminder, populate_important_dates, update_important_dates
|
||||
from ietf.meeting.models import Session, TimeSlot, Meeting, SchedTimeSessAssignment, Schedule, SessionPresentation, SlideSubmission
|
||||
from ietf.meeting.models import Session, TimeSlot, Meeting, SchedTimeSessAssignment, Schedule, SessionPresentation, SlideSubmission, SchedulingEvent
|
||||
from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting
|
||||
from ietf.meeting.utils import finalize, condition_slide_order
|
||||
from ietf.meeting.utils import add_event_info_to_session_qs
|
||||
from ietf.meeting.utils import current_session_status
|
||||
from ietf.name.models import SessionStatusName, ImportantDateName
|
||||
from ietf.utils.decorators import skip_coverage
|
||||
from ietf.utils.mail import outbox, empty_outbox
|
||||
|
@ -97,7 +100,7 @@ class MeetingTests(TestCase):
|
|||
def test_meeting_agenda(self):
|
||||
meeting = make_meeting_test_data()
|
||||
session = Session.objects.filter(meeting=meeting, group__acronym="mars").first()
|
||||
slot = TimeSlot.objects.get(sessionassignments__session=session,sessionassignments__schedule=meeting.agenda)
|
||||
slot = TimeSlot.objects.get(sessionassignments__session=session,sessionassignments__schedule=meeting.schedule)
|
||||
#
|
||||
self.write_materials_files(meeting, session)
|
||||
#
|
||||
|
@ -196,8 +199,11 @@ class MeetingTests(TestCase):
|
|||
self.assertContains(r, slot.location.name)
|
||||
|
||||
# week view with a cancelled session
|
||||
session.status_id='canceled'
|
||||
session.save()
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='canceled'),
|
||||
by=Person.objects.get(name='(System)')
|
||||
)
|
||||
r = self.client.get(urlreverse("ietf.meeting.views.week_view", kwargs=dict(num=meeting.number)))
|
||||
self.assertContains(r, 'CANCELLED')
|
||||
self.assertContains(r, session.group.acronym)
|
||||
|
@ -236,7 +242,7 @@ class MeetingTests(TestCase):
|
|||
self.assertTrue(all([x in unicontent(r) for x in ['mars','Test Room',]]))
|
||||
self.assertNotContains(r, 'IESG Breakfast')
|
||||
|
||||
url = urlreverse("ietf.meeting.views.agenda_by_type",kwargs=dict(num=meeting.number,type='session'))
|
||||
url = urlreverse("ietf.meeting.views.agenda_by_type",kwargs=dict(num=meeting.number,type='regular'))
|
||||
r = self.client.get(url)
|
||||
self.assertTrue(all([x in unicontent(r) for x in ['mars','Test Room']]))
|
||||
self.assertFalse(any([x in unicontent(r) for x in ['IESG Breakfast','Breakfast Room']]))
|
||||
|
@ -476,7 +482,7 @@ class MeetingTests(TestCase):
|
|||
# Create an extra session
|
||||
t2 = TimeSlotFactory.create(meeting=meeting, time=datetime.datetime.combine(meeting.date, datetime.time(11, 30)))
|
||||
s2 = SessionFactory.create(meeting=meeting, group=s1.group, add_to_schedule=False)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=t2, session=s2, schedule=meeting.agenda)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=t2, session=s2, schedule=meeting.schedule)
|
||||
#
|
||||
url = urlreverse('ietf.meeting.views.ical_agenda', kwargs={'num':meeting.number, 'acronym':s1.group.acronym, })
|
||||
r = self.client.get(url)
|
||||
|
@ -538,14 +544,14 @@ class MeetingTests(TestCase):
|
|||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_edit_agenda_properties(self):
|
||||
def test_edit_schedule_properties(self):
|
||||
self.client.login(username='secretary',password='secretary+password')
|
||||
url = urlreverse('ietf.meeting.views.edit_agenda_properties',kwargs={'owner':'does@notexist.example','name':'doesnotexist','num':00})
|
||||
url = urlreverse('ietf.meeting.views.edit_schedule_properties',kwargs={'owner':'does@notexist.example','name':'doesnotexist','num':00})
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code,404)
|
||||
self.client.logout()
|
||||
schedule = ScheduleFactory(meeting__type_id='ietf',visible=False,public=False)
|
||||
url = urlreverse('ietf.meeting.views.edit_agenda_properties',kwargs={'owner':schedule.owner.email(),'name':schedule.name,'num':schedule.meeting.number})
|
||||
url = urlreverse('ietf.meeting.views.edit_schedule_properties',kwargs={'owner':schedule.owner.email(),'name':schedule.name,'num':schedule.meeting.number})
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code,302)
|
||||
self.client.login(username='secretary',password='secretary+password')
|
||||
|
@ -903,27 +909,27 @@ class EditTests(TestCase):
|
|||
fg_group_colors[area_upper] = "#333"
|
||||
bg_group_colors[area_upper] = "#aaa"
|
||||
|
||||
def test_edit_agenda(self):
|
||||
def test_edit_schedule(self):
|
||||
meeting = make_meeting_test_data()
|
||||
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
r = self.client.get(urlreverse("ietf.meeting.views.edit_agenda", kwargs=dict(num=meeting.number)))
|
||||
r = self.client.get(urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number)))
|
||||
self.assertContains(r, "load_assignments")
|
||||
|
||||
def test_save_agenda_as_and_read_permissions(self):
|
||||
meeting = make_meeting_test_data()
|
||||
|
||||
# try to get non-existing agenda
|
||||
url = urlreverse("ietf.meeting.views.edit_agenda", kwargs=dict(num=meeting.number,
|
||||
owner=meeting.agenda.owner_email(),
|
||||
url = urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number,
|
||||
owner=meeting.schedule.owner_email(),
|
||||
name="foo"))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 404)
|
||||
|
||||
# save as new name (requires valid existing agenda)
|
||||
url = urlreverse("ietf.meeting.views.edit_agenda", kwargs=dict(num=meeting.number,
|
||||
owner=meeting.agenda.owner_email(),
|
||||
name=meeting.agenda.name))
|
||||
url = urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number,
|
||||
owner=meeting.schedule.owner_email(),
|
||||
name=meeting.schedule.name))
|
||||
self.client.login(username="ad", password="ad+password")
|
||||
r = self.client.post(url, {
|
||||
'savename': "foo",
|
||||
|
@ -935,7 +941,7 @@ class EditTests(TestCase):
|
|||
|
||||
# get
|
||||
schedule = meeting.get_schedule_by_name("foo")
|
||||
url = urlreverse("ietf.meeting.views.edit_agenda", kwargs=dict(num=meeting.number,
|
||||
url = urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number,
|
||||
owner=schedule.owner_email(),
|
||||
name="foo"))
|
||||
r = self.client.get(url)
|
||||
|
@ -968,9 +974,9 @@ class EditTests(TestCase):
|
|||
meeting = make_meeting_test_data()
|
||||
|
||||
# save as new name (requires valid existing agenda)
|
||||
url = urlreverse("ietf.meeting.views.edit_agenda", kwargs=dict(num=meeting.number,
|
||||
owner=meeting.agenda.owner_email(),
|
||||
name=meeting.agenda.name))
|
||||
url = urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number,
|
||||
owner=meeting.schedule.owner_email(),
|
||||
name=meeting.schedule.name))
|
||||
self.client.login(username="ad", password="ad+password")
|
||||
r = self.client.post(url, {
|
||||
'savename': "/no/this/should/not/work/it/is/too/long",
|
||||
|
@ -1019,12 +1025,12 @@ class EditTests(TestCase):
|
|||
def test_slot_to_the_right(self):
|
||||
meeting = make_meeting_test_data()
|
||||
session = Session.objects.filter(meeting=meeting, group__acronym="mars").first()
|
||||
mars_scheduled = session.timeslotassignments.get(schedule__name='test-agenda')
|
||||
mars_slot = TimeSlot.objects.get(sessionassignments__session=session,sessionassignments__schedule__name='test-agenda')
|
||||
mars_scheduled = session.timeslotassignments.get(schedule__name='test-schedule')
|
||||
mars_slot = TimeSlot.objects.get(sessionassignments__session=session,sessionassignments__schedule__name='test-schedule')
|
||||
mars_ends = mars_slot.time + mars_slot.duration
|
||||
|
||||
session = Session.objects.filter(meeting=meeting, group__acronym="ames").first()
|
||||
ames_slot_qs = TimeSlot.objects.filter(sessionassignments__session=session,sessionassignments__schedule__name='test-agenda')
|
||||
ames_slot_qs = TimeSlot.objects.filter(sessionassignments__session=session,sessionassignments__schedule__name='test-schedule')
|
||||
|
||||
ames_slot_qs.update(time=mars_ends + datetime.timedelta(seconds=11 * 60))
|
||||
self.assertTrue(not mars_slot.slot_to_the_right)
|
||||
|
@ -1097,8 +1103,8 @@ class EditScheduleListTests(TestCase):
|
|||
self.mtg = MeetingFactory(type_id='ietf')
|
||||
ScheduleFactory(meeting=self.mtg,name='Empty-Schedule')
|
||||
|
||||
def test_list_agendas(self):
|
||||
url = urlreverse('ietf.meeting.views.list_agendas',kwargs={'num':self.mtg.number})
|
||||
def test_list_schedules(self):
|
||||
url = urlreverse('ietf.meeting.views.list_schedules',kwargs={'num':self.mtg.number})
|
||||
login_testing_unauthorized(self,"secretary",url)
|
||||
r = self.client.get(url)
|
||||
self.assertTrue(r.status_code, 200)
|
||||
|
@ -1106,8 +1112,8 @@ class EditScheduleListTests(TestCase):
|
|||
def test_delete_schedule(self):
|
||||
url = urlreverse('ietf.meeting.views.delete_schedule',
|
||||
kwargs={'num':self.mtg.number,
|
||||
'owner':self.mtg.agenda.owner.email_address(),
|
||||
'name':self.mtg.agenda.name,
|
||||
'owner':self.mtg.schedule.owner.email_address(),
|
||||
'name':self.mtg.schedule.name,
|
||||
})
|
||||
login_testing_unauthorized(self,"secretary",url)
|
||||
r = self.client.get(url)
|
||||
|
@ -1115,7 +1121,7 @@ class EditScheduleListTests(TestCase):
|
|||
r = self.client.post(url,{'save':1})
|
||||
self.assertTrue(r.status_code, 403)
|
||||
self.assertEqual(self.mtg.schedule_set.count(),2)
|
||||
self.mtg.agenda=None
|
||||
self.mtg.schedule=None
|
||||
self.mtg.save()
|
||||
r = self.client.get(url)
|
||||
self.assertTrue(r.status_code, 200)
|
||||
|
@ -1124,7 +1130,7 @@ class EditScheduleListTests(TestCase):
|
|||
self.assertEqual(self.mtg.schedule_set.count(),1)
|
||||
|
||||
def test_make_schedule_official(self):
|
||||
schedule = self.mtg.schedule_set.exclude(id=self.mtg.agenda.id).first()
|
||||
schedule = self.mtg.schedule_set.exclude(id=self.mtg.schedule.id).first()
|
||||
url = urlreverse('ietf.meeting.views.make_schedule_official',
|
||||
kwargs={'num':self.mtg.number,
|
||||
'owner':schedule.owner.email_address(),
|
||||
|
@ -1136,7 +1142,7 @@ class EditScheduleListTests(TestCase):
|
|||
r = self.client.post(url,{'save':1})
|
||||
self.assertTrue(r.status_code, 302)
|
||||
mtg = Meeting.objects.get(number=self.mtg.number)
|
||||
self.assertEqual(mtg.agenda,schedule)
|
||||
self.assertEqual(mtg.schedule,schedule)
|
||||
|
||||
# -------------------------------------------------
|
||||
# Interim Meeting Tests
|
||||
|
@ -1187,8 +1193,11 @@ class InterimTests(TestCase):
|
|||
url = urlreverse("ietf.meeting.views.interim_announce")
|
||||
meeting = Meeting.objects.filter(type='interim', session__group__acronym='mars').first()
|
||||
session = meeting.session_set.first()
|
||||
session.status = SessionStatusName.objects.get(slug='scheda')
|
||||
session.save()
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='scheda'),
|
||||
by=Person.objects.get(name='(System)')
|
||||
)
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
r = self.client.get(url)
|
||||
self.assertContains(r, meeting.number)
|
||||
|
@ -1207,12 +1216,12 @@ class InterimTests(TestCase):
|
|||
len_before = len(outbox)
|
||||
r = self.client.post(url)
|
||||
self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_announce'))
|
||||
self.assertEqual(meeting.session_set.first().status.slug,'sched')
|
||||
self.assertEqual(add_event_info_to_session_qs(meeting.session_set).first().current_status, 'sched')
|
||||
self.assertEqual(len(outbox), len_before)
|
||||
|
||||
def test_interim_send_announcement(self):
|
||||
make_meeting_test_data()
|
||||
meeting = Meeting.objects.filter(type='interim', session__status='apprw', session__group__acronym='mars').first()
|
||||
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting
|
||||
url = urlreverse("ietf.meeting.views.interim_send_announcement", kwargs={'number': meeting.number})
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
r = self.client.get(url)
|
||||
|
@ -1227,26 +1236,26 @@ class InterimTests(TestCase):
|
|||
|
||||
def test_interim_approve_by_ad(self):
|
||||
make_meeting_test_data()
|
||||
meeting = Meeting.objects.filter(type='interim', session__status='apprw', session__group__acronym='mars').first()
|
||||
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting
|
||||
url = urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number})
|
||||
length_before = len(outbox)
|
||||
login_testing_unauthorized(self, "ad", url)
|
||||
r = self.client.post(url, {'approve': 'approve'})
|
||||
self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_pending'))
|
||||
for session in meeting.session_set.all():
|
||||
self.assertEqual(session.status.slug, 'scheda')
|
||||
for session in add_event_info_to_session_qs(meeting.session_set.all()):
|
||||
self.assertEqual(session.current_status, 'scheda')
|
||||
self.assertEqual(len(outbox), length_before + 1)
|
||||
self.assertIn('ready for announcement', outbox[-1]['Subject'])
|
||||
|
||||
def test_interim_approve_by_secretariat(self):
|
||||
make_meeting_test_data()
|
||||
meeting = Meeting.objects.filter(type='interim', session__status='apprw', session__group__acronym='mars').first()
|
||||
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting
|
||||
url = urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number})
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
r = self.client.post(url, {'approve': 'approve'})
|
||||
self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_send_announcement', kwargs={'number': meeting.number}))
|
||||
for session in meeting.session_set.all():
|
||||
self.assertEqual(session.status.slug, 'scheda')
|
||||
for session in add_event_info_to_session_qs(meeting.session_set.all()):
|
||||
self.assertEqual(session.current_status, 'scheda')
|
||||
|
||||
def test_past(self):
|
||||
today = datetime.date.today()
|
||||
|
@ -1264,8 +1273,9 @@ class InterimTests(TestCase):
|
|||
make_meeting_test_data()
|
||||
url = urlreverse("ietf.meeting.views.upcoming")
|
||||
today = datetime.date.today()
|
||||
mars_interim = Meeting.objects.filter(date__gt=today, type='interim', session__group__acronym='mars', session__status='sched').first()
|
||||
ames_interim = Meeting.objects.filter(date__gt=today, type='interim', session__group__acronym='ames', session__status='canceled').first()
|
||||
add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first()
|
||||
mars_interim = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', meeting__date__gt=today, group__acronym='mars')).filter(current_status='sched').first().meeting
|
||||
ames_interim = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', meeting__date__gt=today, group__acronym='ames')).filter(current_status='canceled').first().meeting
|
||||
r = self.client.get(url)
|
||||
self.assertContains(r, mars_interim.number)
|
||||
self.assertContains(r, ames_interim.number)
|
||||
|
@ -1391,7 +1401,7 @@ class InterimTests(TestCase):
|
|||
session = meeting.session_set.first()
|
||||
self.assertEqual(session.remote_instructions,remote_instructions)
|
||||
self.assertEqual(session.agenda_note,agenda_note)
|
||||
self.assertEqual(session.status.slug,'scheda')
|
||||
self.assertEqual(current_session_status(session).slug,'scheda')
|
||||
timeslot = session.official_timeslotassignment().timeslot
|
||||
self.assertEqual(timeslot.time,dt)
|
||||
self.assertEqual(timeslot.duration,duration)
|
||||
|
@ -1635,7 +1645,7 @@ class InterimTests(TestCase):
|
|||
def test_interim_pending(self):
|
||||
make_meeting_test_data()
|
||||
url = urlreverse('ietf.meeting.views.interim_pending')
|
||||
count = Meeting.objects.filter(type='interim',session__status='apprw').distinct().count()
|
||||
count = len(set(s.meeting_id for s in add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim')).filter(current_status='apprw')))
|
||||
|
||||
# unpriviledged user
|
||||
login_testing_unauthorized(self,"plain",url)
|
||||
|
@ -1656,7 +1666,7 @@ class InterimTests(TestCase):
|
|||
# unprivileged user
|
||||
user = User.objects.get(username='plain')
|
||||
group = Group.objects.get(acronym='mars')
|
||||
meeting = Meeting.objects.filter(type='interim',session__status='apprw',session__group=group).first()
|
||||
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group=group)).filter(current_status='apprw').first().meeting
|
||||
self.assertFalse(can_approve_interim_request(meeting=meeting,user=user))
|
||||
# Secretariat
|
||||
user = User.objects.get(username='secretary')
|
||||
|
@ -1676,7 +1686,7 @@ class InterimTests(TestCase):
|
|||
# unprivileged user
|
||||
user = User.objects.get(username='plain')
|
||||
group = Group.objects.get(acronym='mars')
|
||||
meeting = Meeting.objects.filter(type='interim',session__status='apprw',session__group=group).first()
|
||||
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group=group)).filter(current_status='apprw').first().meeting
|
||||
self.assertFalse(can_view_interim_request(meeting=meeting,user=user))
|
||||
# Secretariat
|
||||
user = User.objects.get(username='secretary')
|
||||
|
@ -1696,7 +1706,7 @@ class InterimTests(TestCase):
|
|||
|
||||
def test_interim_request_details(self):
|
||||
make_meeting_test_data()
|
||||
meeting = Meeting.objects.filter(type='interim',session__status='apprw',session__group__acronym='mars').first()
|
||||
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting
|
||||
url = urlreverse('ietf.meeting.views.interim_request_details',kwargs={'number':meeting.number})
|
||||
login_testing_unauthorized(self,"secretary",url)
|
||||
r = self.client.get(url)
|
||||
|
@ -1726,17 +1736,17 @@ class InterimTests(TestCase):
|
|||
|
||||
def test_interim_request_disapprove(self):
|
||||
make_meeting_test_data()
|
||||
meeting = Meeting.objects.filter(type='interim',session__status='apprw',session__group__acronym='mars').first()
|
||||
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting
|
||||
url = urlreverse('ietf.meeting.views.interim_request_details',kwargs={'number':meeting.number})
|
||||
login_testing_unauthorized(self,"secretary",url)
|
||||
r = self.client.post(url,{'disapprove':'Disapprove'})
|
||||
self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_pending'))
|
||||
for session in meeting.session_set.all():
|
||||
self.assertEqual(session.status_id,'disappr')
|
||||
for session in add_event_info_to_session_qs(meeting.session_set.all()):
|
||||
self.assertEqual(session.current_status,'disappr')
|
||||
|
||||
def test_interim_request_cancel(self):
|
||||
make_meeting_test_data()
|
||||
meeting = Meeting.objects.filter(type='interim', session__status='apprw', session__group__acronym='mars').first()
|
||||
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting
|
||||
url = urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number})
|
||||
# ensure no cancel button for unauthorized user
|
||||
self.client.login(username="ameschairman", password="ameschairman+password")
|
||||
|
@ -1761,17 +1771,17 @@ class InterimTests(TestCase):
|
|||
length_before = len(outbox)
|
||||
r = self.client.post(url, {'comments': comments})
|
||||
self.assertRedirects(r, urlreverse('ietf.meeting.views.upcoming'))
|
||||
for session in meeting.session_set.all():
|
||||
self.assertEqual(session.status_id, 'canceledpa')
|
||||
for session in add_event_info_to_session_qs(meeting.session_set.all()):
|
||||
self.assertEqual(session.current_status,'canceledpa')
|
||||
self.assertEqual(session.agenda_note, comments)
|
||||
self.assertEqual(len(outbox), length_before) # no email notice
|
||||
# test cancelling after announcement
|
||||
meeting = Meeting.objects.filter(type='interim', session__status='sched', session__group__acronym='mars').first()
|
||||
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='sched').first().meeting
|
||||
url = urlreverse('ietf.meeting.views.interim_request_cancel', kwargs={'number': meeting.number})
|
||||
r = self.client.post(url, {'comments': comments})
|
||||
self.assertRedirects(r, urlreverse('ietf.meeting.views.upcoming'))
|
||||
for session in meeting.session_set.all():
|
||||
self.assertEqual(session.status_id, 'canceled')
|
||||
for session in add_event_info_to_session_qs(meeting.session_set.all()):
|
||||
self.assertEqual(session.current_status,'canceled')
|
||||
self.assertEqual(session.agenda_note, comments)
|
||||
self.assertEqual(len(outbox), length_before + 1)
|
||||
self.assertIn('Interim Meeting Cancelled', outbox[-1]['Subject'])
|
||||
|
@ -1779,7 +1789,7 @@ class InterimTests(TestCase):
|
|||
def test_interim_request_edit_no_notice(self):
|
||||
'''Edit a request. No notice should go out if it hasn't been announced yet'''
|
||||
make_meeting_test_data()
|
||||
meeting = Meeting.objects.filter(type='interim', session__status='apprw', session__group__acronym='mars').first()
|
||||
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting
|
||||
group = meeting.session_set.first().group
|
||||
url = urlreverse('ietf.meeting.views.interim_request_edit', kwargs={'number': meeting.number})
|
||||
# test unauthorized access
|
||||
|
@ -1817,7 +1827,7 @@ class InterimTests(TestCase):
|
|||
def test_interim_request_edit(self):
|
||||
'''Edit request. Send notice of change'''
|
||||
make_meeting_test_data()
|
||||
meeting = Meeting.objects.filter(type='interim', session__status='sched', session__group__acronym='mars').first()
|
||||
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='sched').first().meeting
|
||||
group = meeting.session_set.first().group
|
||||
url = urlreverse('ietf.meeting.views.interim_request_edit', kwargs={'number': meeting.number})
|
||||
# test unauthorized access
|
||||
|
@ -1863,7 +1873,7 @@ class InterimTests(TestCase):
|
|||
|
||||
def test_interim_request_details_permissions(self):
|
||||
make_meeting_test_data()
|
||||
meeting = Meeting.objects.filter(type='interim',session__status='apprw',session__group__acronym='mars').first()
|
||||
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting
|
||||
url = urlreverse('ietf.meeting.views.interim_request_details',kwargs={'number':meeting.number})
|
||||
|
||||
# unprivileged user
|
||||
|
@ -1873,7 +1883,7 @@ class InterimTests(TestCase):
|
|||
|
||||
def test_send_interim_approval_request(self):
|
||||
make_meeting_test_data()
|
||||
meeting = Meeting.objects.filter(type='interim',session__status='apprw',session__group__acronym='mars').first()
|
||||
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting
|
||||
length_before = len(outbox)
|
||||
send_interim_approval_request(meetings=[meeting])
|
||||
self.assertEqual(len(outbox),length_before+1)
|
||||
|
@ -1881,7 +1891,7 @@ class InterimTests(TestCase):
|
|||
|
||||
def test_send_interim_cancellation_notice(self):
|
||||
make_meeting_test_data()
|
||||
meeting = Meeting.objects.filter(type='interim',session__status='sched',session__group__acronym='mars').first()
|
||||
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='sched').first().meeting
|
||||
length_before = len(outbox)
|
||||
send_interim_cancellation_notice(meeting=meeting)
|
||||
self.assertEqual(len(outbox),length_before+1)
|
||||
|
@ -1907,7 +1917,7 @@ class InterimTests(TestCase):
|
|||
# Create an extra session
|
||||
t2 = TimeSlotFactory.create(meeting=meeting, time=datetime.datetime.combine(meeting.date, datetime.time(11, 30)))
|
||||
s2 = SessionFactory.create(meeting=meeting, group=s1.group, add_to_schedule=False)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=t2, session=s2, schedule=meeting.agenda)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=t2, session=s2, schedule=meeting.schedule)
|
||||
#
|
||||
url = urlreverse('ietf.meeting.views.ical_agenda', kwargs={'num':meeting.number, 'acronym':s1.group.acronym, })
|
||||
r = self.client.get(url)
|
||||
|
|
|
@ -26,8 +26,8 @@ safe_for_all_meeting_types = [
|
|||
|
||||
|
||||
type_ietf_only_patterns = [
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/edit$' % settings.URL_REGEXPS, views.edit_agenda),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/details$' % settings.URL_REGEXPS, views.edit_agenda_properties),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/edit$' % settings.URL_REGEXPS, views.edit_schedule),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/details$' % settings.URL_REGEXPS, views.edit_schedule_properties),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/delete$' % settings.URL_REGEXPS, views.delete_schedule),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/make_official$' % settings.URL_REGEXPS, views.make_schedule_official),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s(\.(?P<ext>.html))?/?$' % settings.URL_REGEXPS, views.agenda),
|
||||
|
@ -36,16 +36,16 @@ type_ietf_only_patterns = [
|
|||
url(r'^agenda/%(owner)s/%(schedule_name)s/by-room/?$' % settings.URL_REGEXPS, views.agenda_by_room),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/by-type/?$' % settings.URL_REGEXPS, views.agenda_by_type),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/by-type/(?P<type>[a-z]+)$' % settings.URL_REGEXPS, views.agenda_by_type),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/permissions$' % settings.URL_REGEXPS, ajax.agenda_permission_api),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/permissions$' % settings.URL_REGEXPS, ajax.schedule_permission_api),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/session/(?P<assignment_id>\d+).json$' % settings.URL_REGEXPS, ajax.assignment_json),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s/sessions.json$' % settings.URL_REGEXPS, ajax.assignments_json),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s.json$' % settings.URL_REGEXPS, ajax.agenda_infourl),
|
||||
url(r'^agenda/%(owner)s/%(schedule_name)s.json$' % settings.URL_REGEXPS, ajax.schedule_infourl),
|
||||
url(r'^agenda/by-room$', views.agenda_by_room),
|
||||
url(r'^agenda/by-type$', views.agenda_by_type),
|
||||
url(r'^agenda/by-type/(?P<type>[a-z]+)$', views.agenda_by_type),
|
||||
url(r'^agenda/by-type/(?P<type>[a-z]+)/ics$', views.agenda_by_type_ics),
|
||||
url(r'^agendas/list$', views.list_agendas),
|
||||
url(r'^agendas/edit$', RedirectView.as_view(pattern_name='ietf.meeting.views.list_agendas', permanent=True)),
|
||||
url(r'^agendas/list$', views.list_schedules),
|
||||
url(r'^agendas/edit$', RedirectView.as_view(pattern_name='ietf.meeting.views.list_schedules', permanent=True)),
|
||||
url(r'^timeslots/edit$', views.edit_timeslots),
|
||||
url(r'^timeslot/(?P<slot_id>\d+)/edittype$', views.edit_timeslot_type),
|
||||
url(r'^rooms$', ajax.timeslot_roomsurl),
|
||||
|
@ -53,8 +53,8 @@ type_ietf_only_patterns = [
|
|||
url(r'^timeslots$', ajax.timeslot_slotsurl),
|
||||
url(r'^timeslots.json$', ajax.timeslot_slotsurl),
|
||||
url(r'^timeslot/(?P<slotid>\d+).json$', ajax.timeslot_sloturl),
|
||||
url(r'^agendas$', ajax.agenda_infosurl),
|
||||
url(r'^agendas.json$', ajax.agenda_infosurl),
|
||||
url(r'^agendas$', ajax.schedule_infosurl),
|
||||
url(r'^agendas.json$', ajax.schedule_infosurl),
|
||||
url(r'^agenda/(?P<acronym>[-a-z0-9]+)-drafts.pdf$', views.session_draft_pdf),
|
||||
url(r'^agenda/(?P<acronym>[-a-z0-9]+)-drafts.tgz$', views.session_draft_tarfile),
|
||||
url(r'^sessions.json', ajax.sessions_json),
|
||||
|
@ -75,7 +75,7 @@ type_ietf_only_patterns_id_optional = [
|
|||
url(r'^agenda(?P<utc>-utc)?(?P<ext>.html)?/?$', views.agenda),
|
||||
url(r'^agenda(?P<ext>.txt)$', views.agenda),
|
||||
url(r'^agenda(?P<ext>.csv)$', views.agenda),
|
||||
url(r'^agenda/edit$', views.edit_agenda),
|
||||
url(r'^agenda/edit$', views.edit_schedule),
|
||||
url(r'^requests$', views.meeting_requests),
|
||||
url(r'^agenda/agenda\.ics$', views.ical_agenda),
|
||||
url(r'^agenda\.ics$', views.ical_agenda),
|
||||
|
|
|
@ -11,28 +11,51 @@ import six.moves.urllib.request
|
|||
from six.moves.urllib.error import HTTPError
|
||||
from django.conf import settings
|
||||
from django.template.loader import render_to_string
|
||||
from django.db.models.expressions import Subquery, OuterRef
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.dbtemplate.models import DBTemplate
|
||||
from ietf.meeting.models import Session, Meeting
|
||||
from ietf.meeting.models import Session, Meeting, SchedulingEvent, TimeSlot
|
||||
from ietf.group.models import Group
|
||||
from ietf.group.utils import can_manage_materials
|
||||
from ietf.name.models import SessionStatusName
|
||||
from ietf.person.models import Email
|
||||
from ietf.secr.proceedings.proc_utils import import_audio_files
|
||||
|
||||
def group_sessions(sessions):
|
||||
|
||||
def sort_key(session):
|
||||
official_sessions = session.timeslotassignments.filter(schedule=session.meeting.agenda)
|
||||
if official_sessions:
|
||||
return official_sessions.first().timeslot.time
|
||||
elif session.meeting.date:
|
||||
return datetime.datetime.combine(session.meeting.date,datetime.datetime.min.time())
|
||||
def session_time_for_sorting(session, use_meeting_date):
|
||||
official_timeslot = TimeSlot.objects.filter(sessionassignments__session=session, sessionassignments__schedule=session.meeting.schedule).first()
|
||||
if official_timeslot:
|
||||
return official_timeslot.time
|
||||
elif use_meeting_date and session.meeting.date:
|
||||
return datetime.datetime.combine(session.meeting.date, datetime.time.min)
|
||||
else:
|
||||
first_event = SchedulingEvent.objects.filter(session=session).order_by('time', 'id').first()
|
||||
if first_event:
|
||||
return first_event.time
|
||||
else:
|
||||
return session.requested
|
||||
return datetime.datetime.min
|
||||
|
||||
def session_requested_by(session):
|
||||
first_event = SchedulingEvent.objects.filter(session=session).order_by('time', 'id').first()
|
||||
if first_event:
|
||||
return first_event.by
|
||||
|
||||
return None
|
||||
|
||||
def current_session_status(session):
|
||||
last_event = SchedulingEvent.objects.filter(session=session).order_by('-time', '-id').first()
|
||||
if last_event:
|
||||
return last_event.status
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def group_sessions(sessions):
|
||||
status_names = {n.slug: n.name for n in SessionStatusName.objects.all()}
|
||||
for s in sessions:
|
||||
s.time=sort_key(s)
|
||||
s.time = session_time_for_sorting(s, use_meeting_date=True)
|
||||
s.current_status_name = status_names.get(s.current_status, s.current_status)
|
||||
|
||||
sessions = sorted(sessions,key=lambda s:s.time)
|
||||
|
||||
|
@ -68,29 +91,24 @@ def get_upcoming_manageable_sessions(user):
|
|||
# This notion of searching by end-of-meeting is also present in Document.future_presentations.
|
||||
# It would be nice to make it easier to use querysets to talk about meeting endings wthout a heuristic like this one
|
||||
|
||||
candidate_sessions = Session.objects.exclude(status__in=['canceled','disappr','notmeet','deleted']).filter(meeting__date__gte=datetime.date.today()-datetime.timedelta(days=15))
|
||||
refined_candidates = [ sess for sess in candidate_sessions if sess.meeting.end_date()>=datetime.date.today()]
|
||||
# We can in fact do that with something like
|
||||
# .filter(date__gte=today - F('days')), but unfortunately, it
|
||||
# doesn't work correctly with Django 1.11 and MySQL/SQLite
|
||||
|
||||
return [ sess for sess in refined_candidates if can_manage_materials(user, sess.group) ]
|
||||
today = datetime.date.today()
|
||||
|
||||
candidate_sessions = add_event_info_to_session_qs(
|
||||
Session.objects.filter(meeting__date__gte=today - datetime.timedelta(days=15))
|
||||
).exclude(
|
||||
current_status__in=['canceled', 'disappr', 'notmeet', 'deleted']
|
||||
).prefetch_related('meeting')
|
||||
|
||||
return [
|
||||
sess for sess in candidate_sessions if sess.meeting.end_date() >= today and can_manage_materials(user, sess.group)
|
||||
]
|
||||
|
||||
def sort_sessions(sessions):
|
||||
|
||||
# Python sorts are stable since version 2,2, so this series results in a list sorted first
|
||||
# by the meeting 'number', then by session's group acronym, then by scheduled time
|
||||
# (or the time of the session request if the session isn't scheduled).
|
||||
|
||||
def time_sort_key(session):
|
||||
official_sessions = session.timeslotassignments.filter(schedule=session.meeting.agenda)
|
||||
if official_sessions:
|
||||
return official_sessions.first().timeslot.time
|
||||
else:
|
||||
return session.requested
|
||||
|
||||
time_sorted = sorted(sessions,key=time_sort_key)
|
||||
acronym_sorted = sorted(time_sorted,key=lambda x: x.group.acronym)
|
||||
meeting_sorted = sorted(acronym_sorted,key=lambda x: x.meeting.number)
|
||||
|
||||
return meeting_sorted
|
||||
return sorted(sessions, key=lambda s: (s.meeting.number, s.group.acronym, session_time_for_sorting(s, use_meeting_date=False)))
|
||||
|
||||
def create_proceedings_templates(meeting):
|
||||
'''Create DBTemplates for meeting proceedings'''
|
||||
|
@ -182,8 +200,6 @@ def sort_accept_tuple(accept):
|
|||
return sorted(tup, key = lambda x: float(x[1]), reverse = True)
|
||||
return tup
|
||||
|
||||
|
||||
|
||||
def condition_slide_order(session):
|
||||
qs = session.sessionpresentation_set.filter(document__type_id='slides').order_by('order')
|
||||
order_list = qs.values_list('order',flat=True)
|
||||
|
@ -191,3 +207,100 @@ def condition_slide_order(session):
|
|||
for num, sp in enumerate(qs, start=1):
|
||||
sp.order=num
|
||||
sp.save()
|
||||
|
||||
def add_event_info_to_session_qs(qs, current_status=True, requested_by=False, requested_time=False):
|
||||
"""Take a session queryset and add attributes computed from the
|
||||
scheduling events. A queryset is returned and the added attributes
|
||||
can be further filtered on."""
|
||||
from django.db.models import TextField, Value
|
||||
from django.db.models.functions import Coalesce
|
||||
if current_status:
|
||||
qs = qs.annotate(
|
||||
# coalesce with '' to avoid nulls which give funny
|
||||
# results, e.g. .exclude(current_status='canceled') also
|
||||
# skips rows with null in them
|
||||
current_status=Coalesce(Subquery(SchedulingEvent.objects.filter(session=OuterRef('pk')).order_by('-time', '-id').values('status')[:1]), Value(''), output_field=TextField()),
|
||||
)
|
||||
|
||||
if requested_by:
|
||||
qs = qs.annotate(
|
||||
requested_by=Subquery(SchedulingEvent.objects.filter(session=OuterRef('pk')).order_by('time', 'id').values('by')[:1]),
|
||||
)
|
||||
|
||||
if requested_time:
|
||||
qs = qs.annotate(
|
||||
requested_time=Subquery(SchedulingEvent.objects.filter(session=OuterRef('pk')).order_by('time', 'id').values('time')[:1]),
|
||||
)
|
||||
|
||||
return qs
|
||||
|
||||
def only_sessions_that_can_meet(session_qs):
|
||||
qs = add_event_info_to_session_qs(session_qs).exclude(current_status__in=['notmeet', 'disappr', 'deleted', 'apprw'])
|
||||
|
||||
# Restrict graphical scheduling to meeting requests (Sessions) of type 'regular' for now
|
||||
qs = qs.filter(type__slug='regular')
|
||||
|
||||
return qs
|
||||
|
||||
def data_for_meetings_overview(meetings, interim_status=None):
|
||||
"""Return filtered meetings with sessions and group hierarchy (for the
|
||||
interim menu)."""
|
||||
|
||||
# extract sessions
|
||||
for m in meetings:
|
||||
m.sessions = []
|
||||
|
||||
sessions = add_event_info_to_session_qs(
|
||||
Session.objects.filter(meeting__in=meetings).order_by('meeting', 'pk')
|
||||
).select_related('group', 'group__parent')
|
||||
|
||||
meeting_dict = {m.pk: m for m in meetings}
|
||||
for s in sessions.iterator():
|
||||
meeting_dict[s.meeting_id].sessions.append(s)
|
||||
|
||||
# filter
|
||||
if interim_status == 'apprw':
|
||||
meetings = [
|
||||
m for m in meetings
|
||||
if not m.type_id == 'interim' or any(s.current_status == 'apprw' for s in m.sessions)
|
||||
]
|
||||
|
||||
elif interim_status == 'scheda':
|
||||
meetings = [
|
||||
m for m in meetings
|
||||
if not m.type_id == 'interim' or any(s.current_status == 'scheda' for s in m.sessions)
|
||||
]
|
||||
|
||||
else:
|
||||
meetings = [
|
||||
m for m in meetings
|
||||
if not m.type_id == 'interim' or not all(s.current_status in ['apprw', 'scheda', 'canceledpa'] for s in m.sessions)
|
||||
]
|
||||
|
||||
# group hierarchy
|
||||
ietf_group = Group.objects.get(acronym='ietf')
|
||||
|
||||
group_hierarchy = [ietf_group]
|
||||
|
||||
parents = {}
|
||||
for m in meetings:
|
||||
if m.type_id == 'interim' and m.sessions:
|
||||
for s in m.sessions:
|
||||
parent = parents.get(s.group.parent_id)
|
||||
if not parent:
|
||||
parent = s.group.parent
|
||||
parent.group_list = []
|
||||
group_hierarchy.append(parent)
|
||||
|
||||
parent.group_list.append(s.group)
|
||||
|
||||
for p in parents.values():
|
||||
p.group_list.sort(key=lambda g: g.acronym)
|
||||
|
||||
# set some useful attributes
|
||||
for m in meetings:
|
||||
m.end = m.date + datetime.timedelta(days=m.days)
|
||||
m.responsible_group = (m.sessions[0].group if m.sessions else None) if m.type_id == 'interim' else ietf_group
|
||||
m.interim_meeting_cancelled = m.type_id == 'interim' and all(s.current_status == 'canceled' for s in m.sessions)
|
||||
|
||||
return meetings, group_hierarchy
|
||||
|
|
|
@ -8,6 +8,7 @@ import csv
|
|||
import datetime
|
||||
import glob
|
||||
import io
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import pytz
|
||||
|
@ -49,34 +50,36 @@ from ietf.doc.fields import SearchableDocumentsField
|
|||
from ietf.doc.models import Document, State, DocEvent, NewRevisionDocEvent, DocAlias
|
||||
from ietf.group.models import Group
|
||||
from ietf.group.utils import can_manage_session_materials
|
||||
from ietf.person.models import Person
|
||||
from ietf.ietfauth.utils import role_required, has_role
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
from ietf.meeting.models import Meeting, Session, Schedule, FloorPlan, SessionPresentation, TimeSlot, SlideSubmission
|
||||
from ietf.meeting.models import Meeting, Session, Schedule, FloorPlan, SessionPresentation, TimeSlot, SlideSubmission, SessionStatusName, SchedulingEvent, SchedTimeSessAssignment
|
||||
from ietf.meeting.helpers import get_areas, get_person_by_email, get_schedule_by_name
|
||||
from ietf.meeting.helpers import build_all_agenda_slices, get_wg_name_list
|
||||
from ietf.meeting.helpers import get_all_assignments_from_schedule
|
||||
from ietf.meeting.helpers import get_modified_from_assignments
|
||||
from ietf.meeting.helpers import get_wg_list, find_ads_for_meeting
|
||||
from ietf.meeting.helpers import get_meeting, get_schedule, agenda_permissions, get_ietf_meeting
|
||||
from ietf.meeting.helpers import get_meeting, get_schedule, schedule_permissions, get_ietf_meeting
|
||||
from ietf.meeting.helpers import preprocess_assignments_for_agenda, read_agenda_file
|
||||
from ietf.meeting.helpers import convert_draft_to_pdf, get_earliest_session_date
|
||||
from ietf.meeting.helpers import can_view_interim_request, can_approve_interim_request
|
||||
from ietf.meeting.helpers import can_edit_interim_request
|
||||
from ietf.meeting.helpers import can_request_interim_meeting, get_announcement_initial
|
||||
from ietf.meeting.helpers import sessions_post_save, is_meeting_approved
|
||||
from ietf.meeting.helpers import sessions_post_save, is_interim_meeting_approved
|
||||
from ietf.meeting.helpers import send_interim_cancellation_notice
|
||||
from ietf.meeting.helpers import send_interim_approval_request
|
||||
from ietf.meeting.helpers import send_interim_announcement_request
|
||||
from ietf.meeting.utils import finalize, sort_accept_tuple, condition_slide_order
|
||||
from ietf.message.utils import infer_message
|
||||
from ietf.meeting.utils import ( add_event_info_to_session_qs, condition_slide_order,
|
||||
current_session_status, data_for_meetings_overview, finalize, infer_message,
|
||||
session_requested_by, session_time_for_sorting, sort_accept_tuple, )
|
||||
from ietf.secr.proceedings.utils import handle_upload_file
|
||||
from ietf.secr.proceedings.proc_utils import (get_progress_stats, post_process, import_audio_files,
|
||||
create_recording)
|
||||
from ietf.secr.proceedings.proc_utils import (get_progress_stats, post_process,
|
||||
import_audio_files, create_recording)
|
||||
from ietf.utils.decorators import require_api_key
|
||||
from ietf.utils.log import assertion
|
||||
from ietf.utils.mail import send_mail_message, send_mail_text
|
||||
from ietf.utils.pipe import pipe
|
||||
from ietf.utils.pdf import pdf_pages
|
||||
from ietf.utils.pipe import pipe
|
||||
from ietf.utils.text import xslugify
|
||||
from ietf.utils.validators import get_mime_type
|
||||
|
||||
|
@ -84,7 +87,7 @@ from .forms import (InterimMeetingModelForm, InterimAnnounceForm, InterimSession
|
|||
InterimCancelForm, InterimSessionInlineFormSet, FileUploadForm, RequestMinutesForm,)
|
||||
|
||||
|
||||
def get_menu_entries(request):
|
||||
def get_interim_menu_entries(request):
|
||||
'''Setup menu entries for interim meeting view tabs'''
|
||||
entries = []
|
||||
if has_role(request.user, ('Area Director','Secretariat','IRTF Chair','WG Chair', 'RG Chair')):
|
||||
|
@ -128,22 +131,21 @@ def materials(request, num=None):
|
|||
|
||||
past_cutoff_date = datetime.date.today() > meeting.get_submission_correction_date()
|
||||
|
||||
#sessions = Session.objects.filter(meeting__number=meeting.number, timeslot__isnull=False)
|
||||
schedule = get_schedule(meeting, None)
|
||||
sessions = ( Session.objects
|
||||
.filter(meeting__number=meeting.number, timeslotassignments__schedule=schedule)
|
||||
.select_related('meeting__agenda','status','group__state','group__parent', )
|
||||
)
|
||||
for session in sessions:
|
||||
session.past_cutoff_date = past_cutoff_date
|
||||
|
||||
sessions = add_event_info_to_session_qs(Session.objects.filter(
|
||||
meeting__number=meeting.number,
|
||||
timeslotassignments__schedule=schedule
|
||||
).distinct().select_related('meeting__schedule', 'group__state', 'group__parent'))
|
||||
|
||||
plenaries = sessions.filter(name__icontains='plenary')
|
||||
ietf = sessions.filter(group__parent__type__slug = 'area').exclude(group__acronym='edu')
|
||||
irtf = sessions.filter(group__parent__acronym = 'irtf')
|
||||
training = sessions.filter(group__acronym__in=['edu','iaoc'], type_id__in=['session', 'other', ])
|
||||
training = sessions.filter(group__acronym__in=['edu','iaoc'], type_id__in=['regular', 'other', ])
|
||||
iab = sessions.filter(group__parent__acronym = 'iab')
|
||||
other = sessions.filter(type_id__in=['session'], group__type__features__has_meetings = True)
|
||||
for ss in [plenaries, ietf, irtf, training, iab]:
|
||||
other = other.exclude(pk__in=[s.pk for s in ss])
|
||||
|
||||
session_pks = [s.pk for ss in [plenaries, ietf, irtf, training, iab] for s in ss]
|
||||
other = sessions.filter(type__in=['regular'], group__type__features__has_meetings=True).exclude(pk__in=session_pks)
|
||||
|
||||
for topic in [plenaries, ietf, training, irtf, iab]:
|
||||
for event in topic:
|
||||
|
@ -151,6 +153,10 @@ def materials(request, num=None):
|
|||
for slide_event in event.all_meeting_slides(): date_list.append(slide_event.time)
|
||||
for agenda_event in event.all_meeting_agendas(): date_list.append(agenda_event.time)
|
||||
if date_list: setattr(event, 'last_update', sorted(date_list, reverse=True)[0])
|
||||
|
||||
for session_list in [plenaries, ietf, training, irtf, iab, other]:
|
||||
for session in session_list:
|
||||
session.past_cutoff_date = past_cutoff_date
|
||||
|
||||
return render(request, "meeting/materials.html", {
|
||||
'meeting': meeting,
|
||||
|
@ -243,7 +249,7 @@ class SaveAsForm(forms.Form):
|
|||
savename = forms.CharField(max_length=16)
|
||||
|
||||
@role_required('Area Director','Secretariat')
|
||||
def agenda_create(request, num=None, owner=None, name=None):
|
||||
def schedule_create(request, num=None, owner=None, name=None):
|
||||
meeting = get_meeting(num)
|
||||
person = get_person_by_email(owner)
|
||||
schedule = get_schedule_by_name(meeting, person, name)
|
||||
|
@ -251,29 +257,29 @@ def agenda_create(request, num=None, owner=None, name=None):
|
|||
if schedule is None:
|
||||
# here we have to return some ajax to display an error.
|
||||
messages.error("Error: No meeting information for meeting %s owner %s schedule %s available" % (num, owner, name)) # pylint: disable=no-value-for-parameter
|
||||
return redirect(edit_agenda, num=num, owner=owner, name=name)
|
||||
return redirect(edit_schedule, num=num, owner=owner, name=name)
|
||||
|
||||
# authorization was enforced by the @group_require decorator above.
|
||||
|
||||
saveasform = SaveAsForm(request.POST)
|
||||
if not saveasform.is_valid():
|
||||
messages.info(request, "This name is not valid. Please choose another one.")
|
||||
return redirect(edit_agenda, num=num, owner=owner, name=name)
|
||||
return redirect(edit_schedule, num=num, owner=owner, name=name)
|
||||
|
||||
savedname = saveasform.cleaned_data['savename']
|
||||
|
||||
if not ascii_alphanumeric(savedname):
|
||||
messages.info(request, "This name contains illegal characters. Please choose another one.")
|
||||
return redirect(edit_agenda, num=num, owner=owner, name=name)
|
||||
return redirect(edit_schedule, num=num, owner=owner, name=name)
|
||||
|
||||
# create the new schedule, and copy the assignments
|
||||
try:
|
||||
sched = meeting.schedule_set.get(name=savedname, owner=request.user.person)
|
||||
if sched:
|
||||
return redirect(edit_agenda, num=meeting.number, owner=sched.owner_email(), name=sched.name)
|
||||
return redirect(edit_schedule, num=meeting.number, owner=sched.owner_email(), name=sched.name)
|
||||
else:
|
||||
messages.info(request, "Agenda creation failed. Please try again.")
|
||||
return redirect(edit_agenda, num=num, owner=owner, name=name)
|
||||
messages.info(request, "Schedule creation failed. Please try again.")
|
||||
return redirect(edit_schedule, num=num, owner=owner, name=name)
|
||||
|
||||
except Schedule.DoesNotExist:
|
||||
pass
|
||||
|
@ -312,7 +318,7 @@ def agenda_create(request, num=None, owner=None, name=None):
|
|||
|
||||
|
||||
# now redirect to this new schedule.
|
||||
return redirect(edit_agenda, meeting.number, newschedule.owner_email(), newschedule.name)
|
||||
return redirect(edit_schedule, meeting.number, newschedule.owner_email(), newschedule.name)
|
||||
|
||||
|
||||
@role_required('Secretariat')
|
||||
|
@ -343,16 +349,16 @@ def edit_timeslots(request, num=None):
|
|||
#@role_required('Area Director','Secretariat')
|
||||
# disable the above security for now, check it below.
|
||||
@ensure_csrf_cookie
|
||||
def edit_agenda(request, num=None, owner=None, name=None):
|
||||
def edit_schedule(request, num=None, owner=None, name=None):
|
||||
|
||||
if request.method == 'POST':
|
||||
return agenda_create(request, num, owner, name)
|
||||
return schedule_create(request, num, owner, name)
|
||||
|
||||
user = request.user
|
||||
meeting = get_meeting(num)
|
||||
person = get_person_by_email(owner)
|
||||
if name is None:
|
||||
schedule = meeting.agenda
|
||||
schedule = meeting.schedule
|
||||
else:
|
||||
schedule = get_schedule_by_name(meeting, person, name)
|
||||
if schedule is None:
|
||||
|
@ -361,15 +367,15 @@ def edit_agenda(request, num=None, owner=None, name=None):
|
|||
meeting_base_url = request.build_absolute_uri(meeting.base_url())
|
||||
site_base_url = request.build_absolute_uri('/')[:-1] # skip the trailing slash
|
||||
|
||||
rooms = meeting.room_set.filter(session_types__slug='session').distinct().order_by("capacity")
|
||||
rooms = meeting.room_set.filter(session_types__slug='regular').distinct().order_by("capacity")
|
||||
saveas = SaveAsForm()
|
||||
saveasurl=reverse(edit_agenda,
|
||||
saveasurl=reverse(edit_schedule,
|
||||
args=[meeting.number, schedule.owner_email(), schedule.name])
|
||||
|
||||
can_see, can_edit,secretariat = agenda_permissions(meeting, schedule, user)
|
||||
can_see, can_edit,secretariat = schedule_permissions(meeting, schedule, user)
|
||||
|
||||
if not can_see:
|
||||
return render(request, "meeting/private_agenda.html",
|
||||
return render(request, "meeting/private_schedule.html",
|
||||
{"schedule":schedule,
|
||||
"meeting": meeting,
|
||||
"meeting_base_url":meeting_base_url,
|
||||
|
@ -412,32 +418,32 @@ def edit_agenda(request, num=None, owner=None, name=None):
|
|||
})
|
||||
|
||||
##############################################################################
|
||||
# show the properties associated with an agenda (visible, public)
|
||||
# show the properties associated with a schedule (visible, public)
|
||||
#
|
||||
AgendaPropertiesForm = modelform_factory(Schedule, fields=('name','visible', 'public'))
|
||||
SchedulePropertiesForm = modelform_factory(Schedule, fields=('name','visible', 'public'))
|
||||
|
||||
# The meeing urls.py won't allow empy num, owmer, or name values
|
||||
|
||||
@role_required('Area Director','Secretariat')
|
||||
def edit_agenda_properties(request, num=None, owner=None, name=None):
|
||||
def edit_schedule_properties(request, num=None, owner=None, name=None):
|
||||
meeting = get_meeting(num)
|
||||
person = get_person_by_email(owner)
|
||||
schedule = get_schedule_by_name(meeting, person, name)
|
||||
if schedule is None:
|
||||
raise Http404("No meeting information for meeting %s owner %s schedule %s available" % (num, owner, name))
|
||||
|
||||
cansee, canedit, secretariat = agenda_permissions(meeting, schedule, request.user)
|
||||
cansee, canedit, secretariat = schedule_permissions(meeting, schedule, request.user)
|
||||
|
||||
if not (canedit or has_role(request.user,'Secretariat')):
|
||||
return HttpResponseForbidden("You may not edit this agenda")
|
||||
return HttpResponseForbidden("You may not edit this schedule")
|
||||
else:
|
||||
if request.method == 'POST':
|
||||
form = AgendaPropertiesForm(instance=schedule,data=request.POST)
|
||||
form = SchedulePropertiesForm(instance=schedule,data=request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return HttpResponseRedirect(reverse('ietf.meeting.views.list_agendas',kwargs={'num': num}))
|
||||
return HttpResponseRedirect(reverse('ietf.meeting.views.list_schedules',kwargs={'num': num}))
|
||||
else:
|
||||
form = AgendaPropertiesForm(instance=schedule)
|
||||
form = SchedulePropertiesForm(instance=schedule)
|
||||
return render(request, "meeting/properties_edit.html",
|
||||
{"schedule":schedule,
|
||||
"form":form,
|
||||
|
@ -445,11 +451,11 @@ def edit_agenda_properties(request, num=None, owner=None, name=None):
|
|||
})
|
||||
|
||||
##############################################################################
|
||||
# show list of agendas.
|
||||
# show list of schedules.
|
||||
#
|
||||
|
||||
@role_required('Area Director','Secretariat')
|
||||
def list_agendas(request, num=None ):
|
||||
def list_schedules(request, num=None ):
|
||||
|
||||
meeting = get_meeting(num)
|
||||
user = request.user
|
||||
|
@ -462,7 +468,7 @@ def list_agendas(request, num=None ):
|
|||
|
||||
schedules = sorted(list(schedules),key=lambda x:not x.is_official)
|
||||
|
||||
return render(request, "meeting/agenda_list.html",
|
||||
return render(request, "meeting/schedule_list.html",
|
||||
{"meeting": meeting,
|
||||
"schedules": schedules,
|
||||
})
|
||||
|
@ -483,7 +489,7 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""
|
|||
assert num is None or num.isdigit()
|
||||
|
||||
meeting = get_ietf_meeting(num)
|
||||
if not meeting or (meeting.number.isdigit() and int(meeting.number) <= 64 and (not meeting.agenda or not meeting.agenda.assignments.exists())):
|
||||
if not meeting or (meeting.number.isdigit() and int(meeting.number) <= 64 and (not meeting.schedule or not meeting.schedule.assignments.exists())):
|
||||
if ext == '.html' or (meeting and meeting.number.isdigit() and 0 < int(meeting.number) <= 64):
|
||||
return HttpResponseRedirect( 'https://www.ietf.org/proceedings/%s' % num )
|
||||
else:
|
||||
|
@ -604,7 +610,7 @@ def agenda_csv(schedule, filtered_assignments):
|
|||
row.append(item.session.pk)
|
||||
row.append(agenda_field(item))
|
||||
row.append(slides_field(item))
|
||||
elif item.timeslot.type_id == "session":
|
||||
elif item.timeslot.type_id == 'regular':
|
||||
row.append(item.timeslot.name)
|
||||
row.append(item.timeslot.location.name if item.timeslot.location else "")
|
||||
row.append(item.session.historic_group.historic_parent.acronym.upper() if item.session.historic_group.historic_parent else "")
|
||||
|
@ -818,7 +824,7 @@ def week_view(request, num=None, name=None, owner=None):
|
|||
if a.session and a.session.agenda():
|
||||
item["agenda"] = a.session.agenda().href()
|
||||
|
||||
if a.session.status_id=='canceled':
|
||||
if a.session.current_status == 'canceled':
|
||||
item["name"] = "CANCELLED - " + item["name"]
|
||||
|
||||
items.append(item)
|
||||
|
@ -886,6 +892,12 @@ def room_view(request, num=None, name=None, owner=None):
|
|||
template = "meeting/room-view.html"
|
||||
return render(request, template,{"meeting":meeting,"schedule":schedule,"unavailable":unavailable,"assignments":assignments,"rooms":rooms,"days":days})
|
||||
|
||||
def ical_session_status(session_with_current_status):
|
||||
if session_with_current_status == 'canceled':
|
||||
return "CANCELLED"
|
||||
else:
|
||||
return "CONFIRMED"
|
||||
|
||||
def ical_agenda(request, num=None, name=None, acronym=None, session_id=None):
|
||||
meeting = get_meeting(num, type_in=None)
|
||||
schedule = get_schedule(meeting, name)
|
||||
|
@ -932,6 +944,10 @@ def ical_agenda(request, num=None, name=None, acronym=None, session_id=None):
|
|||
elif session_id:
|
||||
assignments = [ a for a in assignments if a.session_id == int(session_id) ]
|
||||
|
||||
for a in assignments:
|
||||
if a.session:
|
||||
a.session.ical_status = ical_session_status(a.session)
|
||||
|
||||
return render(request, "meeting/agenda.ics", {
|
||||
"schedule": schedule,
|
||||
"assignments": assignments,
|
||||
|
@ -945,7 +961,7 @@ def json_agenda(request, num=None ):
|
|||
sessions = []
|
||||
locations = set()
|
||||
parent_acronyms = set()
|
||||
assignments = meeting.agenda.assignments.exclude(session__type__in=['lead','offagenda','break','reg'])
|
||||
assignments = meeting.schedule.assignments.exclude(session__type__in=['lead','offagenda','break','reg'])
|
||||
# Update the assignments with historic information, i.e., valid at the
|
||||
# time of the meeting
|
||||
assignments = assignments.prefetch_related(
|
||||
|
@ -1010,7 +1026,7 @@ def json_agenda(request, num=None ):
|
|||
rev_docevent = doc.latest_event(NewRevisionDocEvent,'new_revision')
|
||||
modified = max(modified, (rev_docevent and rev_docevent.time) or modified)
|
||||
sessdict['modified'] = modified
|
||||
sessdict['status'] = asgn.session.status_id
|
||||
sessdict['status'] = asgn.session.current_status
|
||||
sessions.append(sessdict)
|
||||
|
||||
rooms = []
|
||||
|
@ -1063,7 +1079,27 @@ def json_agenda(request, num=None ):
|
|||
|
||||
def meeting_requests(request, num=None):
|
||||
meeting = get_meeting(num)
|
||||
sessions = Session.objects.filter(meeting__number=meeting.number, type__slug='session', group__parent__isnull = False).exclude(requested_by=0).order_by("group__parent__acronym","status__slug","group__acronym").prefetch_related("group","group__ad_role__person","requested_by")
|
||||
sessions = add_event_info_to_session_qs(
|
||||
Session.objects.filter(
|
||||
meeting__number=meeting.number,
|
||||
type__slug='regular',
|
||||
group__parent__isnull=False
|
||||
),
|
||||
requested_by=True,
|
||||
).exclude(
|
||||
requested_by=0
|
||||
).order_by(
|
||||
"group__parent__acronym", "current_status", "group__acronym"
|
||||
).prefetch_related(
|
||||
"group","group__ad_role__person"
|
||||
)
|
||||
|
||||
status_names = {n.slug: n.name for n in SessionStatusName.objects.all()}
|
||||
session_requesters = {p.pk: p for p in Person.objects.filter(pk__in=[s.requested_by for s in sessions if s.requested_by is not None])}
|
||||
|
||||
for s in sessions:
|
||||
s.current_status_name = status_names.get(s.current_status, s.current_status)
|
||||
s.requested_by_person = session_requesters.get(s.requested_by)
|
||||
|
||||
groups_not_meeting = Group.objects.filter(state='Active',type__in=['wg','rg','ag','bof']).exclude(acronym__in = [session.group.acronym for session in sessions]).order_by("parent__acronym","acronym").prefetch_related("parent")
|
||||
|
||||
|
@ -1073,41 +1109,37 @@ def meeting_requests(request, num=None):
|
|||
|
||||
def get_sessions(num, acronym):
|
||||
meeting = get_meeting(num=num,type_in=None)
|
||||
sessions = Session.objects.filter(meeting=meeting,group__acronym=acronym,type__in=['session','plenary','other'])
|
||||
sessions = Session.objects.filter(meeting=meeting,group__acronym=acronym,type__in=['regular','plenary','other'])
|
||||
|
||||
if not sessions:
|
||||
sessions = Session.objects.filter(meeting=meeting,short=acronym,type__in=['session','plenary','other'])
|
||||
sessions = Session.objects.filter(meeting=meeting,short=acronym,type__in=['regular','plenary','other'])
|
||||
|
||||
def sort_key(session):
|
||||
official_sessions = session.timeslotassignments.filter(schedule=session.meeting.agenda)
|
||||
if official_sessions:
|
||||
return official_sessions.first().timeslot.time
|
||||
else:
|
||||
return session.requested
|
||||
sessions = add_event_info_to_session_qs(sessions)
|
||||
|
||||
return sorted(sessions,key=sort_key)
|
||||
return sorted(sessions, key=lambda s: session_time_for_sorting(s, use_meeting_date=False))
|
||||
|
||||
def session_details(request, num, acronym ):
|
||||
def session_details(request, num, acronym):
|
||||
meeting = get_meeting(num=num,type_in=None)
|
||||
sessions = get_sessions(num, acronym)
|
||||
|
||||
if not sessions:
|
||||
raise Http404
|
||||
|
||||
status_names = {n.slug: n.name for n in SessionStatusName.objects.all()}
|
||||
for session in sessions:
|
||||
|
||||
session.type_counter = Counter()
|
||||
ss = session.timeslotassignments.filter(schedule=meeting.agenda).order_by('timeslot__time')
|
||||
ss = session.timeslotassignments.filter(schedule=meeting.schedule).order_by('timeslot__time')
|
||||
if ss:
|
||||
session.time = ', '.join(x.timeslot.time.strftime("%A %b-%d-%Y %H%M") for x in ss)
|
||||
if session.status.slug == 'canceled':
|
||||
if session.current_status == 'canceled':
|
||||
session.time += " CANCELLED"
|
||||
elif session.meeting.type_id=='interim':
|
||||
session.time = session.meeting.date.strftime("%A %b-%d-%Y")
|
||||
if session.status.slug == 'canceled':
|
||||
if session.current_status == 'canceled':
|
||||
session.time += " CANCELLED"
|
||||
else:
|
||||
session.time = session.status.name
|
||||
session.time = status_names.get(session.current_status, session.current_status)
|
||||
|
||||
session.filtered_artifacts = list(session.sessionpresentation_set.filter(document__type__slug__in=['agenda','minutes','bluesheets']))
|
||||
session.filtered_artifacts.sort(key=lambda d:['agenda','minutes','bluesheets'].index(d.document.type.slug))
|
||||
|
@ -1118,12 +1150,12 @@ def session_details(request, num, acronym ):
|
|||
qs = [p for p in qs if p.document.get_state_slug(p.document.type_id)!='deleted']
|
||||
session.type_counter.update([p.document.type.slug for p in qs])
|
||||
|
||||
# we somewhat arbitrarily use the group of the last session wet get from
|
||||
# we somewhat arbitrarily use the group of the last session we get from
|
||||
# get_sessions() above when checking can_manage_session_materials()
|
||||
can_manage = can_manage_session_materials(request.user, session.group, session)
|
||||
|
||||
scheduled_sessions=[s for s in sessions if s.status_id=='sched']
|
||||
unscheduled_sessions = [s for s in sessions if s.status_id!='sched']
|
||||
scheduled_sessions = [s for s in sessions if s.current_status == 'sched']
|
||||
unscheduled_sessions = [s for s in sessions if s.current_status != 'sched']
|
||||
|
||||
pending_suggestions = None
|
||||
if request.user.is_authenticated:
|
||||
|
@ -1289,7 +1321,7 @@ def upload_session_minutes(request, session_id, num):
|
|||
|
||||
session_number = None
|
||||
sessions = get_sessions(session.meeting.number,session.group.acronym)
|
||||
show_apply_to_all_checkbox = len(sessions) > 1 if session.type_id == 'session' else False
|
||||
show_apply_to_all_checkbox = len(sessions) > 1 if session.type_id == 'regular' else False
|
||||
if len(sessions) > 1:
|
||||
session_number = 1 + sessions.index(session)
|
||||
|
||||
|
@ -1300,7 +1332,7 @@ def upload_session_minutes(request, session_id, num):
|
|||
if form.is_valid():
|
||||
file = request.FILES['file']
|
||||
_, ext = os.path.splitext(file.name)
|
||||
apply_to_all = session.type_id == 'session'
|
||||
apply_to_all = session.type_id == 'regular'
|
||||
if show_apply_to_all_checkbox:
|
||||
apply_to_all = form.cleaned_data['apply_to_all']
|
||||
if minutes_sp:
|
||||
|
@ -1389,7 +1421,7 @@ def upload_session_agenda(request, session_id, num):
|
|||
|
||||
session_number = None
|
||||
sessions = get_sessions(session.meeting.number,session.group.acronym)
|
||||
show_apply_to_all_checkbox = len(sessions) > 1 if session.type_id == 'session' else False
|
||||
show_apply_to_all_checkbox = len(sessions) > 1 if session.type_id == 'regular' else False
|
||||
if len(sessions) > 1:
|
||||
session_number = 1 + sessions.index(session)
|
||||
|
||||
|
@ -1400,7 +1432,7 @@ def upload_session_agenda(request, session_id, num):
|
|||
if form.is_valid():
|
||||
file = request.FILES['file']
|
||||
_, ext = os.path.splitext(file.name)
|
||||
apply_to_all = session.type_id == 'session'
|
||||
apply_to_all = session.type_id == 'regular'
|
||||
if show_apply_to_all_checkbox:
|
||||
apply_to_all = form.cleaned_data['apply_to_all']
|
||||
if agenda_sp:
|
||||
|
@ -1462,7 +1494,7 @@ def upload_session_agenda(request, session_id, num):
|
|||
doc.save_with_history([e])
|
||||
return redirect('ietf.meeting.views.session_details',num=num,acronym=session.group.acronym)
|
||||
else:
|
||||
form = UploadAgendaForm(show_apply_to_all_checkbox, initial={'apply_to_all':session.type_id=='session'})
|
||||
form = UploadAgendaForm(show_apply_to_all_checkbox, initial={'apply_to_all':session.type_id=='regular'})
|
||||
|
||||
return render(request, "meeting/upload_session_agenda.html",
|
||||
{'session': session,
|
||||
|
@ -1500,7 +1532,7 @@ def upload_session_slides(request, session_id, num, name):
|
|||
|
||||
session_number = None
|
||||
sessions = get_sessions(session.meeting.number,session.group.acronym)
|
||||
show_apply_to_all_checkbox = len(sessions) > 1 if session.type_id == 'session' else False
|
||||
show_apply_to_all_checkbox = len(sessions) > 1 if session.type_id == 'regular' else False
|
||||
if len(sessions) > 1:
|
||||
session_number = 1 + sessions.index(session)
|
||||
|
||||
|
@ -1517,7 +1549,7 @@ def upload_session_slides(request, session_id, num, name):
|
|||
if form.is_valid():
|
||||
file = request.FILES['file']
|
||||
_, ext = os.path.splitext(file.name)
|
||||
apply_to_all = session.type_id == 'session'
|
||||
apply_to_all = session.type_id == 'regular'
|
||||
if show_apply_to_all_checkbox:
|
||||
apply_to_all = form.cleaned_data['apply_to_all']
|
||||
if slides_sp:
|
||||
|
@ -1594,7 +1626,7 @@ def propose_session_slides(request, session_id, num):
|
|||
|
||||
session_number = None
|
||||
sessions = get_sessions(session.meeting.number,session.group.acronym)
|
||||
show_apply_to_all_checkbox = len(sessions) > 1 if session.type_id == 'session' else False
|
||||
show_apply_to_all_checkbox = len(sessions) > 1 if session.type_id == 'regular' else False
|
||||
if len(sessions) > 1:
|
||||
session_number = 1 + sessions.index(session)
|
||||
|
||||
|
@ -1604,7 +1636,7 @@ def propose_session_slides(request, session_id, num):
|
|||
if form.is_valid():
|
||||
file = request.FILES['file']
|
||||
_, ext = os.path.splitext(file.name)
|
||||
apply_to_all = session.type_id == 'session'
|
||||
apply_to_all = session.type_id == 'regular'
|
||||
if show_apply_to_all_checkbox:
|
||||
apply_to_all = form.cleaned_data['apply_to_all']
|
||||
title = form.cleaned_data['title']
|
||||
|
@ -1795,9 +1827,9 @@ def make_schedule_official(request, num, owner, name):
|
|||
schedule.public = True
|
||||
schedule.visible = True
|
||||
schedule.save()
|
||||
meeting.agenda = schedule
|
||||
meeting.schedule = schedule
|
||||
meeting.save()
|
||||
return HttpResponseRedirect(reverse('ietf.meeting.views.list_agendas',kwargs={'num':num}))
|
||||
return HttpResponseRedirect(reverse('ietf.meeting.views.list_schedules',kwargs={'num':num}))
|
||||
|
||||
if not schedule.public:
|
||||
messages.warning(request,"This schedule will be made public as it is made official.")
|
||||
|
@ -1822,15 +1854,15 @@ def delete_schedule(request, num, owner, name):
|
|||
if schedule.name=='Empty-Schedule':
|
||||
return HttpResponseForbidden('You may not delete the default empty schedule')
|
||||
|
||||
if schedule == meeting.agenda:
|
||||
return HttpResponseForbidden('You may not delete the official agenda for %s'%meeting)
|
||||
if schedule == meeting.schedule:
|
||||
return HttpResponseForbidden('You may not delete the official schedule for %s'%meeting)
|
||||
|
||||
if not ( has_role(request.user, 'Secretariat') or person.user == request.user ):
|
||||
return HttpResponseForbidden("You may not delete other user's schedules")
|
||||
|
||||
if request.method == 'POST':
|
||||
schedule.delete()
|
||||
return HttpResponseRedirect(reverse('ietf.meeting.views.list_agendas',kwargs={'num':num}))
|
||||
return HttpResponseRedirect(reverse('ietf.meeting.views.list_schedules',kwargs={'num':num}))
|
||||
|
||||
return render(request, "meeting/delete_schedule.html",
|
||||
{ 'schedule' : schedule,
|
||||
|
@ -1881,8 +1913,8 @@ def ajax_get_utc(request):
|
|||
@role_required('Secretariat',)
|
||||
def interim_announce(request):
|
||||
'''View which shows interim meeting requests awaiting announcement'''
|
||||
meetings = Meeting.objects.filter(type='interim', session__status='scheda').distinct()
|
||||
menu_entries = get_menu_entries(request)
|
||||
meetings, _ = data_for_meetings_overview(Meeting.objects.filter(type='interim').order_by('date'), interim_status='scheda')
|
||||
menu_entries = get_interim_menu_entries(request)
|
||||
selected_menu_entry = 'announce'
|
||||
|
||||
return render(request, "meeting/interim_announce.html", {
|
||||
|
@ -1903,7 +1935,12 @@ def interim_send_announcement(request, number):
|
|||
if form.is_valid():
|
||||
message = form.save(user=request.user)
|
||||
message.related_groups.add(group)
|
||||
meeting.session_set.update(status_id='sched')
|
||||
for session in meeting.session_set.all():
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='sched'),
|
||||
by=request.user.person,
|
||||
)
|
||||
send_mail_message(request, message)
|
||||
messages.success(request, 'Interim meeting announcement sent')
|
||||
return redirect(interim_announce)
|
||||
|
@ -1923,7 +1960,12 @@ def interim_skip_announcement(request, number):
|
|||
meeting = get_object_or_404(Meeting, number=number)
|
||||
|
||||
if request.method == 'POST':
|
||||
meeting.session_set.update(status_id='sched')
|
||||
for session in meeting.session_set.all():
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='sched'),
|
||||
by=request.user.person,
|
||||
)
|
||||
messages.success(request, 'Interim meeting scheduled. No announcement sent.')
|
||||
return redirect(interim_announce)
|
||||
|
||||
|
@ -1934,12 +1976,12 @@ def interim_skip_announcement(request, number):
|
|||
@role_required('Area Director', 'Secretariat', 'IRTF Chair', 'WG Chair', 'RG Chair')
|
||||
def interim_pending(request):
|
||||
'''View which shows interim meeting requests pending approval'''
|
||||
meetings = Meeting.objects.filter(type='interim', session__status='apprw').distinct().order_by('date')
|
||||
menu_entries = get_menu_entries(request)
|
||||
meetings, group_parents = data_for_meetings_overview(Meeting.objects.filter(type='interim').order_by('date'), interim_status='apprw')
|
||||
|
||||
menu_entries = get_interim_menu_entries(request)
|
||||
selected_menu_entry = 'pending'
|
||||
|
||||
meetings = [m for m in meetings if can_view_interim_request(
|
||||
m, request.user)]
|
||||
meetings = [m for m in meetings if can_view_interim_request(m, request.user)]
|
||||
for meeting in meetings:
|
||||
if can_approve_interim_request(meeting, request.user):
|
||||
meeting.can_approve = True
|
||||
|
@ -1982,7 +2024,7 @@ def interim_request(request):
|
|||
formset = SessionFormset(instance=meeting, data=request.POST)
|
||||
formset.is_valid()
|
||||
formset.save()
|
||||
sessions_post_save(formset)
|
||||
sessions_post_save(request, formset)
|
||||
|
||||
if not (is_approved or is_virtual):
|
||||
send_interim_approval_request(meetings=[meeting])
|
||||
|
@ -2013,7 +2055,7 @@ def interim_request(request):
|
|||
session.meeting = meeting
|
||||
session.save()
|
||||
series.append(meeting)
|
||||
sessions_post_save([session_form])
|
||||
sessions_post_save(request, [session_form])
|
||||
|
||||
if not (is_approved or is_virtual):
|
||||
send_interim_approval_request(meetings=series)
|
||||
|
@ -2038,7 +2080,9 @@ def interim_request(request):
|
|||
def interim_request_cancel(request, number):
|
||||
'''View for cancelling an interim meeting request'''
|
||||
meeting = get_object_or_404(Meeting, number=number)
|
||||
group = meeting.session_set.first().group
|
||||
first_session = meeting.session_set.first()
|
||||
session_status = current_session_status(first_session)
|
||||
group = first_session.group
|
||||
if not can_view_interim_request(meeting, request.user):
|
||||
return HttpResponseForbidden("You do not have permissions to cancel this meeting request")
|
||||
|
||||
|
@ -2047,11 +2091,20 @@ def interim_request_cancel(request, number):
|
|||
if form.is_valid():
|
||||
if 'comments' in form.changed_data:
|
||||
meeting.session_set.update(agenda_note=form.cleaned_data.get('comments'))
|
||||
if meeting.session_set.first().status.slug == 'sched':
|
||||
meeting.session_set.update(status_id='canceled')
|
||||
|
||||
was_scheduled = session_status.slug == 'sched'
|
||||
|
||||
result_status = SessionStatusName.objects.get(slug='canceled' if was_scheduled else 'canceledpa')
|
||||
for session in meeting.session_set.all():
|
||||
SchedulingEvent.objects.create(
|
||||
session=first_session,
|
||||
status=result_status,
|
||||
by=request.user.person,
|
||||
)
|
||||
|
||||
if was_scheduled:
|
||||
send_interim_cancellation_notice(meeting)
|
||||
else:
|
||||
meeting.session_set.update(status_id='canceledpa')
|
||||
|
||||
messages.success(request, 'Interim meeting cancelled')
|
||||
return redirect(upcoming)
|
||||
else:
|
||||
|
@ -2059,7 +2112,9 @@ def interim_request_cancel(request, number):
|
|||
|
||||
return render(request, "meeting/interim_request_cancel.html", {
|
||||
"form": form,
|
||||
"meeting": meeting})
|
||||
"meeting": meeting,
|
||||
"session_status": session_status,
|
||||
})
|
||||
|
||||
|
||||
@role_required('Area Director', 'Secretariat', 'IRTF Chair', 'WG Chair', 'RG Chair')
|
||||
|
@ -2072,7 +2127,12 @@ def interim_request_details(request, number):
|
|||
|
||||
if request.method == 'POST':
|
||||
if request.POST.get('approve') and can_approve_interim_request(meeting, request.user):
|
||||
meeting.session_set.update(status_id='scheda')
|
||||
for session in meeting.session_set.all():
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='scheda'),
|
||||
by=request.user.person,
|
||||
)
|
||||
messages.success(request, 'Interim meeting approved')
|
||||
if has_role(request.user, 'Secretariat'):
|
||||
return redirect(interim_send_announcement, number=number)
|
||||
|
@ -2080,13 +2140,23 @@ def interim_request_details(request, number):
|
|||
send_interim_announcement_request(meeting)
|
||||
return redirect(interim_pending)
|
||||
if request.POST.get('disapprove') and can_approve_interim_request(meeting, request.user):
|
||||
meeting.session_set.update(status_id='disappr')
|
||||
for session in meeting.session_set.all():
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='disappr'),
|
||||
by=request.user.person,
|
||||
)
|
||||
messages.success(request, 'Interim meeting disapproved')
|
||||
return redirect(interim_pending)
|
||||
|
||||
first_session = sessions.first()
|
||||
|
||||
return render(request, "meeting/interim_request_details.html", {
|
||||
"meeting": meeting,
|
||||
"sessions": sessions,
|
||||
"group": first_session.group,
|
||||
"requester": session_requested_by(first_session),
|
||||
"session_status": current_session_status(first_session),
|
||||
"can_edit": can_edit,
|
||||
"can_approve": can_approve})
|
||||
|
||||
|
@ -2109,7 +2179,7 @@ def interim_request_edit(request, number):
|
|||
form = InterimMeetingModelForm(request=request, instance=meeting,
|
||||
data=request.POST)
|
||||
group = Group.objects.get(pk=form.data['group'])
|
||||
is_approved = is_meeting_approved(meeting)
|
||||
is_approved = is_interim_meeting_approved(meeting)
|
||||
|
||||
SessionFormset.form.__init__ = curry(
|
||||
InterimSessionModelForm.__init__,
|
||||
|
@ -2122,10 +2192,11 @@ def interim_request_edit(request, number):
|
|||
if form.is_valid() and formset.is_valid():
|
||||
meeting = form.save(date=get_earliest_session_date(formset))
|
||||
formset.save()
|
||||
sessions_post_save(formset)
|
||||
sessions_post_save(request, formset)
|
||||
|
||||
message = 'Interim meeting request saved'
|
||||
if (form.has_changed() or formset.has_changed()) and meeting.session_set.filter(status='sched'):
|
||||
meeting_is_scheduled = add_event_info_to_session_qs(meeting.session_set).filter(current_status='sched').exists()
|
||||
if (form.has_changed() or formset.has_changed()) and meeting_is_scheduled:
|
||||
send_interim_change_notice(request, meeting)
|
||||
message = message + ' and change announcement sent'
|
||||
messages.success(request, message)
|
||||
|
@ -2144,32 +2215,8 @@ def interim_request_edit(request, number):
|
|||
def past(request):
|
||||
'''List of past meetings'''
|
||||
today = datetime.datetime.today()
|
||||
meetings = ( Meeting.objects.filter(date__lte=today)
|
||||
.exclude(session__status__in=('apprw', 'scheda', 'canceledpa'))
|
||||
.order_by('-date')
|
||||
.select_related('type')
|
||||
.prefetch_related('session_set__status','session_set__group',)
|
||||
)
|
||||
|
||||
# extract groups hierarchy for display filter
|
||||
seen = set()
|
||||
groups = [m.session_set.first().group for m
|
||||
in meetings.filter(type='interim')]
|
||||
group_parents = [ Group.objects.get(acronym='ietf') ]
|
||||
for g in groups:
|
||||
if g.parent and g.parent.acronym not in seen:
|
||||
group_parents.append(g.parent)
|
||||
seen.add(g.parent.acronym)
|
||||
|
||||
seen = set()
|
||||
for p in group_parents:
|
||||
p.group_list = []
|
||||
for g in groups:
|
||||
if g.acronym not in seen and g.parent == p:
|
||||
p.group_list.append(g)
|
||||
seen.add(g.acronym)
|
||||
|
||||
p.group_list.sort(key=lambda g: g.acronym)
|
||||
meetings, group_parents = data_for_meetings_overview(Meeting.objects.filter(date__lte=today).order_by('-date'))
|
||||
|
||||
return render(request, 'meeting/past.html', {
|
||||
'meetings': meetings,
|
||||
|
@ -2178,35 +2225,11 @@ def past(request):
|
|||
def upcoming(request):
|
||||
'''List of upcoming meetings'''
|
||||
today = datetime.datetime.today()
|
||||
meetings = Meeting.objects.filter(date__gte=today).exclude(
|
||||
session__status__in=('apprw', 'scheda', 'canceledpa')).order_by('date')
|
||||
|
||||
# extract groups hierarchy for display filter
|
||||
seen = set()
|
||||
groups = [m.session_set.first().group for m
|
||||
in meetings.filter(type='interim')]
|
||||
group_parents = []
|
||||
for g in groups:
|
||||
if g.parent.acronym not in seen:
|
||||
group_parents.append(g.parent)
|
||||
seen.add(g.parent.acronym)
|
||||
|
||||
seen = set()
|
||||
for p in group_parents:
|
||||
p.group_list = []
|
||||
for g in groups:
|
||||
if g.acronym not in seen and g.parent == p:
|
||||
p.group_list.append(g)
|
||||
seen.add(g.acronym)
|
||||
|
||||
p.group_list.sort(key=lambda g: g.acronym)
|
||||
|
||||
meetings = list(meetings)
|
||||
for m in meetings:
|
||||
m.end = m.date+datetime.timedelta(days=m.days)
|
||||
meetings, group_parents = data_for_meetings_overview(Meeting.objects.filter(date__gte=today).order_by('date'))
|
||||
|
||||
# add menu entries
|
||||
menu_entries = get_menu_entries(request)
|
||||
menu_entries = get_interim_menu_entries(request)
|
||||
selected_menu_entry = 'upcoming'
|
||||
|
||||
# add menu actions
|
||||
|
@ -2229,14 +2252,17 @@ def upcoming_ical(request):
|
|||
'''Return Upcoming meetings in iCalendar file'''
|
||||
filters = request.GET.getlist('filters')
|
||||
today = datetime.datetime.today()
|
||||
meetings = Meeting.objects.filter(date__gte=today).exclude(
|
||||
session__status__in=('apprw', 'schedpa')).order_by('date')
|
||||
|
||||
assignments = []
|
||||
for meeting in meetings:
|
||||
items = meeting.agenda.assignments.order_by(
|
||||
'session__type__slug', 'timeslot__time')
|
||||
assignments.extend(items)
|
||||
meetings, _ = data_for_meetings_overview(Meeting.objects.filter(date__gte=today).order_by('date'))
|
||||
|
||||
assignments = list(SchedTimeSessAssignment.objects.filter(
|
||||
schedule__meeting__schedule=F('schedule'),
|
||||
session__in=[s.pk for m in meetings for s in m.sessions]
|
||||
).order_by(
|
||||
'schedule__meeting__date', 'session__type', 'timeslot__time'
|
||||
).select_related(
|
||||
'session__group', 'session__group__parent', 'timeslot', 'schedule', 'schedule__meeting'
|
||||
).distinct())
|
||||
|
||||
# apply filters
|
||||
if filters:
|
||||
|
@ -2246,6 +2272,14 @@ def upcoming_ical(request):
|
|||
a.session.group.parent and a.session.group.parent.acronym in filters
|
||||
)
|
||||
) ]
|
||||
|
||||
# we already collected sessions with current_status, so reuse those
|
||||
sessions = {s.pk: s for m in meetings for s in m.sessions}
|
||||
for a in assignments:
|
||||
if a.session_id is not None:
|
||||
a.session = sessions.get(a.session_id) or a.session
|
||||
a.session.ical_status = ical_session_status(a.session.current_status)
|
||||
|
||||
# gather vtimezones
|
||||
vtimezones = set()
|
||||
for meeting in meetings:
|
||||
|
@ -2266,7 +2300,7 @@ def upcoming_ical(request):
|
|||
|
||||
def floor_plan(request, num=None, floor=None, ):
|
||||
meeting = get_meeting(num)
|
||||
schedule = meeting.agenda
|
||||
schedule = meeting.schedule
|
||||
floors = FloorPlan.objects.filter(meeting=meeting).order_by('order')
|
||||
if floor:
|
||||
floors = [ f for f in floors if xslugify(f.name) == floor ]
|
||||
|
@ -2280,7 +2314,7 @@ def proceedings(request, num=None):
|
|||
|
||||
meeting = get_meeting(num)
|
||||
|
||||
if (meeting.number.isdigit() and int(meeting.number) <= 64) or not meeting.agenda or not meeting.agenda.assignments.exists():
|
||||
if (meeting.number.isdigit() and int(meeting.number) <= 64) or not meeting.schedule or not meeting.schedule.assignments.exists():
|
||||
return HttpResponseRedirect( 'https://www.ietf.org/proceedings/%s' % num )
|
||||
|
||||
begin_date = meeting.get_submission_start_date()
|
||||
|
@ -2289,17 +2323,36 @@ def proceedings(request, num=None):
|
|||
now = datetime.date.today()
|
||||
|
||||
schedule = get_schedule(meeting, None)
|
||||
sessions = Session.objects.filter(meeting__number=meeting.number).filter(Q(timeslotassignments__schedule=schedule)|Q(status='notmeet')).select_related().order_by('-status_id')
|
||||
plenaries = sessions.filter(name__icontains='plenary').exclude(status='notmeet')
|
||||
sessions = add_event_info_to_session_qs(
|
||||
Session.objects.filter(meeting__number=meeting.number)
|
||||
).filter(
|
||||
Q(timeslotassignments__schedule=schedule) | Q(current_status='notmeet')
|
||||
).select_related().order_by('-current_status')
|
||||
plenaries = sessions.filter(name__icontains='plenary').exclude(current_status='notmeet')
|
||||
ietf = sessions.filter(group__parent__type__slug = 'area').exclude(group__acronym='edu')
|
||||
irtf = sessions.filter(group__parent__acronym = 'irtf')
|
||||
training = sessions.filter(group__acronym__in=['edu','iaoc'], type_id__in=['session', 'other', ]).exclude(status='notmeet')
|
||||
iab = sessions.filter(group__parent__acronym = 'iab').exclude(status='notmeet')
|
||||
training = sessions.filter(group__acronym__in=['edu','iaoc'], type_id__in=['regular', 'other', ]).exclude(current_status='notmeet')
|
||||
iab = sessions.filter(group__parent__acronym = 'iab').exclude(current_status='notmeet')
|
||||
|
||||
cache_version = Document.objects.filter(session__meeting__number=meeting.number).aggregate(Max('time'))["time__max"]
|
||||
|
||||
ietf_areas = []
|
||||
for area, sessions in itertools.groupby(sorted(ietf, key=lambda s: (s.group.parent.acronym, s.group.acronym)), key=lambda s: s.group.parent):
|
||||
sessions = list(sessions)
|
||||
meeting_groups = set(s.group_id for s in sessions if s.current_status != 'notmeet')
|
||||
meeting_sessions = []
|
||||
not_meeting_sessions = []
|
||||
for s in sessions:
|
||||
if s.current_status == 'notmeet' and s.group_id not in meeting_groups:
|
||||
not_meeting_sessions.append(s)
|
||||
else:
|
||||
meeting_sessions.append(s)
|
||||
ietf_areas.append((area, meeting_sessions, not_meeting_sessions))
|
||||
|
||||
return render(request, "meeting/proceedings.html", {
|
||||
'meeting': meeting,
|
||||
'plenaries': plenaries, 'ietf': ietf, 'training': training, 'irtf': irtf, 'iab': iab,
|
||||
'ietf_areas': ietf_areas,
|
||||
'cut_off_date': cut_off_date,
|
||||
'cor_cut_off_date': cor_cut_off_date,
|
||||
'submission_started': now > begin_date,
|
||||
|
@ -2311,7 +2364,7 @@ def finalize_proceedings(request, num=None):
|
|||
|
||||
meeting = get_meeting(num)
|
||||
|
||||
if (meeting.number.isdigit() and int(meeting.number) <= 64) or not meeting.agenda or not meeting.agenda.assignments.exists() or meeting.proceedings_final:
|
||||
if (meeting.number.isdigit() and int(meeting.number) <= 64) or not meeting.schedule or not meeting.schedule.assignments.exists() or meeting.proceedings_final:
|
||||
raise Http404
|
||||
|
||||
if request.method=='POST':
|
||||
|
@ -2495,7 +2548,7 @@ def edit_timeslot_type(request, num, slot_id):
|
|||
else:
|
||||
form = TimeSlotTypeForm(instance=timeslot)
|
||||
|
||||
sessions = timeslot.sessions.filter(timeslotassignments__schedule=meeting.agenda)
|
||||
sessions = timeslot.sessions.filter(timeslotassignments__schedule=meeting.schedule)
|
||||
|
||||
return render(request, 'meeting/edit_timeslot_type.html', {'timeslot':timeslot,'form':form,'sessions':sessions})
|
||||
|
||||
|
@ -2515,12 +2568,19 @@ def request_minutes(request, num=None):
|
|||
)
|
||||
return HttpResponseRedirect(reverse('ietf.meeting.views.materials',kwargs={'num':num}))
|
||||
else:
|
||||
needs_minutes = set()
|
||||
for a in meeting.agenda.assignments.filter(session__group__type_id__in=('wg','rg','ag')).exclude(session__status='canceled'):
|
||||
if not a.session.all_meeting_minutes():
|
||||
group = a.session.group
|
||||
needs_minutes = set()
|
||||
session_qs = add_event_info_to_session_qs(
|
||||
Session.objects.filter(
|
||||
timeslotassignments__schedule__meeting=meeting,
|
||||
timeslotassignments__schedule__meeting__schedule=F('timeslotassignments__schedule'),
|
||||
group__type__in=['wg','rg','ag'],
|
||||
)
|
||||
).filter(~Q(current_status='canceled')).select_related('group', 'group__parent')
|
||||
for session in session_qs:
|
||||
if not session.all_meeting_minutes():
|
||||
group = session.group
|
||||
if group.parent and group.parent.type_id in ('area','irtf'):
|
||||
needs_minutes.add(a.session.group)
|
||||
needs_minutes.add(group)
|
||||
needs_minutes = list(needs_minutes)
|
||||
needs_minutes.sort(key=lambda g: ('zzz' if g.parent.acronym == 'irtf' else g.parent.acronym)+":"+g.acronym)
|
||||
body_context = {'meeting':meeting,
|
||||
|
@ -2556,7 +2616,7 @@ def approve_proposed_slides(request, slidesubmission_id, num):
|
|||
|
||||
session_number = None
|
||||
sessions = get_sessions(submission.session.meeting.number,submission.session.group.acronym)
|
||||
show_apply_to_all_checkbox = len(sessions) > 1 if submission.session.type_id == 'session' else False
|
||||
show_apply_to_all_checkbox = len(sessions) > 1 if submission.session.type_id == 'regular' else False
|
||||
if len(sessions) > 1:
|
||||
session_number = 1 + sessions.index(submission.session)
|
||||
name, _ = os.path.splitext(submission.filename)
|
||||
|
@ -2565,7 +2625,7 @@ def approve_proposed_slides(request, slidesubmission_id, num):
|
|||
if request.method == 'POST':
|
||||
form = ApproveSlidesForm(show_apply_to_all_checkbox, request.POST)
|
||||
if form.is_valid():
|
||||
apply_to_all = submission.session.type_id == 'session'
|
||||
apply_to_all = submission.session.type_id == 'regular'
|
||||
if show_apply_to_all_checkbox:
|
||||
apply_to_all = form.cleaned_data['apply_to_all']
|
||||
if request.POST.get('approve'):
|
||||
|
|
|
@ -11643,7 +11643,7 @@
|
|||
"used": true
|
||||
},
|
||||
"model": "name.timeslottypename",
|
||||
"pk": "session"
|
||||
"pk": "regular"
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
|
|
|
@ -68,7 +68,7 @@ class AgendaTypeName(NameModel):
|
|||
class SessionStatusName(NameModel):
|
||||
"""Waiting for Approval, Approved, Waiting for Scheduling, Scheduled, Cancelled, Disapproved"""
|
||||
class TimeSlotTypeName(NameModel):
|
||||
"""Session, Break, Registration, Other(Non-Session), Reserved, unavail"""
|
||||
"""Session, Break, Registration, Other, Reserved, unavail"""
|
||||
class ConstraintName(NameModel):
|
||||
"""Conflict"""
|
||||
penalty = models.IntegerField(default=0, help_text="The penalty for violating this kind of constraint; for instance 10 (small penalty) or 10000 (large penalty)")
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# Copyright The IETF Trust 2013-2019, All Rights Reserved
|
||||
import re
|
||||
|
||||
from django import forms
|
||||
|
@ -48,7 +49,7 @@ def get_times(meeting,day):
|
|||
The label is [start_time]-[end_time].
|
||||
'''
|
||||
# pick a random room
|
||||
rooms = Room.objects.filter(meeting=meeting,session_types='session')
|
||||
rooms = Room.objects.filter(meeting=meeting,session_types='regular')
|
||||
if rooms:
|
||||
room = rooms[0]
|
||||
else:
|
||||
|
@ -102,7 +103,7 @@ class MeetingModelForm(forms.ModelForm):
|
|||
idsubmit_cutoff_warning_days = ietf.utils.fields.DurationField()
|
||||
class Meta:
|
||||
model = Meeting
|
||||
exclude = ('type', 'agenda', 'session_request_lock_message')
|
||||
exclude = ('type', 'schedule', 'session_request_lock_message')
|
||||
|
||||
|
||||
def __init__(self,*args,**kwargs):
|
||||
|
@ -147,9 +148,9 @@ class TimeSlotForm(forms.Form):
|
|||
raise forms.ValidationError('{} value has an invalid format. It must be in HH:MM format'.format(duration))
|
||||
return self.cleaned_data['duration']
|
||||
|
||||
class NonSessionForm(TimeSlotForm):
|
||||
class MiscSessionForm(TimeSlotForm):
|
||||
short = forms.CharField(max_length=32,label='Short Name',help_text='Enter an abbreviated session name (used for material file names)',required=False)
|
||||
type = forms.ModelChoiceField(queryset=TimeSlotTypeName.objects.filter(used=True).exclude(slug__in=('session',)),empty_label=None)
|
||||
type = forms.ModelChoiceField(queryset=TimeSlotTypeName.objects.filter(used=True).exclude(slug__in=('regular',)),empty_label=None)
|
||||
group = forms.ModelChoiceField(
|
||||
queryset=Group.objects.filter(type__in=['ietf','team'],state='active'),
|
||||
help_text='''Select a group to associate with this session. For example:<br>
|
||||
|
@ -165,11 +166,11 @@ class NonSessionForm(TimeSlotForm):
|
|||
self.meeting = kwargs.pop('meeting')
|
||||
if 'session' in kwargs:
|
||||
self.session = kwargs.pop('session')
|
||||
super(NonSessionForm, self).__init__(*args,**kwargs)
|
||||
super(MiscSessionForm, self).__init__(*args,**kwargs)
|
||||
self.fields['location'].queryset = Room.objects.filter(meeting=self.meeting)
|
||||
|
||||
def clean(self):
|
||||
super(NonSessionForm, self).clean()
|
||||
super(MiscSessionForm, self).clean()
|
||||
if any(self.errors):
|
||||
return
|
||||
cleaned_data = self.cleaned_data
|
||||
|
@ -198,7 +199,7 @@ class UploadBlueSheetForm(forms.Form):
|
|||
raise forms.ValidationError('Incorrect filename format')
|
||||
return file
|
||||
|
||||
class SessionEditForm(forms.ModelForm):
|
||||
class RegularSessionEditForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Session
|
||||
fields = ['agenda_note']
|
||||
|
|
|
@ -16,7 +16,7 @@ from django.conf import settings
|
|||
from django.urls import reverse
|
||||
|
||||
from ietf.group.models import Group, GroupEvent
|
||||
from ietf.meeting.models import Meeting, Room, TimeSlot, SchedTimeSessAssignment, Session
|
||||
from ietf.meeting.models import Meeting, Room, TimeSlot, SchedTimeSessAssignment, Session, SchedulingEvent
|
||||
from ietf.meeting.test_data import make_meeting_test_data
|
||||
from ietf.name.models import SessionStatusName
|
||||
from ietf.person.models import Person
|
||||
|
@ -93,7 +93,7 @@ class SecrMeetingTestCase(TestCase):
|
|||
self.assertEqual(Meeting.objects.count(),count + 1)
|
||||
new_meeting = Meeting.objects.get(number=number)
|
||||
|
||||
self.assertTrue(new_meeting.agenda)
|
||||
self.assertTrue(new_meeting.schedule)
|
||||
self.assertEqual(new_meeting.attendees, None)
|
||||
|
||||
def test_edit_meeting(self):
|
||||
|
@ -151,11 +151,10 @@ class SecrMeetingTestCase(TestCase):
|
|||
meeting = make_meeting_test_data()
|
||||
mars_group = Group.objects.get(acronym='mars')
|
||||
ames_group = Group.objects.get(acronym='ames')
|
||||
ames_stsa = meeting.agenda.assignments.get(session__group=ames_group)
|
||||
assert ames_stsa.session.status_id == 'schedw'
|
||||
mars_stsa = meeting.agenda.assignments.get(session__group=mars_group)
|
||||
mars_stsa.session.status = SessionStatusName.objects.get(slug='appr')
|
||||
mars_stsa.session.save()
|
||||
ames_stsa = meeting.schedule.assignments.get(session__group=ames_group)
|
||||
assert SchedulingEvent.objects.filter(session=ames_stsa.session).order_by('-id')[0].status_id == 'schedw'
|
||||
mars_stsa = meeting.schedule.assignments.get(session__group=mars_group)
|
||||
SchedulingEvent.objects.create(session=mars_stsa.session, status=SessionStatusName.objects.get(slug='appr'), by=Person.objects.get(name="(System)"))
|
||||
url = reverse('ietf.secr.meetings.views.notifications',kwargs={'meeting_id':72})
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
response = self.client.get(url)
|
||||
|
@ -169,7 +168,7 @@ class SecrMeetingTestCase(TestCase):
|
|||
person = Person.objects.get(name="(System)")
|
||||
GroupEvent.objects.create(group=mars_group,time=now,type='sent_notification',
|
||||
by=person,desc='sent scheduled notification for %s' % meeting)
|
||||
ss = meeting.agenda.assignments.get(session__group=ames_group)
|
||||
ss = meeting.schedule.assignments.get(session__group=ames_group)
|
||||
ss.modified = then
|
||||
ss.save()
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
|
@ -184,14 +183,14 @@ class SecrMeetingTestCase(TestCase):
|
|||
response = self.client.post(url)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
ames_stsa = meeting.agenda.assignments.get(session__group=ames_group)
|
||||
assert ames_stsa.session.status_id == 'sched'
|
||||
mars_stsa = meeting.agenda.assignments.get(session__group=mars_group)
|
||||
assert mars_stsa.session.status_id == 'sched'
|
||||
ames_stsa = meeting.schedule.assignments.get(session__group=ames_group)
|
||||
self.assertEqual(SchedulingEvent.objects.filter(session=ames_stsa.session).order_by('-id')[0].status_id, 'sched')
|
||||
mars_stsa = meeting.schedule.assignments.get(session__group=mars_group)
|
||||
self.assertEqual(SchedulingEvent.objects.filter(session=mars_stsa.session).order_by('-id')[0].status_id, 'sched')
|
||||
|
||||
def test_meetings_rooms(self):
|
||||
meeting = make_meeting_test_data()
|
||||
url = reverse('ietf.secr.meetings.views.rooms',kwargs={'meeting_id':72,'schedule_name':'test-agenda'})
|
||||
url = reverse('ietf.secr.meetings.views.rooms',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
@ -200,7 +199,7 @@ class SecrMeetingTestCase(TestCase):
|
|||
|
||||
# test delete
|
||||
# first unschedule sessions so we can delete
|
||||
SchedTimeSessAssignment.objects.filter(schedule=meeting.agenda).delete()
|
||||
SchedTimeSessAssignment.objects.filter(schedule=meeting.schedule).delete()
|
||||
SchedTimeSessAssignment.objects.filter(schedule=meeting.unofficial_schedule).delete()
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
post_dict = {
|
||||
|
@ -217,7 +216,7 @@ class SecrMeetingTestCase(TestCase):
|
|||
|
||||
def test_meetings_times(self):
|
||||
make_meeting_test_data()
|
||||
url = reverse('ietf.secr.meetings.views.times',kwargs={'meeting_id':72,'schedule_name':'test-agenda'})
|
||||
url = reverse('ietf.secr.meetings.views.times',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
@ -232,32 +231,32 @@ class SecrMeetingTestCase(TestCase):
|
|||
|
||||
def test_meetings_times_delete(self):
|
||||
meeting = make_meeting_test_data()
|
||||
qs = TimeSlot.objects.filter(meeting=meeting,type='session')
|
||||
qs = TimeSlot.objects.filter(meeting=meeting,type='regular')
|
||||
before = qs.count()
|
||||
expected_deletion_count = qs.filter(time=qs.first().time).count()
|
||||
url = reverse('ietf.secr.meetings.views.times_delete',kwargs={
|
||||
'meeting_id':meeting.number,
|
||||
'schedule_name':meeting.agenda.name,
|
||||
'schedule_name':meeting.schedule.name,
|
||||
'time':qs.first().time.strftime("%Y:%m:%d:%H:%M")
|
||||
})
|
||||
redirect_url = reverse('ietf.secr.meetings.views.times',kwargs={
|
||||
'meeting_id':meeting.number,
|
||||
'schedule_name':meeting.agenda.name
|
||||
'schedule_name':meeting.schedule.name
|
||||
})
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.post(url, {'post':'yes'})
|
||||
self.assertRedirects(response, redirect_url)
|
||||
after = TimeSlot.objects.filter(meeting=meeting,type='session').count()
|
||||
after = TimeSlot.objects.filter(meeting=meeting,type='regular').count()
|
||||
self.assertEqual(after,before - expected_deletion_count)
|
||||
|
||||
def test_meetings_times_edit(self):
|
||||
meeting = make_meeting_test_data()
|
||||
timeslot = TimeSlot.objects.filter(meeting=meeting,type='session').first()
|
||||
timeslot = TimeSlot.objects.filter(meeting=meeting,type='regular').first()
|
||||
url = reverse('ietf.secr.meetings.views.times_edit',kwargs={
|
||||
'meeting_id':72,
|
||||
'schedule_name':'test-agenda',
|
||||
'schedule_name':'test-schedule',
|
||||
'time':timeslot.time.strftime("%Y:%m:%d:%H:%M")
|
||||
})
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
|
@ -270,18 +269,18 @@ class SecrMeetingTestCase(TestCase):
|
|||
self.assertEqual(response.status_code, 302)
|
||||
self.assertTrue(TimeSlot.objects.filter(meeting=meeting,name='Testing'))
|
||||
|
||||
def test_meetings_nonsession(self):
|
||||
def test_meetings_misc_sessions(self):
|
||||
make_meeting_test_data()
|
||||
url = reverse('ietf.secr.meetings.views.non_session',kwargs={'meeting_id':72,'schedule_name':'test-agenda'})
|
||||
url = reverse('ietf.secr.meetings.views.misc_sessions',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_meetings_nonsession_add_valid(self):
|
||||
def test_meetings_misc_session_add_valid(self):
|
||||
meeting = make_meeting_test_data()
|
||||
room = meeting.room_set.first()
|
||||
group = Group.objects.get(acronym='secretariat')
|
||||
url = reverse('ietf.secr.meetings.views.non_session',kwargs={'meeting_id':72,'schedule_name':'test-agenda'})
|
||||
url = reverse('ietf.secr.meetings.views.misc_sessions',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
response = self.client.post(url, {
|
||||
'day':'1',
|
||||
|
@ -299,10 +298,10 @@ class SecrMeetingTestCase(TestCase):
|
|||
|
||||
self.assertEqual(session.timeslotassignments.first().timeslot.location, room)
|
||||
|
||||
def test_meetings_nonsession_add_invalid(self):
|
||||
def test_meetings_misc_session_add_invalid(self):
|
||||
make_meeting_test_data()
|
||||
group = Group.objects.get(acronym='secretariat')
|
||||
url = reverse('ietf.secr.meetings.views.non_session',kwargs={'meeting_id':72,'schedule_name':'test-agenda'})
|
||||
url = reverse('ietf.secr.meetings.views.misc_sessions',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
response = self.client.post(url, {
|
||||
'day':'1',
|
||||
|
@ -316,12 +315,12 @@ class SecrMeetingTestCase(TestCase):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'invalid format')
|
||||
|
||||
def test_meetings_nonsession_edit(self):
|
||||
def test_meetings_misc_session_edit(self):
|
||||
meeting = make_meeting_test_data()
|
||||
session = meeting.session_set.exclude(name='').first() # get first non-session session
|
||||
session = meeting.session_set.exclude(name='').first() # get first misc session
|
||||
timeslot = session.official_timeslotassignment().timeslot
|
||||
url = reverse('ietf.secr.meetings.views.non_session_edit',kwargs={'meeting_id':72,'schedule_name':meeting.agenda.name,'slot_id':timeslot.pk})
|
||||
redirect_url = reverse('ietf.secr.meetings.views.non_session',kwargs={'meeting_id':72,'schedule_name':'test-agenda'})
|
||||
url = reverse('ietf.secr.meetings.views.misc_session_edit',kwargs={'meeting_id':72,'schedule_name':meeting.schedule.name,'slot_id':timeslot.pk})
|
||||
redirect_url = reverse('ietf.secr.meetings.views.misc_sessions',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
|
||||
new_time = timeslot.time + datetime.timedelta(days=1)
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
response = self.client.get(url)
|
||||
|
@ -340,36 +339,36 @@ class SecrMeetingTestCase(TestCase):
|
|||
timeslot = session.official_timeslotassignment().timeslot
|
||||
self.assertEqual(timeslot.time,new_time)
|
||||
|
||||
def test_meetings_non_session_delete(self):
|
||||
def test_meetings_misc_session_delete(self):
|
||||
meeting = make_meeting_test_data()
|
||||
slot = meeting.agenda.assignments.filter(timeslot__type='reg').first().timeslot
|
||||
url = reverse('ietf.secr.meetings.views.non_session_delete', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.agenda.name,'slot_id':slot.id})
|
||||
target = reverse('ietf.secr.meetings.views.non_session', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.agenda.name})
|
||||
slot = meeting.schedule.assignments.filter(timeslot__type='reg').first().timeslot
|
||||
url = reverse('ietf.secr.meetings.views.misc_session_delete', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.schedule.name,'slot_id':slot.id})
|
||||
target = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.schedule.name})
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.post(url, {'post':'yes'})
|
||||
self.assertRedirects(response, target)
|
||||
self.assertFalse(meeting.agenda.assignments.filter(timeslot=slot))
|
||||
self.assertFalse(meeting.schedule.assignments.filter(timeslot=slot))
|
||||
|
||||
def test_meetings_non_session_cancel(self):
|
||||
def test_meetings_misc_session_cancel(self):
|
||||
meeting = make_meeting_test_data()
|
||||
slot = meeting.agenda.assignments.filter(timeslot__type='reg').first().timeslot
|
||||
url = reverse('ietf.secr.meetings.views.non_session_cancel', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.agenda.name,'slot_id':slot.id})
|
||||
redirect_url = reverse('ietf.secr.meetings.views.non_session', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.agenda.name})
|
||||
slot = meeting.schedule.assignments.filter(timeslot__type='reg').first().timeslot
|
||||
url = reverse('ietf.secr.meetings.views.misc_session_cancel', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.schedule.name,'slot_id':slot.id})
|
||||
redirect_url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.schedule.name})
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.post(url, {'post':'yes'})
|
||||
self.assertRedirects(response, redirect_url)
|
||||
session = slot.sessionassignments.filter(schedule=meeting.agenda).first().session
|
||||
self.assertEqual(session.status_id, 'canceled')
|
||||
session = slot.sessionassignments.filter(schedule=meeting.schedule).first().session
|
||||
self.assertEqual(SchedulingEvent.objects.filter(session=session).order_by('-id')[0].status_id, 'canceled')
|
||||
|
||||
def test_meetings_session_edit(self):
|
||||
def test_meetings_regular_session_edit(self):
|
||||
meeting = make_meeting_test_data()
|
||||
session = Session.objects.filter(meeting=meeting,group__acronym='mars').first()
|
||||
url = reverse('ietf.secr.meetings.views.session_edit', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.agenda.name,'session_id':session.id})
|
||||
redirect_url = reverse('ietf.secr.meetings.views.sessions', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.agenda.name})
|
||||
url = reverse('ietf.secr.meetings.views.regular_session_edit', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.schedule.name,'session_id':session.id})
|
||||
redirect_url = reverse('ietf.secr.meetings.views.regular_sessions', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.schedule.name})
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
@ -383,7 +382,7 @@ class SecrMeetingTestCase(TestCase):
|
|||
# -----------------------
|
||||
def test_get_times(self):
|
||||
meeting = make_meeting_test_data()
|
||||
timeslot = meeting.timeslot_set.filter(type='session').first()
|
||||
timeslot = meeting.timeslot_set.filter(type='regular').first()
|
||||
day = (timeslot.time.weekday() + 1) % 7 + 1 # fix up to match django __week_day filter
|
||||
times = get_times(meeting,day)
|
||||
values = [ x[0] for x in times ]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# Copyright The IETF Trust 2007-2019, All Rights Reserved
|
||||
|
||||
from ietf.secr.meetings import views
|
||||
from ietf.utils.urls import url
|
||||
|
@ -13,14 +14,14 @@ urlpatterns = [
|
|||
url(r'^(?P<meeting_id>\d{1,6})/edit/$', views.edit_meeting),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/notifications/$', views.notifications),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/$', views.rooms),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/non_session/$', views.non_session),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/non_session/cancel/(?P<slot_id>\d{1,6})/$', views.non_session_cancel),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/non_session/edit/(?P<slot_id>\d{1,6})/$', views.non_session_edit),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/non_session/delete/(?P<slot_id>\d{1,6})/$', views.non_session_delete),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/miscsessions/$', views.misc_sessions),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/miscsessions/cancel/(?P<slot_id>\d{1,6})/$', views.misc_session_cancel),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/miscsessions/edit/(?P<slot_id>\d{1,6})/$', views.misc_session_edit),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/miscsessions/delete/(?P<slot_id>\d{1,6})/$', views.misc_session_delete),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/rooms/$', views.rooms),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/times/$', views.times),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/sessions/$', views.sessions),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/regularsessions/$', views.regular_sessions),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/times/delete/(?P<time>[0-9\:]+)/$', views.times_delete),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/times/edit/(?P<time>[0-9\:]+)/$', views.times_edit),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/(?P<session_id>\d{1,6})/edit/$', views.session_edit),
|
||||
url(r'^(?P<meeting_id>\d{1,6})/(?P<schedule_name>[A-Za-z0-9_\-]+)/(?P<session_id>\d{1,6})/edit/$', views.regular_session_edit),
|
||||
]
|
||||
|
|
|
@ -17,13 +17,15 @@ from ietf.ietfauth.utils import role_required
|
|||
from ietf.utils.mail import send_mail
|
||||
from ietf.meeting.forms import duration_string
|
||||
from ietf.meeting.helpers import get_meeting, make_materials_directories, populate_important_dates
|
||||
from ietf.meeting.models import Meeting, Session, Room, TimeSlot, SchedTimeSessAssignment, Schedule
|
||||
from ietf.meeting.models import Meeting, Session, Room, TimeSlot, SchedTimeSessAssignment, Schedule, SchedulingEvent
|
||||
from ietf.meeting.utils import add_event_info_to_session_qs
|
||||
from ietf.meeting.utils import only_sessions_that_can_meet
|
||||
from ietf.name.models import SessionStatusName
|
||||
from ietf.group.models import Group, GroupEvent
|
||||
from ietf.person.models import Person
|
||||
from ietf.secr.meetings.blue_sheets import create_blue_sheets
|
||||
from ietf.secr.meetings.forms import ( BaseMeetingRoomFormSet, MeetingModelForm, MeetingSelectForm,
|
||||
MeetingRoomForm, NonSessionForm, TimeSlotForm, SessionEditForm,
|
||||
MeetingRoomForm, MiscSessionForm, TimeSlotForm, RegularSessionEditForm,
|
||||
UploadBlueSheetForm )
|
||||
from ietf.secr.proceedings.utils import handle_upload_file
|
||||
from ietf.secr.sreq.views import get_initial_session
|
||||
|
@ -35,17 +37,6 @@ from ietf.mailtrigger.utils import gather_address_lists
|
|||
# --------------------------------------------------
|
||||
# Helper Functions
|
||||
# --------------------------------------------------
|
||||
def assign(session,timeslot,meeting,schedule=None):
|
||||
'''
|
||||
Robust function to assign a session to a timeslot. Much simplyfied 2014-03-26.
|
||||
'''
|
||||
if schedule == None:
|
||||
schedule = meeting.agenda
|
||||
SchedTimeSessAssignment.objects.create(schedule=schedule,
|
||||
session=session,
|
||||
timeslot=timeslot)
|
||||
session.status_id = 'sched'
|
||||
session.save()
|
||||
|
||||
def build_timeslots(meeting,room=None):
|
||||
'''
|
||||
|
@ -54,16 +45,16 @@ def build_timeslots(meeting,room=None):
|
|||
If room is passed pre-create timeslots for the new room. Call this after saving new rooms
|
||||
or adding a room.
|
||||
'''
|
||||
slots = meeting.timeslot_set.filter(type='session')
|
||||
slots = meeting.timeslot_set.filter(type='regular')
|
||||
|
||||
# Don't do anything if the room is not capable of handling sessions
|
||||
if room and not room.session_types.filter(slug='session'):
|
||||
if room and not room.session_types.filter(slug='regular'):
|
||||
return
|
||||
|
||||
if room:
|
||||
rooms = [room]
|
||||
else:
|
||||
rooms = meeting.room_set.filter(session_types__slug='session')
|
||||
rooms = meeting.room_set.filter(session_types__slug='regular')
|
||||
if not slots or room:
|
||||
# if we are just building timeslots for a new room, the room argument was passed,
|
||||
# then we need to use current meeting times as a template, not the last meeting times
|
||||
|
@ -75,23 +66,23 @@ def build_timeslots(meeting,room=None):
|
|||
delta = meeting.date - source_meeting.date
|
||||
timeslots = []
|
||||
time_seen = set()
|
||||
for t in source_meeting.timeslot_set.filter(type='session'):
|
||||
for t in source_meeting.timeslot_set.filter(type='regular'):
|
||||
if not t.time in time_seen:
|
||||
time_seen.add(t.time)
|
||||
timeslots.append(t)
|
||||
for t in timeslots:
|
||||
new_time = t.time + delta
|
||||
for room in rooms:
|
||||
TimeSlot.objects.create(type_id='session',
|
||||
TimeSlot.objects.create(type_id='regular',
|
||||
meeting=meeting,
|
||||
name=t.name,
|
||||
time=new_time,
|
||||
location=room,
|
||||
duration=t.duration)
|
||||
|
||||
def check_nonsession(meeting,schedule):
|
||||
def check_misc_sessions(meeting,schedule):
|
||||
'''
|
||||
Ensure non-session timeslots exist and have appropriate SchedTimeSessAssignment objects
|
||||
Ensure misc session timeslots exist and have appropriate SchedTimeSessAssignment objects
|
||||
for the specified schedule.
|
||||
'''
|
||||
slots = TimeSlot.objects.filter(meeting=meeting,type__in=('break','reg','other','plenary','lead','offagenda'))
|
||||
|
@ -122,7 +113,7 @@ def is_combined(session,meeting,schedule=None):
|
|||
Check to see if this session is using two combined timeslots
|
||||
'''
|
||||
if schedule == None:
|
||||
schedule = meeting.agenda
|
||||
schedule = meeting.schedule
|
||||
if session.timeslotassignments.filter(schedule=schedule).count() > 1:
|
||||
return True
|
||||
else:
|
||||
|
@ -155,16 +146,21 @@ def send_notifications(meeting, groups, person):
|
|||
items[i]['period'] = '%s-%s' % (t.time.strftime('%H%M'),(t.time + t.duration).strftime('%H%M'))
|
||||
|
||||
# send email
|
||||
first_event = SchedulingEvent.objects.filter(session=sessions[0]).select_related('by').order_by('time', 'id').first()
|
||||
requested_by = None
|
||||
if first_event and first_event.status_id in ['appw', 'schedw']:
|
||||
requested_by = first_event.by
|
||||
|
||||
context = {
|
||||
'items': items,
|
||||
'meeting': meeting,
|
||||
'baseurl': settings.IDTRACKER_BASE_URL,
|
||||
}
|
||||
context['to_name'] = sessions[0].requested_by
|
||||
context['to_name'] = str(requested_by) or "Requester"
|
||||
context['agenda_note'] = sessions[0].agenda_note
|
||||
context['session'] = get_initial_session(sessions)
|
||||
context['group'] = group
|
||||
context['login'] = sessions[0].requested_by
|
||||
context['login'] = requested_by
|
||||
|
||||
send_mail(None,
|
||||
addrs.to,
|
||||
|
@ -230,7 +226,7 @@ def add(request):
|
|||
owner = Person.objects.get(name='(System)'),
|
||||
visible = True,
|
||||
public = True)
|
||||
meeting.agenda = schedule
|
||||
meeting.schedule = schedule
|
||||
|
||||
# we want to carry session request lock status over from previous meeting
|
||||
previous_meeting = get_meeting( int(meeting.number) - 1 )
|
||||
|
@ -300,7 +296,7 @@ def blue_sheet_generate(request, meeting_id):
|
|||
# TODO: Why aren't 'ag' in here as well?
|
||||
groups = Group.objects.filter(
|
||||
type__in=['wg','rg'],
|
||||
session__timeslotassignments__schedule=meeting.agenda).order_by('acronym')
|
||||
session__timeslotassignments__schedule=meeting.schedule).order_by('acronym')
|
||||
create_blue_sheets(meeting, groups)
|
||||
|
||||
messages.success(request, 'Blue Sheets generated')
|
||||
|
@ -374,21 +370,21 @@ def main(request):
|
|||
)
|
||||
|
||||
@role_required('Secretariat')
|
||||
def non_session(request, meeting_id, schedule_name):
|
||||
def misc_sessions(request, meeting_id, schedule_name):
|
||||
'''
|
||||
Display and add "non-session" time slots, ie. registration, beverage and snack breaks
|
||||
Display and add misc session time slots, e.g. registration, beverage and snack breaks
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_id)
|
||||
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
|
||||
|
||||
check_nonsession(meeting,schedule)
|
||||
check_misc_sessions(meeting,schedule)
|
||||
|
||||
non_session_types = ('break','reg','other','plenary','lead')
|
||||
assignments = schedule.assignments.filter(timeslot__type__in=non_session_types)
|
||||
misc_session_types = ['break','reg','other','plenary','lead']
|
||||
assignments = schedule.assignments.filter(timeslot__type__in=misc_session_types)
|
||||
assignments = assignments.order_by('-timeslot__type__name','timeslot__time')
|
||||
|
||||
if request.method == 'POST':
|
||||
form = NonSessionForm(request.POST, meeting=meeting)
|
||||
form = MiscSessionForm(request.POST, meeting=meeting)
|
||||
if form.is_valid():
|
||||
time = get_timeslot_time(form, meeting)
|
||||
name = form.cleaned_data['name']
|
||||
|
@ -411,41 +407,52 @@ def non_session(request, meeting_id, schedule_name):
|
|||
group = Group.objects.get(acronym='secretariat')
|
||||
|
||||
# create associated Session object
|
||||
session = Session(meeting=meeting,
|
||||
name=name,
|
||||
short=short,
|
||||
group=group,
|
||||
requested_by=Person.objects.get(name='(System)'),
|
||||
status_id='sched',
|
||||
type=type,
|
||||
)
|
||||
session.save()
|
||||
|
||||
session = Session.objects.create(meeting=meeting,
|
||||
name=name,
|
||||
short=short,
|
||||
group=group,
|
||||
type=type)
|
||||
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='sched'),
|
||||
by=request.user.person,
|
||||
)
|
||||
|
||||
# create association
|
||||
SchedTimeSessAssignment.objects.create(timeslot=timeslot,
|
||||
session=session,
|
||||
schedule=schedule)
|
||||
|
||||
messages.success(request, 'Non-Sessions updated successfully')
|
||||
return redirect('ietf.secr.meetings.views.non_session', meeting_id=meeting_id, schedule_name=schedule_name)
|
||||
messages.success(request, 'Misc. sessions updated successfully')
|
||||
return redirect('ietf.secr.meetings.views.misc_sessions', meeting_id=meeting_id, schedule_name=schedule_name)
|
||||
else:
|
||||
form = NonSessionForm(initial={'show_location':True}, meeting=meeting)
|
||||
form = MiscSessionForm(initial={'show_location':True}, meeting=meeting)
|
||||
|
||||
if TimeSlot.objects.filter(meeting=meeting,type='other',location__isnull=True):
|
||||
messages.warning(request, 'There are non-session items which do not have a room assigned')
|
||||
no_room = TimeSlot.objects.filter(meeting=meeting,type='other',location__isnull=True)
|
||||
if no_room:
|
||||
messages.warning(request, 'There are misc. session time slots which do not have a room assigned')
|
||||
|
||||
return render(request, 'meetings/non_session.html', {
|
||||
session_statuses = {
|
||||
e.session_id: e.status_id
|
||||
for e in SchedulingEvent.objects.filter(session__in=[a.session_id for a in assignments]).order_by('time', 'id')
|
||||
}
|
||||
|
||||
for a in assignments:
|
||||
a.current_session_status = session_statuses.get(a.session_id)
|
||||
|
||||
return render(request, 'meetings/misc_sessions.html', {
|
||||
'assignments': assignments,
|
||||
'form': form,
|
||||
'meeting': meeting,
|
||||
'schedule': schedule,
|
||||
'selected': 'non-sessions'},
|
||||
'selected': 'misc-sessions'},
|
||||
)
|
||||
|
||||
@role_required('Secretariat')
|
||||
def non_session_cancel(request, meeting_id, schedule_name, slot_id):
|
||||
def misc_session_cancel(request, meeting_id, schedule_name, slot_id):
|
||||
'''
|
||||
This function cancels the non-session TimeSlot. Check for uploaded
|
||||
This function cancels the misc session TimeSlot. Check for uploaded
|
||||
material first. SchedTimeSessAssignment objects get cancelled as well.
|
||||
'''
|
||||
slot = get_object_or_404(TimeSlot, id=slot_id)
|
||||
|
@ -453,18 +460,22 @@ def non_session_cancel(request, meeting_id, schedule_name, slot_id):
|
|||
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
|
||||
|
||||
if request.method == 'POST' and request.POST['post'] == 'yes':
|
||||
assignments = slot.sessionassignments.filter(schedule=schedule)
|
||||
Session.objects.filter(pk__in=[x.session.pk for x in assignments]).update(status_id='canceled')
|
||||
for session in Session.objects.filter(timeslotassignments__schedule=schedule, timeslotassignments__timeslot=slot):
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='canceled'),
|
||||
by=request.user.person,
|
||||
)
|
||||
|
||||
messages.success(request, 'The session was cancelled successfully')
|
||||
return redirect('ietf.secr.meetings.views.non_session', meeting_id=meeting_id, schedule_name=schedule_name)
|
||||
return redirect('ietf.secr.meetings.views.misc_sessions', meeting_id=meeting_id, schedule_name=schedule_name)
|
||||
|
||||
return render(request, 'confirm_cancel.html', {'object': slot})
|
||||
|
||||
@role_required('Secretariat')
|
||||
def non_session_delete(request, meeting_id, schedule_name, slot_id):
|
||||
def misc_session_delete(request, meeting_id, schedule_name, slot_id):
|
||||
'''
|
||||
This function deletes the non-session TimeSlot. Check for uploaded
|
||||
This function deletes the misc session TimeSlot. Check for uploaded
|
||||
material first. SchedTimeSessAssignment objects get deleted as well.
|
||||
'''
|
||||
slot = get_object_or_404(TimeSlot, id=slot_id)
|
||||
|
@ -476,7 +487,7 @@ def non_session_delete(request, meeting_id, schedule_name, slot_id):
|
|||
for session in session_objects:
|
||||
if session.materials.exclude(states__slug='deleted'):
|
||||
messages.error(request, 'Materials have already been uploaded for "%s". You must delete those before deleting the timeslot.' % slot.name)
|
||||
return redirect('ietf.secr.meetings.views.non_session', meeting_id=meeting_id, schedule_name=schedule_name)
|
||||
return redirect('ietf.secr.meetings.views.misc_sessions', meeting_id=meeting_id, schedule_name=schedule_name)
|
||||
|
||||
# delete high order assignments, then sessions and slots
|
||||
assignments.delete()
|
||||
|
@ -484,14 +495,14 @@ def non_session_delete(request, meeting_id, schedule_name, slot_id):
|
|||
slot.delete()
|
||||
|
||||
messages.success(request, 'The entry was deleted successfully')
|
||||
return redirect('ietf.secr.meetings.views.non_session', meeting_id=meeting_id, schedule_name=schedule_name)
|
||||
return redirect('ietf.secr.meetings.views.misc_sessions', meeting_id=meeting_id, schedule_name=schedule_name)
|
||||
|
||||
return render(request, 'confirm_delete.html', {'object': slot})
|
||||
|
||||
@role_required('Secretariat')
|
||||
def non_session_edit(request, meeting_id, schedule_name, slot_id):
|
||||
def misc_session_edit(request, meeting_id, schedule_name, slot_id):
|
||||
'''
|
||||
Allows the user to assign a location to this non-session timeslot
|
||||
Allows the user to assign a location to this misc session timeslot
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_id)
|
||||
slot = get_object_or_404(TimeSlot, id=slot_id)
|
||||
|
@ -501,9 +512,9 @@ def non_session_edit(request, meeting_id, schedule_name, slot_id):
|
|||
if request.method == 'POST':
|
||||
button_text = request.POST.get('submit', '')
|
||||
if button_text == 'Back':
|
||||
return redirect('ietf.secr.meetings.views.non_session', meeting_id=meeting_id, schedule_name=schedule_name)
|
||||
return redirect('ietf.secr.meetings.views.misc_sessions', meeting_id=meeting_id, schedule_name=schedule_name)
|
||||
|
||||
form = NonSessionForm(request.POST,meeting=meeting,session=session)
|
||||
form = MiscSessionForm(request.POST,meeting=meeting,session=session)
|
||||
if form.is_valid():
|
||||
location = form.cleaned_data['location']
|
||||
group = form.cleaned_data['group']
|
||||
|
@ -527,7 +538,7 @@ def non_session_edit(request, meeting_id, schedule_name, slot_id):
|
|||
session.save()
|
||||
|
||||
messages.success(request, 'Location saved')
|
||||
return redirect('ietf.secr.meetings.views.non_session', meeting_id=meeting_id, schedule_name=schedule_name)
|
||||
return redirect('ietf.secr.meetings.views.misc_sessions', meeting_id=meeting_id, schedule_name=schedule_name)
|
||||
|
||||
else:
|
||||
# we need to pass the session to the form in order to disallow changing
|
||||
|
@ -542,9 +553,9 @@ def non_session_edit(request, meeting_id, schedule_name, slot_id):
|
|||
'duration':duration_string(slot.duration),
|
||||
'show_location':slot.show_location,
|
||||
'type':slot.type}
|
||||
form = NonSessionForm(initial=initial, meeting=meeting, session=session)
|
||||
form = MiscSessionForm(initial=initial, meeting=meeting, session=session)
|
||||
|
||||
return render(request, 'meetings/non_session_edit.html', {
|
||||
return render(request, 'meetings/misc_session_edit.html', {
|
||||
'meeting': meeting,
|
||||
'form': form,
|
||||
'schedule': schedule,
|
||||
|
@ -560,7 +571,7 @@ def notifications(request, meeting_id):
|
|||
meeting = get_object_or_404(Meeting, number=meeting_id)
|
||||
last_notice = GroupEvent.objects.filter(type='sent_notification').first()
|
||||
groups = set()
|
||||
for ss in meeting.agenda.assignments.filter(timeslot__type='session'):
|
||||
for ss in meeting.schedule.assignments.filter(timeslot__type='regular'):
|
||||
last_notice = ss.session.group.latest_event(type='sent_notification')
|
||||
if last_notice and ss.modified > last_notice.time:
|
||||
groups.add(ss.session.group)
|
||||
|
@ -569,12 +580,13 @@ def notifications(request, meeting_id):
|
|||
|
||||
if request.method == "POST":
|
||||
# ensure session state is scheduled
|
||||
for ss in meeting.agenda.assignments.all():
|
||||
session = ss.session
|
||||
if session.status.slug in ["schedw", "appr"]:
|
||||
session.status_id = "sched"
|
||||
session.scheduled = datetime.datetime.now()
|
||||
session.save()
|
||||
sessions = add_event_info_to_session_qs(Session.objects.filter(timeslotassignments__schedule=meeting.schedule_id)).filter(current_status__in=["schedw", "appr"])
|
||||
for session in sessions:
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='sched'),
|
||||
by=request.user.person,
|
||||
)
|
||||
send_notifications(meeting,groups,request.user.person)
|
||||
|
||||
messages.success(request, "Notifications Sent")
|
||||
|
@ -632,55 +644,72 @@ def rooms(request, meeting_id, schedule_name):
|
|||
)
|
||||
|
||||
@role_required('Secretariat')
|
||||
def sessions(request, meeting_id, schedule_name):
|
||||
def regular_sessions(request, meeting_id, schedule_name):
|
||||
'''
|
||||
Display and edit Session records for the specified meeting
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_id)
|
||||
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
|
||||
sessions = schedule.sessions_that_can_meet.order_by('group__acronym')
|
||||
|
||||
|
||||
sessions = add_event_info_to_session_qs(
|
||||
only_sessions_that_can_meet(schedule.meeting.session_set)
|
||||
).order_by('group__acronym')
|
||||
|
||||
if request.method == 'POST':
|
||||
if 'cancel' in request.POST:
|
||||
pk = request.POST.get('pk')
|
||||
session = Session.objects.get(pk=pk)
|
||||
session.status = SessionStatusName.objects.get(slug='canceled')
|
||||
session.save()
|
||||
session = get_object_or_404(sessions, pk=pk)
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='canceled'),
|
||||
by=request.user.person,
|
||||
)
|
||||
messages.success(request, 'Session cancelled')
|
||||
|
||||
status_names = {n.slug: n.name for n in SessionStatusName.objects.all()}
|
||||
|
||||
for s in sessions:
|
||||
s.current_status_name = status_names.get(s.current_status, s.current_status)
|
||||
|
||||
return render(request, 'meetings/sessions.html', {
|
||||
'meeting': meeting,
|
||||
'schedule': schedule,
|
||||
'sessions': sessions,
|
||||
'formset': None,
|
||||
'selected': 'sessions',},
|
||||
'selected': 'regular-sessions',},
|
||||
)
|
||||
|
||||
@role_required('Secretariat')
|
||||
def session_edit(request, meeting_id, schedule_name, session_id):
|
||||
def regular_session_edit(request, meeting_id, schedule_name, session_id):
|
||||
'''
|
||||
Edit session details
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_id)
|
||||
schedule = get_object_or_404(Schedule, meeting=meeting, name=schedule_name)
|
||||
session = get_object_or_404(Session, id=session_id)
|
||||
assignment = SchedTimeSessAssignment.objects.get(schedule=schedule,session=session)
|
||||
assignment = SchedTimeSessAssignment.objects.filter(schedule=schedule, session=session).first()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = SessionEditForm(request.POST, instance=session)
|
||||
form = RegularSessionEditForm(request.POST, instance=session)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, 'Session saved')
|
||||
return redirect('ietf.secr.meetings.views.sessions', meeting_id=meeting_id,schedule_name=schedule_name)
|
||||
return redirect('ietf.secr.meetings.views.regular_sessions', meeting_id=meeting_id,schedule_name=schedule_name)
|
||||
|
||||
else:
|
||||
form = SessionEditForm(instance=session)
|
||||
form = RegularSessionEditForm(instance=session)
|
||||
|
||||
return render(request, 'meetings/session_edit.html', {
|
||||
current_status_name = None
|
||||
latest_event = SchedulingEvent.objects.filter(session=session).order_by('-time', '-id').first()
|
||||
if latest_event:
|
||||
current_status_name = latest_event.status.name
|
||||
|
||||
return render(request, 'meetings/regular_session_edit.html', {
|
||||
'meeting': meeting,
|
||||
'schedule': schedule,
|
||||
'session': session,
|
||||
'timeslot': assignment.timeslot,
|
||||
'timeslot': assignment.timeslot if assignment else None,
|
||||
'current_status_name': current_status_name,
|
||||
'form': form},
|
||||
)
|
||||
|
||||
|
@ -700,7 +729,7 @@ def times(request, meeting_id, schedule_name):
|
|||
slots = []
|
||||
timeslots = []
|
||||
time_seen = set()
|
||||
for t in meeting.timeslot_set.filter(type='session'):
|
||||
for t in meeting.timeslot_set.filter(type='regular'):
|
||||
if not t.time in time_seen:
|
||||
time_seen.add(t.time)
|
||||
timeslots.append(t)
|
||||
|
@ -724,7 +753,7 @@ def times(request, meeting_id, schedule_name):
|
|||
return redirect('ietf.secr.meetings.views.times', meeting_id=meeting_id,schedule_name=schedule_name)
|
||||
|
||||
for room in meeting.room_set.all():
|
||||
TimeSlot.objects.create(type_id='session',
|
||||
TimeSlot.objects.create(type_id='regular',
|
||||
meeting=meeting,
|
||||
name=name,
|
||||
time=time,
|
||||
|
@ -821,8 +850,13 @@ def times_delete(request, meeting_id, schedule_name, time):
|
|||
for assignment in slot.sessionassignments.all():
|
||||
if assignment.session:
|
||||
session = assignment.session
|
||||
session.status = status
|
||||
session.save()
|
||||
latest_event = SchedulingEvent.objects.filter(session=session).order_by('-time', '-id').first()
|
||||
if not latest_event or latest_event.status_id != 'schedw':
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=status,
|
||||
by=request.user.person,
|
||||
)
|
||||
assignment.delete()
|
||||
slot.delete()
|
||||
messages.success(request, 'The entry was deleted successfully')
|
||||
|
@ -848,7 +882,7 @@ def view(request, meeting_id):
|
|||
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_id)
|
||||
|
||||
|
||||
return render(request, 'meetings/view.html', {
|
||||
'meeting': meeting},
|
||||
)
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
# Copyright The IETF Trust 2007-2019, All Rights Reserved
|
||||
|
||||
from django import forms
|
||||
|
||||
from ietf.doc.models import Document
|
||||
from ietf.meeting.models import Session
|
||||
from ietf.meeting.utils import add_event_info_to_session_qs
|
||||
|
||||
|
||||
# ---------------------------------------------
|
||||
|
@ -25,8 +27,9 @@ class RecordingForm(forms.Form):
|
|||
def __init__(self, *args, **kwargs):
|
||||
self.meeting = kwargs.pop('meeting')
|
||||
super(RecordingForm, self).__init__(*args,**kwargs)
|
||||
self.fields['session'].queryset = Session.objects.filter(meeting=self.meeting,
|
||||
type__in=('session','plenary','other'),status='sched').order_by('group__acronym')
|
||||
self.fields['session'].queryset = add_event_info_to_session_qs(
|
||||
Session.objects.filter(meeting=self.meeting, type__in=['regular','plenary','other'])
|
||||
).filter(current_status='sched').order_by('group__acronym')
|
||||
|
||||
class RecordingEditForm(forms.ModelForm):
|
||||
class Meta:
|
||||
|
|
|
@ -22,7 +22,7 @@ from django.core.exceptions import ObjectDoesNotExist
|
|||
|
||||
from ietf.doc.models import Document, DocAlias, DocEvent, NewRevisionDocEvent, State
|
||||
from ietf.group.models import Group
|
||||
from ietf.meeting.models import Meeting, SessionPresentation, TimeSlot, SchedTimeSessAssignment
|
||||
from ietf.meeting.models import Meeting, SessionPresentation, TimeSlot, SchedTimeSessAssignment, Session
|
||||
from ietf.person.models import Person
|
||||
from ietf.utils.log import log
|
||||
from ietf.utils.mail import send_mail
|
||||
|
@ -34,7 +34,7 @@ VIDEO_TITLE_RE = re.compile(r'IETF(?P<number>[\d]+)-(?P<name>.*)-(?P<date>\d{8})
|
|||
def _get_session(number,name,date,time):
|
||||
'''Lookup session using data from video title'''
|
||||
meeting = Meeting.objects.get(number=number)
|
||||
schedule = meeting.agenda
|
||||
schedule = meeting.schedule
|
||||
timeslot_time = datetime.datetime.strptime(date + time,'%Y%m%d%H%M')
|
||||
try:
|
||||
assignment = SchedTimeSessAssignment.objects.get(
|
||||
|
@ -65,6 +65,8 @@ def import_audio_files(meeting):
|
|||
|
||||
Example: ietf90-salonb-20140721-1710.mp3
|
||||
'''
|
||||
from ietf.meeting.utils import add_event_info_to_session_qs
|
||||
|
||||
unmatched_files = []
|
||||
path = os.path.join(settings.MEETING_RECORDINGS_DIR, meeting.type.slug + meeting.number)
|
||||
if not os.path.exists(path):
|
||||
|
@ -72,15 +74,18 @@ def import_audio_files(meeting):
|
|||
for filename in os.listdir(path):
|
||||
timeslot = get_timeslot_for_filename(filename)
|
||||
if timeslot:
|
||||
sessionassignments = timeslot.sessionassignments.filter(
|
||||
schedule=timeslot.meeting.agenda,
|
||||
session__status='sched',
|
||||
).exclude(session__agenda_note__icontains='canceled').order_by('timeslot__time')
|
||||
if not sessionassignments:
|
||||
sessions = add_event_info_to_session_qs(Session.objects.filter(
|
||||
timeslotassignments__schedule=timeslot.meeting.schedule_id,
|
||||
).exclude(
|
||||
agenda_note__icontains='canceled'
|
||||
)).filter(
|
||||
current_status='sched',
|
||||
).order_by('timeslotassignments__timeslot__time')
|
||||
if not sessions:
|
||||
continue
|
||||
url = settings.IETF_AUDIO_URL + 'ietf{}/{}'.format(meeting.number, filename)
|
||||
doc = get_or_create_recording_document(url,sessionassignments[0].session)
|
||||
attach_recording(doc, [ x.session for x in sessionassignments ])
|
||||
doc = get_or_create_recording_document(url, sessions[0])
|
||||
attach_recording(doc, sessions)
|
||||
else:
|
||||
# use for reconciliation email
|
||||
unmatched_files.append(filename)
|
||||
|
@ -92,6 +97,8 @@ def get_timeslot_for_filename(filename):
|
|||
'''Returns a timeslot matching the filename given.
|
||||
NOTE: currently only works with ietfNN prefix (regular meetings)
|
||||
'''
|
||||
from ietf.meeting.utils import add_event_info_to_session_qs
|
||||
|
||||
basename, _ = os.path.splitext(filename)
|
||||
match = AUDIO_FILE_RE.match(basename)
|
||||
if match:
|
||||
|
@ -103,10 +110,11 @@ def get_timeslot_for_filename(filename):
|
|||
meeting=meeting,
|
||||
location__name=room_mapping[match.groupdict()['room']],
|
||||
time=time,
|
||||
sessionassignments__schedule=meeting.agenda,
|
||||
).exclude(sessions__status_id='canceled').distinct()
|
||||
return slots.get()
|
||||
except (ObjectDoesNotExist, KeyError):
|
||||
sessionassignments__schedule=meeting.schedule,
|
||||
).distinct()
|
||||
uncancelled_slots = [t for t in slots if not add_event_info_to_session_qs(t.sessions.all()).filter(current_status='canceled').exists()]
|
||||
return uncancelled_slots[0]
|
||||
except (ObjectDoesNotExist, KeyError, IndexError):
|
||||
return None
|
||||
|
||||
def attach_recording(doc, sessions):
|
||||
|
|
|
@ -15,8 +15,9 @@ from django.urls import reverse
|
|||
|
||||
from ietf.doc.models import Document
|
||||
from ietf.group.factories import RoleFactory
|
||||
from ietf.meeting.models import SchedTimeSessAssignment
|
||||
from ietf.meeting.models import SchedTimeSessAssignment, SchedulingEvent
|
||||
from ietf.meeting.factories import MeetingFactory, SessionFactory
|
||||
from ietf.person.models import Person
|
||||
from ietf.name.models import SessionStatusName
|
||||
from ietf.utils.test_utils import TestCase
|
||||
from ietf.utils.mail import outbox
|
||||
|
@ -129,12 +130,18 @@ class RecordingTestCase(TestCase):
|
|||
mars_session = SessionFactory(meeting=meeting,status_id='sched',group__acronym='mars')
|
||||
ames_session = SessionFactory(meeting=meeting,status_id='sched',group__acronym='ames')
|
||||
scheduled = SessionStatusName.objects.get(slug='sched')
|
||||
mars_session.status = scheduled
|
||||
mars_session.save()
|
||||
ames_session.status = scheduled
|
||||
ames_session.save()
|
||||
SchedulingEvent.objects.create(
|
||||
session=mars_session,
|
||||
status=scheduled,
|
||||
by=Person.objects.get(name='(System)')
|
||||
)
|
||||
SchedulingEvent.objects.create(
|
||||
session=ames_session,
|
||||
status=scheduled,
|
||||
by=Person.objects.get(name='(System)')
|
||||
)
|
||||
timeslot = mars_session.official_timeslotassignment().timeslot
|
||||
SchedTimeSessAssignment.objects.create(timeslot=timeslot,session=ames_session,schedule=meeting.agenda)
|
||||
SchedTimeSessAssignment.objects.create(timeslot=timeslot,session=ames_session,schedule=meeting.schedule)
|
||||
self.create_audio_file_for_timeslot(timeslot)
|
||||
import_audio_files(meeting)
|
||||
doc = mars_session.materials.filter(type='recording').first()
|
||||
|
|
|
@ -25,6 +25,7 @@ from ietf.doc.models import Document, DocEvent
|
|||
from ietf.person.models import Person
|
||||
from ietf.ietfauth.utils import has_role, role_required
|
||||
from ietf.meeting.models import Meeting, Session
|
||||
from ietf.meeting.utils import add_event_info_to_session_qs
|
||||
|
||||
from ietf.secr.proceedings.forms import RecordingForm, RecordingEditForm
|
||||
from ietf.secr.proceedings.proc_utils import (create_recording)
|
||||
|
@ -95,7 +96,7 @@ def get_extras(meeting):
|
|||
sessions = Session.objects.filter(meeting=meeting).exclude(group__parent__type__in=('area','irtf'))
|
||||
for session in sessions:
|
||||
timeslot = get_timeslot(session)
|
||||
if timeslot and timeslot.type.slug == 'session' and session.materials.all():
|
||||
if timeslot and timeslot.type_id == 'regular' and session.materials.all():
|
||||
groups.append(session.group)
|
||||
return groups
|
||||
|
||||
|
@ -170,7 +171,8 @@ def main(request):
|
|||
meetings = [m for m in Meeting.objects.filter(type='ietf').order_by('-number') if m.get_submission_correction_date()>=today]
|
||||
|
||||
groups = get_my_groups(request.user)
|
||||
interim_meetings = Meeting.objects.filter(type='interim',session__group__in=groups,session__status='sched').order_by('-date')
|
||||
interim_sessions = add_event_info_to_session_qs(Session.objects.filter(group__in=groups, meeting__type='interim')).filter(current_status='sched').select_related('meeting')
|
||||
interim_meetings = sorted({s.meeting for s in interim_sessions}, key=lambda m: m.date, reverse=True)
|
||||
# tac on group for use in templates
|
||||
for m in interim_meetings:
|
||||
m.group = m.session_set.first().group
|
||||
|
@ -232,7 +234,7 @@ def recording(request, meeting_num):
|
|||
session.
|
||||
'''
|
||||
meeting = get_object_or_404(Meeting, number=meeting_num)
|
||||
assignments = meeting.agenda.assignments.exclude(session__type__in=('reg','break')).order_by('session__group__acronym')
|
||||
assignments = meeting.schedule.assignments.exclude(session__type__in=('reg','break')).order_by('session__group__acronym')
|
||||
sessions = [ x.session for x in assignments ]
|
||||
|
||||
if request.method == 'POST':
|
||||
|
|
|
@ -13,7 +13,7 @@ import debug # pyflakes:ignore
|
|||
|
||||
from ietf.utils.test_utils import TestCase
|
||||
from ietf.group.factories import GroupFactory, RoleFactory
|
||||
from ietf.meeting.models import Session, ResourceAssociation
|
||||
from ietf.meeting.models import Session, ResourceAssociation, SchedulingEvent
|
||||
from ietf.meeting.factories import MeetingFactory, SessionFactory
|
||||
from ietf.person.models import Person
|
||||
from ietf.utils.mail import outbox, empty_outbox
|
||||
|
@ -42,16 +42,16 @@ class SessionRequestTestCase(TestCase):
|
|||
def test_main(self):
|
||||
meeting = MeetingFactory(type_id='ietf', date=datetime.date.today())
|
||||
SessionFactory.create_batch(2, meeting=meeting, status_id='sched')
|
||||
SessionFactory.create_batch(2, meeting=meeting, status_id='unsched')
|
||||
SessionFactory.create_batch(2, meeting=meeting, status_id='disappr')
|
||||
# An additional unscheduled group comes from make_immutable_base_data
|
||||
url = reverse('ietf.secr.sreq.views.main')
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
sched = r.context['scheduled_groups']
|
||||
self.assertEqual(len(sched), 2)
|
||||
unsched = r.context['unscheduled_groups']
|
||||
self.assertEqual(len(unsched),8)
|
||||
self.assertEqual(len(sched),2)
|
||||
self.assertEqual(len(unsched), 8)
|
||||
|
||||
def test_approve(self):
|
||||
meeting = MeetingFactory(type_id='ietf', date=datetime.date.today())
|
||||
|
@ -64,25 +64,23 @@ class SessionRequestTestCase(TestCase):
|
|||
self.client.login(username="ad", password="ad+password")
|
||||
r = self.client.get(url)
|
||||
self.assertRedirects(r,reverse('ietf.secr.sreq.views.view', kwargs={'acronym':'mars'}))
|
||||
session = Session.objects.get(pk=session.pk)
|
||||
self.assertEqual(session.status_id,'appr')
|
||||
self.assertEqual(SchedulingEvent.objects.filter(session=session).order_by('-id')[0].status_id, 'appr')
|
||||
|
||||
def test_cancel(self):
|
||||
meeting = MeetingFactory(type_id='ietf', date=datetime.date.today())
|
||||
ad = Person.objects.get(user__username='ad')
|
||||
area = RoleFactory(name_id='ad', person=ad, group__type_id='area').group
|
||||
mars = SessionFactory(meeting=meeting, group__parent=area, group__acronym='mars', status_id='sched').group
|
||||
session = SessionFactory(meeting=meeting, group__parent=area, group__acronym='mars', status_id='sched')
|
||||
url = reverse('ietf.secr.sreq.views.cancel', kwargs={'acronym':'mars'})
|
||||
self.client.login(username="ad", password="ad+password")
|
||||
r = self.client.get(url)
|
||||
self.assertRedirects(r,reverse('ietf.secr.sreq.views.main'))
|
||||
sessions = Session.objects.filter(meeting=meeting, group=mars)
|
||||
self.assertEqual(sessions[0].status_id,'deleted')
|
||||
|
||||
self.assertEqual(SchedulingEvent.objects.filter(session=session).order_by('-id')[0].status_id, 'deleted')
|
||||
|
||||
def test_edit(self):
|
||||
meeting = MeetingFactory(type_id='ietf', date=datetime.date.today())
|
||||
mars = RoleFactory(name_id='chair', person__user__username='marschairman', group__acronym='mars').group
|
||||
SessionFactory(meeting=meeting,group=mars,status_id='sched',scheduled=datetime.datetime.now())
|
||||
SessionFactory(meeting=meeting,group=mars,status_id='sched')
|
||||
|
||||
url = reverse('ietf.secr.sreq.views.edit', kwargs={'acronym':'mars'})
|
||||
self.client.login(username="marschairman", password="marschairman+password")
|
||||
|
@ -129,14 +127,14 @@ class SubmitRequestCase(TestCase):
|
|||
post_data['submit'] = 'Submit'
|
||||
r = self.client.post(confirm_url,post_data)
|
||||
self.assertRedirects(r, main_url)
|
||||
session_count_after = Session.objects.filter(meeting=meeting, group=group).count()
|
||||
self.assertTrue(session_count_after == session_count_before + 1)
|
||||
session_count_after = Session.objects.filter(meeting=meeting, group=group, type='regular').count()
|
||||
self.assertEqual(session_count_after, session_count_before + 1)
|
||||
|
||||
# test that second confirm does not add sessions
|
||||
r = self.client.post(confirm_url,post_data)
|
||||
self.assertRedirects(r, main_url)
|
||||
session_count_after = Session.objects.filter(meeting=meeting, group=group).count()
|
||||
self.assertTrue(session_count_after == session_count_before + 1)
|
||||
session_count_after = Session.objects.filter(meeting=meeting, group=group, type='regular').count()
|
||||
self.assertEqual(session_count_after, session_count_before + 1)
|
||||
|
||||
def test_submit_request_invalid(self):
|
||||
MeetingFactory(type_id='ietf', date=datetime.date.today())
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# Copyright The IETF Trust 2007-2019, All Rights Reserved
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from ietf.secr.sreq import views
|
||||
|
@ -14,5 +16,5 @@ urlpatterns = [
|
|||
url(r'^%(acronym)s/edit/$' % settings.URL_REGEXPS, views.edit),
|
||||
url(r'^%(acronym)s/new/$' % settings.URL_REGEXPS, views.new),
|
||||
url(r'^%(acronym)s/no_session/$' % settings.URL_REGEXPS, views.no_session),
|
||||
url(r'^(?P<num>[A-Za-z0-9_\-\+]+)/%(acronym)s/edit/$' % settings.URL_REGEXPS, views.edit_mtg),
|
||||
url(r'^(?P<num>[A-Za-z0-9_\-\+]+)/%(acronym)s/edit/$' % settings.URL_REGEXPS, views.edit),
|
||||
]
|
||||
|
|
|
@ -5,22 +5,25 @@
|
|||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import datetime
|
||||
from collections import defaultdict
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.http import Http404
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.group.models import Group
|
||||
from ietf.group.models import Group, GroupFeatures
|
||||
from ietf.ietfauth.utils import has_role, role_required
|
||||
from ietf.meeting.models import Meeting, Session, Constraint, ResourceAssociation
|
||||
from ietf.meeting.models import Meeting, Session, Constraint, ResourceAssociation, SchedulingEvent
|
||||
from ietf.meeting.helpers import get_meeting
|
||||
from ietf.meeting.utils import add_event_info_to_session_qs
|
||||
from ietf.name.models import SessionStatusName, ConstraintName
|
||||
from ietf.secr.sreq.forms import SessionForm, ToolStatusForm
|
||||
from ietf.secr.utils.decorators import check_permissions
|
||||
from ietf.secr.utils.group import groups_by_session
|
||||
from ietf.secr.utils.group import get_my_groups
|
||||
from ietf.utils.mail import send_mail
|
||||
from ietf.person.models import Person
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
@ -171,11 +174,18 @@ def approve(request, acronym):
|
|||
'''
|
||||
meeting = get_meeting()
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
session = Session.objects.get(meeting=meeting,group=group,status='apprw')
|
||||
|
||||
session = add_event_info_to_session_qs(Session.objects.filter(meeting=meeting, group=group)).filter(current_status='apprw').first()
|
||||
if session is None:
|
||||
raise Http404
|
||||
|
||||
if has_role(request.user,'Secretariat') or group.parent.role_set.filter(name='ad',person=request.user.person):
|
||||
session.status = SessionStatusName.objects.get(slug='appr')
|
||||
session_save(session)
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='appr'),
|
||||
by=request.user.person,
|
||||
)
|
||||
session_changed(session)
|
||||
|
||||
messages.success(request, 'Third session approved')
|
||||
return redirect('ietf.secr.sreq.views.view', acronym=acronym)
|
||||
|
@ -205,8 +215,12 @@ def cancel(request, acronym):
|
|||
|
||||
# mark sessions as deleted
|
||||
for session in sessions:
|
||||
session.status_id = 'deleted'
|
||||
session_save(session)
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='deleted'),
|
||||
by=request.user.person,
|
||||
)
|
||||
session_changed(session)
|
||||
|
||||
# clear schedule assignments if already scheduled
|
||||
session.timeslotassignments.all().delete()
|
||||
|
@ -236,7 +250,7 @@ def confirm(request, acronym):
|
|||
login = request.user.person
|
||||
|
||||
# check if request already exists for this group
|
||||
if Session.objects.filter(group=group,meeting=meeting).exclude(status__in=('deleted','notmeet')):
|
||||
if add_event_info_to_session_qs(Session.objects.filter(group=group, meeting=meeting)).filter(Q(current_status__isnull=True) | ~Q(current_status__in=['deleted', 'notmeet'])):
|
||||
messages.warning(request, 'Sessions for working group %s have already been requested once.' % group.acronym)
|
||||
return redirect('ietf.secr.sreq.views.main')
|
||||
|
||||
|
@ -258,7 +272,7 @@ def confirm(request, acronym):
|
|||
|
||||
if request.method == 'POST' and button_text == 'Submit':
|
||||
# delete any existing session records with status = canceled or notmeet
|
||||
Session.objects.filter(group=group,meeting=meeting,status__in=('canceled','notmeet')).delete()
|
||||
add_event_info_to_session_qs(Session.objects.filter(group=group, meeting=meeting)).filter(current_status__in=['canceled', 'notmeet']).delete()
|
||||
|
||||
# create new session records
|
||||
count = 0
|
||||
|
@ -268,19 +282,22 @@ def confirm(request, acronym):
|
|||
count += 1
|
||||
if duration:
|
||||
slug = 'apprw' if count == 3 else 'schedw'
|
||||
new_session = Session(meeting=meeting,
|
||||
group=group,
|
||||
attendees=form.data['attendees'],
|
||||
requested=datetime.datetime.now(),
|
||||
requested_by=login,
|
||||
requested_duration=datetime.timedelta(0,int(duration)),
|
||||
comments=form.data['comments'],
|
||||
status=SessionStatusName.objects.get(slug=slug),
|
||||
type_id='session',
|
||||
)
|
||||
session_save(new_session)
|
||||
new_session = Session.objects.create(
|
||||
meeting=meeting,
|
||||
group=group,
|
||||
attendees=form.data['attendees'],
|
||||
requested_duration=datetime.timedelta(0,int(duration)),
|
||||
comments=form.data['comments'],
|
||||
type_id='regular',
|
||||
)
|
||||
SchedulingEvent.objects.create(
|
||||
session=new_session,
|
||||
status=SessionStatusName.objects.get(slug=slug),
|
||||
by=login,
|
||||
)
|
||||
if 'resources' in form.data:
|
||||
new_session.resources.set(session_data['resources'])
|
||||
session_changed(new_session)
|
||||
|
||||
# write constraint records
|
||||
save_conflicts(group,meeting,form.data.get('conflict1',''),'conflict')
|
||||
|
@ -293,7 +310,7 @@ def confirm(request, acronym):
|
|||
Constraint.objects.create(name=bethere_cn, source=group, person=p, meeting=new_session.meeting)
|
||||
|
||||
# clear not meeting
|
||||
Session.objects.filter(group=group,meeting=meeting,status='notmeet').delete()
|
||||
add_event_info_to_session_qs(Session.objects.filter(group=group, meeting=meeting)).filter(current_status='notmeet').delete()
|
||||
|
||||
# send notification
|
||||
send_notification(group,meeting,login,session_data,'new')
|
||||
|
@ -322,23 +339,21 @@ def add_essential_people(group,initial):
|
|||
initial['bethere'] = list(people)
|
||||
|
||||
|
||||
def edit(request, *args, **kwargs):
|
||||
return edit_mtg(request, None, *args, **kwargs)
|
||||
def session_changed(session):
|
||||
latest_event = SchedulingEvent.objects.filter(session=session).order_by('-time', '-id').first()
|
||||
|
||||
def session_save(session):
|
||||
session.save()
|
||||
if session.status_id == "schedw" and session.meeting.agenda != None:
|
||||
if latest_event and latest_event.status_id == "schedw" and session.meeting.schedule != None:
|
||||
# send an email to iesg-secretariat to alert to change
|
||||
pass
|
||||
|
||||
@check_permissions
|
||||
def edit_mtg(request, num, acronym):
|
||||
def edit(request, acronym, num=None):
|
||||
'''
|
||||
This view allows the user to edit details of the session request
|
||||
'''
|
||||
meeting = get_meeting(num)
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
sessions = Session.objects.filter(meeting=meeting,group=group).exclude(status__in=('deleted','notmeet')).order_by('id')
|
||||
sessions = add_event_info_to_session_qs(Session.objects.filter(group=group, meeting=meeting)).filter(Q(current_status__isnull=True) | ~Q(current_status__in=['canceled', 'notmeet'])).order_by('id')
|
||||
sessions_count = sessions.count()
|
||||
initial = get_initial_session(sessions)
|
||||
if 'resources' in initial:
|
||||
|
@ -370,7 +385,8 @@ def edit_mtg(request, num, acronym):
|
|||
if 'length_session1' in form.changed_data:
|
||||
session = sessions[0]
|
||||
session.requested_duration = datetime.timedelta(0,int(form.cleaned_data['length_session1']))
|
||||
session_save(session)
|
||||
session.save()
|
||||
session_changed(session)
|
||||
|
||||
# session 2
|
||||
if 'length_session2' in form.changed_data:
|
||||
|
@ -379,22 +395,24 @@ def edit_mtg(request, num, acronym):
|
|||
sessions[1].delete()
|
||||
elif sessions_count < 2:
|
||||
duration = datetime.timedelta(0,int(form.cleaned_data['length_session2']))
|
||||
new_session = Session(meeting=meeting,
|
||||
group=group,
|
||||
attendees=form.cleaned_data['attendees'],
|
||||
requested=datetime.datetime.now(),
|
||||
requested_by=login,
|
||||
requested_duration=duration,
|
||||
comments=form.cleaned_data['comments'],
|
||||
status=SessionStatusName.objects.get(slug='schedw'),
|
||||
type_id='session',
|
||||
)
|
||||
new_session.save()
|
||||
new_session = Session.objects.create(
|
||||
meeting=meeting,
|
||||
group=group,
|
||||
attendees=form.cleaned_data['attendees'],
|
||||
requested_duration=duration,
|
||||
comments=form.cleaned_data['comments'],
|
||||
type_id='regular',
|
||||
)
|
||||
SchedulingEvent.objects.create(
|
||||
session=new_session,
|
||||
status=SessionStatusName.objects.get(slug='schedw'),
|
||||
by=request.user.person,
|
||||
)
|
||||
else:
|
||||
duration = datetime.timedelta(0,int(form.cleaned_data['length_session2']))
|
||||
session = sessions[1]
|
||||
session.requested_duration = duration
|
||||
session_save(session)
|
||||
session.save()
|
||||
|
||||
# session 3
|
||||
if 'length_session3' in form.changed_data:
|
||||
|
@ -403,22 +421,25 @@ def edit_mtg(request, num, acronym):
|
|||
sessions[2].delete()
|
||||
elif sessions_count < 3:
|
||||
duration = datetime.timedelta(0,int(form.cleaned_data['length_session3']))
|
||||
new_session = Session(meeting=meeting,
|
||||
group=group,
|
||||
attendees=form.cleaned_data['attendees'],
|
||||
requested=datetime.datetime.now(),
|
||||
requested_by=login,
|
||||
requested_duration=duration,
|
||||
comments=form.cleaned_data['comments'],
|
||||
status=SessionStatusName.objects.get(slug='apprw'),
|
||||
type_id='session',
|
||||
)
|
||||
new_session.save()
|
||||
new_session = Session.objects.create(
|
||||
meeting=meeting,
|
||||
group=group,
|
||||
attendees=form.cleaned_data['attendees'],
|
||||
requested_duration=duration,
|
||||
comments=form.cleaned_data['comments'],
|
||||
type_id='regular',
|
||||
)
|
||||
SchedulingEvent.objects.create(
|
||||
session=new_session,
|
||||
status=SessionStatusName.objects.get(slug='apprw'),
|
||||
by=request.user.person,
|
||||
)
|
||||
else:
|
||||
duration = datetime.timedelta(0,int(form.cleaned_data['length_session3']))
|
||||
session = sessions[2]
|
||||
session.requested_duration = duration
|
||||
session_save(session)
|
||||
session.save()
|
||||
session_changed(session)
|
||||
|
||||
|
||||
if 'attendees' in form.changed_data:
|
||||
|
@ -495,7 +516,28 @@ def main(request):
|
|||
|
||||
meeting = get_meeting()
|
||||
|
||||
scheduled_groups, unscheduled_groups = groups_by_session(request.user, meeting)
|
||||
scheduled_groups = []
|
||||
unscheduled_groups = []
|
||||
|
||||
group_types = GroupFeatures.objects.filter(has_meetings=True).values_list('type', flat=True)
|
||||
|
||||
my_groups = [g for g in get_my_groups(request.user, conclude=True) if g.type_id in group_types]
|
||||
|
||||
sessions_by_group = defaultdict(list)
|
||||
for s in add_event_info_to_session_qs(Session.objects.filter(meeting=meeting, group__in=my_groups)).filter(current_status__in=['schedw', 'apprw', 'appr', 'sched']):
|
||||
sessions_by_group[s.group_id].append(s)
|
||||
|
||||
for group in my_groups:
|
||||
group.meeting_sessions = sessions_by_group.get(group.pk, [])
|
||||
|
||||
if group.pk in sessions_by_group:
|
||||
# include even if concluded as we need to to see that the
|
||||
# sessions are there
|
||||
scheduled_groups.append(group)
|
||||
else:
|
||||
if group.state_id not in ['conclude', 'bof-conc']:
|
||||
# too late for unscheduled if concluded
|
||||
unscheduled_groups.append(group)
|
||||
|
||||
# warn if there are no associated groups
|
||||
if not scheduled_groups and not unscheduled_groups:
|
||||
|
@ -503,15 +545,14 @@ def main(request):
|
|||
|
||||
# add session status messages for use in template
|
||||
for group in scheduled_groups:
|
||||
sessions = group.session_set.filter(meeting=meeting)
|
||||
if sessions.count() < 3:
|
||||
group.status_message = sessions[0].status
|
||||
if len(group.meeting_sessions) < 3:
|
||||
group.status_message = group.meeting_sessions[0].current_status
|
||||
else:
|
||||
group.status_message = 'First two sessions: %s, Third session: %s' % (sessions[0].status,sessions[2].status)
|
||||
group.status_message = 'First two sessions: %s, Third session: %s' % (group.meeting_sessions[0].current_status, group.meeting_sessions[2].current_status)
|
||||
|
||||
# add not meeting indicators for use in template
|
||||
for group in unscheduled_groups:
|
||||
if group.session_set.filter(meeting=meeting,status='notmeet'):
|
||||
if any(s.current_status == 'notmeet' for s in group.meeting_sessions):
|
||||
group.not_meeting = True
|
||||
|
||||
return render(request, 'sreq/main.html', {
|
||||
|
@ -550,7 +591,7 @@ def new(request, acronym):
|
|||
# pre-populated with data from last meeeting's session request
|
||||
elif request.method == 'GET' and 'previous' in request.GET:
|
||||
previous_meeting = Meeting.objects.get(number=str(int(meeting.number) - 1))
|
||||
previous_sessions = Session.objects.filter(meeting=previous_meeting,group=group).exclude(status__in=('notmeet','deleted')).order_by('id')
|
||||
previous_sessions = add_event_info_to_session_qs(Session.objects.filter(meeting=previous_meeting, group=group)).exclude(current_status__in=['notmeet', 'deleted']).order_by('id')
|
||||
if not previous_sessions:
|
||||
messages.warning(request, 'This group did not meet at %s' % previous_meeting)
|
||||
return redirect('ietf.secr.sreq.views.new', acronym=acronym)
|
||||
|
@ -586,22 +627,25 @@ def no_session(request, acronym):
|
|||
login = request.user.person
|
||||
|
||||
# delete canceled record if there is one
|
||||
Session.objects.filter(group=group,meeting=meeting,status='canceled').delete()
|
||||
add_event_info_to_session_qs(Session.objects.filter(group=group, meeting=meeting)).filter(current_status='canceled').delete()
|
||||
|
||||
# skip if state is already notmeet
|
||||
if Session.objects.filter(group=group,meeting=meeting,status='notmeet'):
|
||||
if add_event_info_to_session_qs(Session.objects.filter(group=group, meeting=meeting)).filter(current_status='notmeet'):
|
||||
messages.info(request, 'The group %s is already marked as not meeting' % group.acronym)
|
||||
return redirect('ietf.secr.sreq.views.main')
|
||||
|
||||
session = Session(group=group,
|
||||
meeting=meeting,
|
||||
requested=datetime.datetime.now(),
|
||||
requested_by=login,
|
||||
requested_duration=datetime.timedelta(0),
|
||||
status=SessionStatusName.objects.get(slug='notmeet'),
|
||||
type_id='session',
|
||||
)
|
||||
session_save(session)
|
||||
session = Session.objects.create(
|
||||
group=group,
|
||||
meeting=meeting,
|
||||
requested_duration=datetime.timedelta(0),
|
||||
type_id='regular',
|
||||
)
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='notmeet'),
|
||||
by=login,
|
||||
)
|
||||
session_changed(session)
|
||||
|
||||
# send notification
|
||||
(to_email, cc_list) = gather_address_lists('session_request_not_meeting',group=group,person=login)
|
||||
|
@ -669,7 +713,7 @@ def view(request, acronym, num = None):
|
|||
'''
|
||||
meeting = get_meeting(num)
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
sessions = Session.objects.filter(~Q(status__in=('canceled','notmeet','deleted')),meeting=meeting,group=group).order_by('id')
|
||||
sessions = add_event_info_to_session_qs(Session.objects.filter(meeting=meeting, group=group)).filter(Q(current_status__isnull=True) | ~Q(current_status__in=('canceled','notmeet','deleted'))).order_by('id')
|
||||
|
||||
# check if app is locked
|
||||
is_locked = check_app_locked()
|
||||
|
@ -683,16 +727,12 @@ def view(request, acronym, num = None):
|
|||
else:
|
||||
return redirect('ietf.secr.sreq.views.new', acronym=acronym)
|
||||
|
||||
# TODO simulate activity records
|
||||
activities = [{'act_date':sessions[0].requested.strftime('%b %d, %Y'),
|
||||
'act_time':sessions[0].requested.strftime('%H:%M:%S'),
|
||||
'activity':'New session was requested',
|
||||
'act_by':sessions[0].requested_by}]
|
||||
if sessions[0].scheduled:
|
||||
activities.append({'act_date':sessions[0].scheduled.strftime('%b %d, %Y'),
|
||||
'act_time':sessions[0].scheduled.strftime('%H:%M:%S'),
|
||||
'activity':'Session was scheduled',
|
||||
'act_by':'Secretariat'})
|
||||
activities = [{
|
||||
'act_date': e.time.strftime('%b %d, %Y'),
|
||||
'act_time': e.time.strftime('%H:%M:%S'),
|
||||
'activity': e.status.name,
|
||||
'act_by': e.by,
|
||||
} for e in sessions[0].schedulingevent_set.select_related('status', 'by')]
|
||||
|
||||
# other groups that list this group in their conflicts
|
||||
session_conflicts = session_conflicts_as_string(group, meeting)
|
||||
|
@ -700,7 +740,7 @@ def view(request, acronym, num = None):
|
|||
|
||||
# if sessions include a 3rd session waiting approval and the user is a secretariat or AD of the group
|
||||
# display approve button
|
||||
if sessions.filter(status='apprw'):
|
||||
if any(s.current_status == 'apprw' for s in sessions):
|
||||
if has_role(request.user,'Secretariat') or group.parent.role_set.filter(name='ad',person=request.user.person):
|
||||
show_approve_button = True
|
||||
|
||||
|
|
|
@ -461,7 +461,7 @@ input.draft-file-input {
|
|||
Meeting Tool
|
||||
========================================================================== */
|
||||
|
||||
#non-session-edit-form input[type="text"] {
|
||||
#misc-session-edit-form input[type="text"] {
|
||||
width: 30em;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
<ul class="list-nav">
|
||||
<li id="nav-room" class="leftmost{% if selected == 'rooms' %} selected{% endif %}"><a href="{% url "ietf.secr.meetings.views.rooms" meeting_id=meeting.number schedule_name=schedule.name %}">Rooms</a></li>
|
||||
<li id="nav-time" class="{% if selected == 'times' %}selected{% endif %}"><a href="{% url "ietf.secr.meetings.views.times" meeting_id=meeting.number schedule_name=schedule.name %}">Times</a></li>
|
||||
<li id="nav-non-session" class="{% if selected == 'non-sessions' %}selected{% endif %}"><a href="{% url "ietf.secr.meetings.views.non_session" meeting_id=meeting.number schedule_name=schedule.name %}">Non-Session</a></li>
|
||||
<li id="nav-session" class="{% if selected == 'sessions' %}selected{% endif %}"><a href="{% url "ietf.secr.meetings.views.sessions" meeting_id=meeting.number schedule_name=schedule.name %}">Sessions</a></li>
|
||||
<li id="nav-misc-session" class="{% if selected == 'misc-sessions' %}selected{% endif %}"><a href="{% url "ietf.secr.meetings.views.misc_sessions" meeting_id=meeting.number schedule_name=schedule.name %}">Misc. Sessions</a></li>
|
||||
<li id="nav-session" class="{% if selected == 'regular-sessions' %}selected{% endif %}"><a href="{% url "ietf.secr.meetings.views.regular_sessions" meeting_id=meeting.number schedule_name=schedule.name %}">Regular Sessions</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<div class="module interim-container">
|
||||
<h2>Meeting - {{ meeting }}</h2>
|
||||
<p><h3>Session: {{ slot.name }}</h3></p>
|
||||
<form id="non-session-edit-form" enctype="multipart/form-data" action="." method="post">{% csrf_token %}
|
||||
<form id="misc-session-edit-form" enctype="multipart/form-data" action="." method="post">{% csrf_token %}
|
||||
<table class="full-width amstable">
|
||||
{{ form.as_table }}
|
||||
</table>
|
|
@ -6,7 +6,7 @@
|
|||
<h2>TimeSlots</h2>
|
||||
|
||||
{% if assignments %}
|
||||
<table id="nonsessions" class="full-width">
|
||||
<table id="misc-sessions" class="full-width">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Day</th>
|
||||
|
@ -24,7 +24,7 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
{% for assignment in assignments %}
|
||||
<tr class="{% cycle 'row1' 'row2' %}{% ifchanged assignment.session.type %} break{% endifchanged %}{% if assignment.session.status.slug == "canceled" %} cancelled{% endif %}">
|
||||
<tr class="{% cycle 'row1' 'row2' %}{% ifchanged assignment.session.type %} break{% endifchanged %}{% if assignment.current_session_status == "canceled" %} cancelled{% endif %}">
|
||||
<td>{{ assignment.timeslot.time|date:"D" }}</td>
|
||||
<td>{{ assignment.timeslot.time|date:"H:i" }}-{{ assignment.timeslot.end_time|date:"H:i" }}</td>
|
||||
<td>{{ assignment.timeslot.name }}</td>
|
||||
|
@ -33,13 +33,13 @@
|
|||
<td>{{ assignment.timeslot.location }}</td>
|
||||
<td>{{ assignment.timeslot.show_location }}</td>
|
||||
<td>{{ assignment.timeslot.type }}</td>
|
||||
<td><a href="{% url "ietf.secr.meetings.views.non_session_edit" meeting_id=meeting.number schedule_name=schedule.name slot_id=assignment.timeslot.id %}">Edit</a></td>
|
||||
<td><a href="{% url "ietf.secr.meetings.views.misc_session_edit" meeting_id=meeting.number schedule_name=schedule.name slot_id=assignment.timeslot.id %}">Edit</a></td>
|
||||
<td>
|
||||
{% if not assignment.session.type.slug == "break" %}
|
||||
<a href="{% url "ietf.secr.meetings.views.non_session_cancel" meeting_id=meeting.number schedule_name=schedule.name slot_id=assignment.timeslot.id %}">Cancel</a>
|
||||
<a href="{% url "ietf.secr.meetings.views.misc_session_cancel" meeting_id=meeting.number schedule_name=schedule.name slot_id=assignment.timeslot.id %}">Cancel</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><a href="{% url "ietf.secr.meetings.views.non_session_delete" meeting_id=meeting.number schedule_name=schedule.name slot_id=assignment.timeslot.id %}">Delete</a></td>
|
||||
<td><a href="{% url "ietf.secr.meetings.views.misc_session_delete" meeting_id=meeting.number schedule_name=schedule.name slot_id=assignment.timeslot.id %}">Delete</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
|
@ -23,19 +23,19 @@
|
|||
<col width="200">
|
||||
<tr>
|
||||
<th>Day:</th>
|
||||
<td>{{ timeslot.time|date:"l" }}</td>
|
||||
<td>{% if timeslot %}{{ timeslot.time|date:"l" }}{% endif %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Time:</th>
|
||||
<td>{{ timeslot.time|time:"H:i" }}</td>
|
||||
<td>{% if timeslot %}{{ timeslot.time|time:"H:i" }}{% endif %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Room:</th>
|
||||
<td>{{ timeslot.location.name }}</td>
|
||||
<td>{% if timeslot %}{{ timeslot.location.name }}{% endif %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status:</th>
|
||||
<td>{{ session.status }}</td>
|
||||
<td>{{ current_status_name }}</td>
|
||||
</tr>
|
||||
{{ form }}
|
||||
</table>
|
||||
|
@ -45,7 +45,7 @@
|
|||
<div class="button-group">
|
||||
<ul>
|
||||
<li><button type="submit" name="submit" value="Save">Save</button></li>
|
||||
<li><button type="button" onclick="window.location='{% url "ietf.secr.meetings.views.sessions" meeting_id=meeting.number schedule_name=schedule.name %}'">Back</button></li>
|
||||
<li><button type="button" onclick="window.location='{% url "ietf.secr.meetings.views.regular_sessions" meeting_id=meeting.number schedule_name=schedule.name %}'">Back</button></li>
|
||||
</ul>
|
||||
</div> <!-- button-group -->
|
||||
|
|
@ -33,8 +33,8 @@
|
|||
{% endif %}
|
||||
</td>
|
||||
<td>{{ session.agenda_note }}</td>
|
||||
<td>{{ session.status }}</td>
|
||||
<td><a href="{% url 'ietf.secr.meetings.views.session_edit' meeting_id=meeting.number schedule_name=schedule.name session_id=session.id %}">Edit</a></td>
|
||||
<td>{{ session.current_status_name }}</td>
|
||||
<td><a href="{% url 'ietf.secr.meetings.views.regular_session_edit' meeting_id=meeting.number schedule_name=schedule.name session_id=session.id %}">Edit</a></td>
|
||||
<td>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
|
|
@ -37,18 +37,18 @@
|
|||
<li><button onclick="window.location='{% url "ietf.secr.meetings.views.edit_meeting" meeting_id=meeting.number %}'">Edit</button></li>
|
||||
<li><button onclick="window.location='{% url "ietf.secr.meetings.views.notifications" meeting_id=meeting.number %}'">Notifications</button></li>
|
||||
<li><button onclick="window.location='{% url "ietf.secr.meetings.views.blue_sheet" meeting_id=meeting.number %}'">Blue Sheets</button></li>
|
||||
<li><button onclick="window.location='{% url "ietf.meeting.views.list_agendas" num=meeting.number %}'">Agenda List</button></li>
|
||||
<li><button onclick="window.location='{% url "ietf.meeting.views.list_schedules" num=meeting.number %}'">Agenda List</button></li>
|
||||
<li>
|
||||
<form id="id_schedule_selector">
|
||||
<select name="forma" onchange="location = this.options[this.selectedIndex].value;">
|
||||
<option value="">Select a schedule...</option>
|
||||
{% for sched in meeting.schedule_set.all %}
|
||||
<option value="{{ sched.name }}">{{ sched.name }}{% if sched == meeting.agenda %} (official){% endif %}</option>
|
||||
<option value="{{ sched.name }}">{{ sched.name }}{% if sched == meeting.schedule %} (official){% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
</li>
|
||||
<li><button onclick="window.location='{% url "ietf.secr.meetings.views.rooms" meeting_id=meeting.number schedule_name=meeting.agenda.name %}'">Official Schedule</button></li>
|
||||
<li><button onclick="window.location='{% url "ietf.secr.meetings.views.rooms" meeting_id=meeting.number schedule_name=meeting.schedule.name %}'">Official Schedule</button></li>
|
||||
</ul>
|
||||
</div> <!-- button-group -->
|
||||
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
<tr class="{% cycle 'row1' 'row2' %}">
|
||||
<td align="left" style="white-space: nowrap">{{ meeting.date }}</td>
|
||||
<td><a href="https://datatracker.ietf.org/wg/{{ meeting.group.acronym }}">{{ meeting.group.acronym }}</a></td>
|
||||
{% if meeting.agenda %}
|
||||
<td width="70" align="center"><a href="{{ meeting.agenda.get_absolute_url }}">Agenda</a></td>
|
||||
{% if meeting.schedule %}
|
||||
<td width="70" align="center"><a href="{{ meeting.schedule.get_absolute_url }}">Agenda</a></td>
|
||||
{% else %}
|
||||
<td width="70" align="center">Agenda</td>
|
||||
{% endif %}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<div class="button-group">
|
||||
<ul>
|
||||
<li><button name="edit" onclick="window.location='{% url "ietf.secr.sreq.views.edit_mtg" acronym=group.acronym num=meeting.number %}'"{% if is_locked %} disabled{% endif %}>Edit</button></li>
|
||||
<li><button name="edit" onclick="window.location='{% url "ietf.secr.sreq.views.edit" acronym=group.acronym num=meeting.number %}'"{% if is_locked %} disabled{% endif %}>Edit</button></li>
|
||||
{% if show_approve_button %}
|
||||
<li><button onclick="window.location='approve/'">Approve Third Session</button></li>
|
||||
{% endif %}
|
||||
|
|
|
@ -13,8 +13,7 @@ from django.conf import settings
|
|||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
# Datatracker imports
|
||||
from ietf.group.models import Group, GroupFeatures
|
||||
from ietf.meeting.models import Session
|
||||
from ietf.group.models import Group
|
||||
from ietf.ietfauth.utils import has_role
|
||||
|
||||
|
||||
|
@ -75,35 +74,3 @@ def get_my_groups(user,conclude=False):
|
|||
continue
|
||||
|
||||
return list(my_groups)
|
||||
|
||||
def groups_by_session(user, meeting, types=None):
|
||||
'''
|
||||
Takes a Django User object, Meeting object and optionally string of meeting types to
|
||||
include. Returns a tuple scheduled_groups, unscheduled groups. sorted lists of those
|
||||
groups that the user has access to, secretariat defaults to all groups
|
||||
If user=None than all groups are returned.
|
||||
|
||||
For groups with a session, we must include "concluded" groups because we still want to know
|
||||
who had a session at a particular meeting even if they are concluded after. This is not true
|
||||
for groups without a session because this function is often used to build select lists (ie.
|
||||
Session Request Tool) and you don't want concluded groups appearing as options.
|
||||
'''
|
||||
groups_session = []
|
||||
groups_no_session = []
|
||||
my_groups = get_my_groups(user,conclude=True)
|
||||
sessions = Session.objects.filter(meeting=meeting,status__in=('schedw','apprw','appr','sched'))
|
||||
groups_with_sessions = [ s.group for s in sessions ]
|
||||
for group in my_groups:
|
||||
if group in groups_with_sessions:
|
||||
groups_session.append(group)
|
||||
else:
|
||||
if group.state_id not in ('conclude','bof-conc'):
|
||||
groups_no_session.append(group)
|
||||
|
||||
if not types:
|
||||
types = GroupFeatures.objects.filter(has_meetings=True).values_list('type', flat=True)
|
||||
|
||||
groups_session = [x for x in groups_session if x.type_id in types]
|
||||
groups_no_session = [x for x in groups_no_session if x.type_id in types]
|
||||
|
||||
return groups_session, groups_no_session
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# Copyright The IETF Trust 2013-2019, All Rights Reserved
|
||||
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -49,7 +51,7 @@ def get_session(timeslot, schedule=None):
|
|||
'''
|
||||
# todo, doesn't account for shared timeslot
|
||||
if not schedule:
|
||||
schedule = timeslot.meeting.agenda
|
||||
schedule = timeslot.meeting.schedule
|
||||
qs = timeslot.sessions.filter(timeslotassignments__schedule=schedule) #.exclude(states__slug='deleted')
|
||||
if qs:
|
||||
return qs[0]
|
||||
|
@ -63,7 +65,7 @@ def get_timeslot(session, schedule=None):
|
|||
schedule to use the meeting "official" schedule.
|
||||
'''
|
||||
if not schedule:
|
||||
schedule = session.meeting.agenda
|
||||
schedule = session.meeting.schedule
|
||||
ss = session.timeslotassignments.filter(schedule=schedule)
|
||||
if ss:
|
||||
return ss[0].timeslot
|
||||
|
|
|
@ -377,7 +377,7 @@ MIDDLEWARE = [
|
|||
'django_referrer_policy.middleware.ReferrerPolicyMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'csp.middleware.CSPMiddleware',
|
||||
# 'csp.middleware.CSPMiddleware',
|
||||
'ietf.middleware.unicode_nfkc_normalization_middleware',
|
||||
]
|
||||
|
||||
|
|
|
@ -907,7 +907,7 @@ td.ourconflicts, td.theirconflicts {
|
|||
background-image: url('images/orange_hatch.png');
|
||||
}
|
||||
|
||||
.agenda_slot_session {
|
||||
.agenda_slot_regular {
|
||||
background-color: #E0E0E0;
|
||||
}
|
||||
|
||||
|
|
|
@ -378,7 +378,7 @@ function calculate_room_select_box() {
|
|||
html=html+"' id='info_location_select_option_";
|
||||
html=html+value.timeslot_id+"'>";
|
||||
html=html+value.short_string;
|
||||
if(value.roomtype != "session") {
|
||||
if(value.roomtype != "regular") {
|
||||
html = html+ "(" + value.roomtype + ")";
|
||||
}
|
||||
html=html+"</option>";
|
||||
|
|
|
@ -252,9 +252,9 @@ function build_select_box(roomtype, domid, slot_id, select_id) {
|
|||
roomtypeclass="";
|
||||
roomtypeunavailable="";
|
||||
|
||||
if(roomtype == "session") {
|
||||
if(roomtype == "regular") {
|
||||
roomtypesession="selected";
|
||||
roomtypeclass="agenda_slot_session";
|
||||
roomtypeclass="agenda_slot_regular";
|
||||
} else if(roomtype == "other") {
|
||||
roomtypeother="selected";
|
||||
roomtypeclass="agenda_slot_other";
|
||||
|
@ -270,8 +270,8 @@ function build_select_box(roomtype, domid, slot_id, select_id) {
|
|||
}
|
||||
|
||||
html = "<form action=\"/some/place\" method=\"post\"><select id='"+select_id+"'>";
|
||||
html = html + "<option value='session' "+roomtypesession+" id='option_"+domid+"_session'>session</option>";
|
||||
html = html + "<option value='other' "+roomtypeother+" id='option_"+domid+"_other'>non-session</option>";
|
||||
html = html + "<option value='regular' "+roomtypesession+" id='option_"+domid+"_session'>regular session</option>";
|
||||
html = html + "<option value='other' "+roomtypeother+" id='option_"+domid+"_other'>misc</option>";
|
||||
html = html + "<option value='reserved' "+roomtypereserved+" id='option_"+domid+"_reserved'>reserved</option>";
|
||||
html = html + "<option value='plenary' "+roomtypeplenary+" id='option_"+domid+"_plenary'>plenary</option>";
|
||||
html = html + "<option value='unavail' "+roomtypeunavailable+" id='option_"+domid+"_unavail'>unavailable</option>";
|
||||
|
@ -290,7 +290,7 @@ function insert_timeslotedit_cell(ts) {
|
|||
|
||||
$(slot_id).removeClass("agenda_slot_unavailable")
|
||||
$(slot_id).removeClass("agenda_slot_other")
|
||||
$(slot_id).removeClass("agenda_slot_session")
|
||||
$(slot_id).removeClass("agenda_slot_regular")
|
||||
$(slot_id).removeClass("agenda_slot_plenary")
|
||||
$(slot_id).removeClass("agenda_slot_reserved")
|
||||
$(slot_id).removeClass("no_timeslot");
|
||||
|
@ -341,7 +341,7 @@ function create_timeslotedit_cell(slot_id) {
|
|||
|
||||
/* $(slot_id).removeClass("agenda_slot_unavailable") */
|
||||
$(slot_id).removeClass("agenda_slot_other")
|
||||
$(slot_id).removeClass("agenda_slot_session")
|
||||
$(slot_id).removeClass("agenda_slot_regular")
|
||||
$(slot_id).removeClass("agenda_slot_plenary")
|
||||
$(slot_id).removeClass("agenda_slot_reserved")
|
||||
|
||||
|
|
|
@ -21,10 +21,10 @@
|
|||
<td>{% ifchanged s.meeting %}{% if s.meeting.type.slug == 'ietf' %}IETF{% endif %}{{s.meeting.number}}{% endifchanged %}</td>
|
||||
<td>
|
||||
{% if s.name %}{{ s.name }}<br>{% else %}{{ s.group.acronym }} - {% endif %}
|
||||
{% if s.status.slug == "sched" %}
|
||||
{% if s.current_status == "sched" %}
|
||||
{% if s.meeting.type.slug == 'ietf' %}{{s.time|date:"D M d, Y Hi"}}{% else %}{{s.time|date:"D M d, Y"}}{% endif %}
|
||||
{% else %}
|
||||
{{s.status}}
|
||||
{{s.current_status_name}}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% if s.agenda %}<a href="{{ s.agenda.get_absolute_url }}">Agenda</a>{% endif %}</td>
|
||||
|
|
|
@ -22,13 +22,13 @@
|
|||
{% endifchanged %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{% if s.name %}{{ s.name }}<br>{% endif %}
|
||||
{% if s.status.slug == "sched" %}
|
||||
{{s.time|date:"D"}} {{s.time|date:"Y-m-d"}}
|
||||
{% if s.name %}{{ s.name }}<br>{% endif %}
|
||||
{% if s.current_status == "sched" %}
|
||||
{{s.time|date:"D"}} {{s.time|date:"Y-m-d"}}
|
||||
{% else %}
|
||||
{{s.status}}
|
||||
{{s.current_status_name}}
|
||||
{% endif %}
|
||||
{% if show_request and s.meeting.type.slug == 'ietf' %}
|
||||
{% if show_request and s.meeting.type_id == 'ietf' %}
|
||||
{% if can_edit %}
|
||||
<br>
|
||||
<a class="btn btn-default btn-xs" href="{% url 'ietf.secr.sreq.views.view' num=s.meeting.number acronym=s.group.acronym %}">Edit Session Request</a>
|
||||
|
@ -37,7 +37,7 @@
|
|||
</td>
|
||||
<td class="text-left padded-left ">
|
||||
{% if show_ical %}
|
||||
{% if s.meeting.type.slug == 'ietf' %}
|
||||
{% if s.meeting.type_id == 'ietf' %}
|
||||
{{s.time|date:"H:i"}}
|
||||
<a href="{% url 'ietf.meeting.views.ical_agenda' num=s.meeting.number session_id=s.id %}"><span class="fa fa-calendar"></span></a>
|
||||
{% else %}
|
||||
|
|
|
@ -168,7 +168,7 @@
|
|||
</tr>
|
||||
{% endifchanged %}
|
||||
|
||||
{% if item.timeslot.type.slug == 'session' %}
|
||||
{% if item.timeslot.type_id == 'regular' %}
|
||||
{% ifchanged %}
|
||||
<tr class="info">
|
||||
<th class="text-nowrap text-right">
|
||||
|
@ -223,7 +223,7 @@
|
|||
{{item.timeslot.name}}
|
||||
{% endif %}
|
||||
|
||||
{% if item.session.status.slug == 'canceled' %}
|
||||
{% if item.session.current_status == 'canceled' %}
|
||||
<span class="label label-danger pull-right">CANCELLED</span>
|
||||
{% endif %}
|
||||
|
||||
|
@ -248,7 +248,7 @@
|
|||
</tr>
|
||||
{% endif %}
|
||||
|
||||
{% if item.timeslot.type.slug == 'session' or item.timeslot.type.slug == 'plenary' %}
|
||||
{% if item.timeslot.type_id == 'regular' or item.timeslot.type.slug == 'plenary' %}
|
||||
{% if item.session.historic_group %}
|
||||
<tr id="row-{{item.slug}}" data-ske="row-{{ item.slug }}" {% if item.timeslot.type.slug == 'plenary' %}class="{{item.timeslot.type.slug}}danger"{% endif %}>
|
||||
{% if item.timeslot.type.slug == 'plenary' %}
|
||||
|
@ -316,7 +316,7 @@
|
|||
<span class="label label-success pull-right">BOF</span>
|
||||
{% endif %}
|
||||
|
||||
{% if item.session.status.slug == 'canceled' %}
|
||||
{% if item.session.current_status == 'canceled' %}
|
||||
<span class="label label-danger pull-right">CANCELLED</span>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -19,10 +19,10 @@
|
|||
{{ item.timeslot.time_desc }} {{ item.session.name }} - {{ item.timeslot.location.name }}
|
||||
|
||||
{{ item.session.agenda_text.strip|indent:"3" }}
|
||||
{% endif %}{% if item.timeslot.type.slug == "session" %}{% if item.session.historic_group %}{% ifchanged %}
|
||||
{% endif %}{% if item.timeslot.type_id == 'regular' %}{% if item.session.historic_group %}{% ifchanged %}
|
||||
|
||||
{{ item.timeslot.time_desc }} {{ item.timeslot.name }}
|
||||
{% endifchanged %}{{ item.timeslot.location.name|ljust:14 }} {{ item.session.historic_group.historic_parent.acronym|upper|ljust:4 }} {{ item.session.historic_group.acronym|ljust:10 }} {{ item.session.historic_group.name }} {% if item.session.historic_group.state_id == "bof" %}BOF{% elif item.session.historic_group.type_id == "wg" %}WG{% endif %}{% if item.session.agenda_note %} - {{ item.session.agenda_note }}{% endif %}{% if item.session.status.slug == 'canceled' %} *** CANCELLED ***{% endif %}
|
||||
{% endifchanged %}{{ item.timeslot.location.name|ljust:14 }} {{ item.session.historic_group.historic_parent.acronym|upper|ljust:4 }} {{ item.session.historic_group.acronym|ljust:10 }} {{ item.session.historic_group.name }} {% if item.session.historic_group.state_id == "bof" %}BOF{% elif item.session.historic_group.type_id == "wg" %}WG{% endif %}{% if item.session.agenda_note %} - {{ item.session.agenda_note }}{% endif %}{% if item.session.current_status == 'canceled' %} *** CANCELLED ***{% endif %}
|
||||
{% endif %}{% endif %}{% if item.timeslot.type.slug == "break" %}
|
||||
{{ item.timeslot.time_desc }} {{ item.timeslot.name }}{% if schedule.meeting.break_area and item.timeslot.show_location %} - {{ schedule.meeting.break_area }}{% endif %}{% endif %}{% if item.timeslot.type.slug == "other" %}
|
||||
{{ item.timeslot.time_desc }} {{ item.timeslot.name }} - {{ item.timeslot.location.name }}{% endif %}{% endfor %}
|
||||
|
|
|
@ -33,7 +33,7 @@ li.daylistentry { margin-left:2em; font-weight: 400; }
|
|||
<ul class="typelist">
|
||||
{% for type in type_list %}
|
||||
<li class="typelistentry {% cycle 'even' 'odd' %}">
|
||||
<h2>{{type.grouper|title}}</h2> {% if schedule == meeting.agenda %}<a id="ical-link" class="btn btn-primary" href="{% url "ietf.meeting.views.agenda_by_type_ics" num=meeting.number type=type.grouper %}">Download to Calendar</a>{% endif %}
|
||||
<h2>{{type.grouper|title}}</h2> {% if schedule == meeting.schedule %}<a id="ical-link" class="btn btn-primary" href="{% url "ietf.meeting.views.agenda_by_type_ics" num=meeting.number type=type.grouper %}">Download to Calendar</a>{% endif %}
|
||||
<ul class="daylist">
|
||||
{% regroup type.list by timeslot.time|date:"l Y-M-d" as daylist %}
|
||||
{% for day in daylist %}
|
||||
|
@ -45,7 +45,7 @@ li.daylistentry { margin-left:2em; font-weight: 400; }
|
|||
<td>{{ss.timeslot.time|date:"H:i"}}-{{ss.timeslot.end_time|date:"H:i"}}</td>
|
||||
<td>{{ss.timeslot.get_hidden_location}}</td>
|
||||
<td class="type-{{ss.session.type.slug}}">{{ss.session.short_name}} </td>
|
||||
<td>{% if ss.session.type_id == 'session' or ss.session.type_id == 'plenary' or ss.session.type_id == 'other' %} <a href="{% url 'ietf.meeting.views.session_details' num=ss.session.meeting.number acronym=ss.session.group.acronym %}">Materials</a>{% else %} {% endif %}</td>
|
||||
<td>{% if ss.session.type_id == 'regular' or ss.session.type_id == 'plenary' or ss.session.type_id == 'other' %} <a href="{% url 'ietf.meeting.views.session_details' num=ss.session.meeting.number acronym=ss.session.group.acronym %}">Materials</a>{% else %} {% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
{# Null Form #}
|
||||
{% buttons %}
|
||||
<input class="btn btn-primary" type="submit" value="Delete schedule" name="save">
|
||||
<a href="{% url 'ietf.meeting.views.list_agendas' num=meeting.number %}" class="btn btn-default">Cancel</a>
|
||||
<a href="{% url 'ietf.meeting.views.list_schedules' num=meeting.number %}" class="btn btn-default">Cancel</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
<span class="label label-warning">No minutes</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if session.type.slug == 'session' and show_agenda == "True" %}
|
||||
{% if session.type_id == 'regular' and show_agenda == "True" %}
|
||||
{% if session.all_meeting_bluesheets %}
|
||||
{% if session.all_meeting_bluesheets|length == 1 %}
|
||||
<a href="{{ session.all_meeting_bluesheets.0|meeting_href:session.meeting }}">Bluesheets</a><br>
|
||||
|
|
|
@ -35,17 +35,9 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
{% for meeting in meetings %}
|
||||
{% if meeting.type.slug == 'interim' %}
|
||||
<tr id="row-{{ forloop.counter }}-{{ meeting.session_set.all.0.group.acronym }}">
|
||||
{% else %}
|
||||
<tr id="row-{{ forloop.counter }}-ietf">
|
||||
{% endif %}
|
||||
<tr id="row-{{ forloop.counter }}{% if meeting.responsible_group.parent %}-{{ meeting.responsible_group.parent.acronym }}{% endif %}-{{ meeting.responsible_group.acronym }}">
|
||||
<td>{{ meeting.date }}</td>
|
||||
{% if meeting.type.slug == 'interim' %}
|
||||
<td>{{ meeting.session_set.all.0.group.acronym }}</td>
|
||||
{% else %}
|
||||
<td>ietf</td>
|
||||
{% endif %}
|
||||
<td>{{ meeting.responsible_group.acronym }}</td>
|
||||
<td>
|
||||
<a href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">{{ meeting.number }}</a>
|
||||
</td>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{% load ietf_filters %}{% if is_change %}MEETING DETAILS HAVE CHANGED. SEE LATEST DETAILS BELOW.
|
||||
|
||||
{% endif %}The {{ group.name }} ({{ group.acronym }}) {% if group.type.slug == "rg" %}Research Group{% elif group.state.slug == "active" %}Working Group{% elif group.state.slug == 'bof' %}BOF{% endif %} will hold
|
||||
{% if meeting.session_set.count == 1 %}a{% if meeting.city %}n {% else %} virtual {% endif %}interim meeting on {{ meeting.date }} from {{ meeting.agenda.assignments.first.timeslot.time | date:"H:i" }} to {{ meeting.agenda.assignments.first.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone }}.
|
||||
{% if meeting.session_set.count == 1 %}a{% if meeting.city %}n {% else %} virtual {% endif %}interim meeting on {{ meeting.date }} from {{ meeting.schedule.assignments.first.timeslot.time | date:"H:i" }} to {{ meeting.schedule.assignments.first.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone }}.
|
||||
{% else %}a multi-day {% if not meeting.city %}virtual {% endif %}interim meeting.
|
||||
|
||||
{% for assignment in meeting.agenda.assignments.all %}Session {{ forloop.counter }}:
|
||||
{% for assignment in meeting.schedule.assignments.all %}Session {{ forloop.counter }}:
|
||||
{{ assignment.timeslot.time | date:"Y-m-d" }} {{ assignment.timeslot.time | date:"H:i" }} to {{ assignment.timeslot.end_time | date:"H:i" }} {{ meeting.time_zone }}
|
||||
{% endfor %}{% endif %}
|
||||
{% if meeting.city %}Meeting Location:
|
||||
|
|
|
@ -14,54 +14,46 @@
|
|||
{% origin %}
|
||||
<h1>Pending Interim Meetings</h1>
|
||||
|
||||
{% if menu_entries %}
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
{% for name, url in menu_entries %}
|
||||
<li {% if selected_menu_entry == name.lower %}class="active"{% endif %}>
|
||||
<a href="{{ url }}">{{ name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if menu_entries %}
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
{% for name, url in menu_entries %}
|
||||
<li {% if selected_menu_entry == name.lower %}class="active"{% endif %}>
|
||||
<a href="{{ url }}">{{ name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if meetings %}
|
||||
<table id="pending-interim-meetings-table" class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Group</th>
|
||||
<th>Name</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for meeting in meetings %}
|
||||
{% if meeting.type.slug == 'interim' %}
|
||||
<tr id="row-{{ forloop.counter }}-{{ meeting.session_set.all.0.group.acronym }}">
|
||||
{% else %}
|
||||
<tr id="row-{{ forloop.counter }}-ietf">
|
||||
{% endif %}
|
||||
<td>{{ meeting.date }}</td>
|
||||
{% if meeting.type.slug == 'interim' %}
|
||||
<td>{{ meeting.session_set.all.0.group.acronym }}</td>
|
||||
{% else %}
|
||||
<td>ietf</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
{% if meeting.type.slug == "interim" %}
|
||||
<a href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">{{ meeting.number }}{% if meeting.session_set.all.0.status.slug == "canceled" %} -- CANCELLED --{% endif %}</a>
|
||||
{% if meetings %}
|
||||
<table id="pending-interim-meetings-table" class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Group</th>
|
||||
<th>Name</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for meeting in meetings %}
|
||||
<tr id="row-{{ forloop.counter }}{% if meeting.responsible_group.parent %}-{{ meeting.responsible_group.parent.acronym }}{% endif %}-{{ meeting.responsible_group.acronym }}">
|
||||
<td>{{ meeting.date }}</td>
|
||||
<td>{{ meeting.responsible_group.acronym }}</td>
|
||||
<td>
|
||||
{% if meeting.type_id == "interim" %}
|
||||
<a href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">{{ meeting.number }}{% if meeting.interim_meeting_cancelled %} <span class="label label-warning">CANCELLED</span>{% endif %}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">IETF - {{ meeting.number }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% if meeting.can_approve %}<span class="label label-success">can be approved</span>{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<h3>No pending interim meetings</h3>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% if meeting.can_approve %}<span class="label label-success">can be approved</span>{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<h3>No pending interim meetings</h3>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% load origin %}
|
||||
{% load staticfiles bootstrap3 widget_tweaks %}
|
||||
|
||||
{% block title %}Cancel Interim Meeting {% if meeting.session_set.first.status.slug != "sched" %}Request{% endif %}{% endblock %}
|
||||
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static 'select2/select2.css' %}">
|
||||
|
@ -13,7 +13,7 @@
|
|||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Cancel Interim Meeting {% if meeting.session_set.first.status.slug != "sched" %}Request{% endif %}</h1>
|
||||
<h1>{% block title %}Cancel Interim Meeting {% if session_status != "sched" %}Request{% endif %}{% endblock %}</h1>
|
||||
|
||||
<form id="interim-request-cancel-form" role="form" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
|
|
|
@ -15,18 +15,18 @@
|
|||
<h1>Interim Meeting Request Details</h1>
|
||||
<dl class="dl-horizontal">
|
||||
<dt>Group</dt>
|
||||
<dd>{{ sessions.0.group.acronym }}
|
||||
<dd>{{ group.acronym }}
|
||||
<dt>Requested By</dt>
|
||||
<dd>{{ sessions.0.requested_by }}
|
||||
<dd>{{ requester }}
|
||||
<dt>Status</dt>
|
||||
<dd>{{ sessions.0.status }}</dd>
|
||||
<dd>{{ session_status.name }}</dd>
|
||||
<dt>City</dt>
|
||||
<dd>{{ meeting.city }}</dd>
|
||||
<dt>Country</dt>
|
||||
<dd>{{ meeting.country }}</dd>
|
||||
<dt>Timezone</dt>
|
||||
<dd>{{ meeting.time_zone }}</dd>
|
||||
{% for assignment in meeting.agenda.assignments.all %}
|
||||
{% for assignment in meeting.schedule.assignments.all %}
|
||||
<br>
|
||||
<dt>Date</dt>
|
||||
<dd>{{ assignment.timeslot.time|date:"Y-m-d" }}
|
||||
|
@ -46,24 +46,24 @@
|
|||
{% if can_edit %}
|
||||
<a class="btn btn-default" href="{% url 'ietf.meeting.views.interim_request_edit' number=meeting.number %}">Edit</a>
|
||||
{% endif %}
|
||||
{% if can_approve and sessions.0.status.slug == 'apprw' %}
|
||||
{% if can_approve and session_status.slug == 'apprw' %}
|
||||
<input class="btn btn-default" type="submit" value="Approve" name='approve' />
|
||||
<input class="btn btn-default" type="submit" value="Disapprove" name='disapprove' />
|
||||
{% endif %}
|
||||
{% if user|has_role:"Secretariat" and sessions.0.status.slug == 'scheda' %}
|
||||
{% if user|has_role:"Secretariat" and session_status.slug == 'scheda' %}
|
||||
<a class="btn btn-default" href="{% url 'ietf.meeting.views.interim_send_announcement' number=meeting.number %}">Announce</a>
|
||||
<a class="btn btn-default" href="{% url 'ietf.meeting.views.interim_skip_announcement' number=meeting.number %}">Skip Announcement</a>
|
||||
{% endif %}
|
||||
{% if can_edit %}
|
||||
{% if sessions.0.status.slug == 'apprw' or sessions.0.status.slug == 'scheda' or sessions.0.status.slug == 'sched' %}
|
||||
{% if session_status.slug == 'apprw' or session_status.slug == 'scheda' or session_status.slug == 'sched' %}
|
||||
<a class="btn btn-default" href="{% url 'ietf.meeting.views.interim_request_cancel' number=meeting.number %}">Cancel Meeting</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if sessions.0.status.slug == "apprw" %}
|
||||
{% if session_status.slug == "apprw" %}
|
||||
<a class="btn btn-default" href="{% url 'ietf.meeting.views.interim_pending' %}">Back</a>
|
||||
{% elif sessions.0.status.slug == "scheda" %}
|
||||
{% elif session_status.slug == "scheda" %}
|
||||
<a class="btn btn-default" href="{% url 'ietf.meeting.views.interim_announce' %}">Back</a>
|
||||
{% elif sessions.0.status.slug == "sched" %}
|
||||
{% elif session_status.slug == "sched" %}
|
||||
<a class="btn btn-default" href="{% url 'ietf.meeting.views.session_details' num=meeting.number acronym=meeting.session_set.first.group.acronym %}">Back</a>
|
||||
{% else %}
|
||||
<a class="btn btn-default" href="{% url 'ietf.meeting.views.upcoming' %}">Back</a>
|
||||
|
|
|
@ -117,7 +117,7 @@ promiselist.push(ss_promise);
|
|||
|
||||
<div id="unassigned-items">
|
||||
<div id="all_agendas" class="events_bar_buttons">
|
||||
<a href="{% url "ietf.meeting.views.list_agendas" meeting.number %}">
|
||||
<a href="{% url "ietf.meeting.views.list_schedules" meeting.number %}">
|
||||
<button class="styled_button">all agendas</button>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
{# Null Form #}
|
||||
{% buttons %}
|
||||
<input class="btn btn-primary" type="submit" value="Make this schedule official" name="save">
|
||||
<a href="{% url 'ietf.meeting.views.list_agendas' num=meeting.number %}" class="btn btn-default">Cancel</a>
|
||||
<a href="{% url 'ietf.meeting.views.list_schedules' num=meeting.number %}" class="btn btn-default">Cancel</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
{% origin %}
|
||||
|
||||
{% if schedule != meeting.agenda %}
|
||||
{% if schedule != meeting.schedule %}
|
||||
<h3 class="alert alert-danger text-center">
|
||||
This is schedule {{schedule.owner.email}}/{{ schedule.name }}, not the official schedule.
|
||||
</h3>
|
||||
|
@ -34,7 +34,7 @@
|
|||
<li {% if selected == "agenda-utc" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.agenda' num=schedule.meeting.number utc='-utc' %}">UTC Agenda</a></li>
|
||||
{% if user|has_role:"Secretariat,Area Director,IAB" %}
|
||||
{% if schedule != meeting.agenda %}
|
||||
{% if schedule != meeting.schedule %}
|
||||
<li {% if selected == "by-room" %}class="active"{% endif %}>
|
||||
<a href="{% url 'ietf.meeting.views.agenda_by_room' num=schedule.meeting.number name=schedule.name owner=schedule.owner.email %}">by Room</a></li>
|
||||
<li {% if selected == "by-type" %}class="active"{% endif %}>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
{% load ietf_filters staticfiles %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static "jquery.tablesorter/css/theme.bootstrap.min.css" %}">
|
||||
<link rel="stylesheet" href="{% static "jquery.tablesorter/css/theme.bootstrap.min.css" %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block bodyAttrs %}data-spy="scroll" data-target="#affix"{% endblock %}
|
||||
|
@ -75,47 +75,43 @@
|
|||
{% else %}
|
||||
<blockquote><i>No past meetings are available.</i></blockquote>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div> <!-- panel-body -->
|
||||
</div> <!-- panel-collapse -->
|
||||
</div> <!-- panel -->
|
||||
</div> <!-- panel-group -->
|
||||
|
||||
{% if meetings %}
|
||||
<h3></h3>
|
||||
<table class="table table-condensed table-striped tablesorter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Group</th>
|
||||
<th>Name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for meeting in meetings %}
|
||||
{% if meeting.type.slug == 'interim' %}
|
||||
<tr id="row-{{ forloop.counter }}-{{ meeting.session_set.first.group.parent.acronym }}-{{ meeting.session_set.first.group.acronym }}">
|
||||
{% else %}
|
||||
<tr id="row-{{ forloop.counter }}-ietf">
|
||||
{% endif %}
|
||||
<td>{{ meeting.date }}</td>
|
||||
{% if meeting.type.slug == 'interim' %}
|
||||
<td>
|
||||
<a href="{% url 'ietf.group.views.group_home' meeting.session_set.all.0.group.acronym %}">{{ meeting.session_set.all.0.group.acronym }}</a>
|
||||
</td>
|
||||
{% if meetings %}
|
||||
<h3></h3>
|
||||
<table class="table table-condensed table-striped tablesorter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Group</th>
|
||||
<th>Name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for meeting in meetings %}
|
||||
<tr id="row-{{ forloop.counter }}{% if meeting.responsible_group.parent %}-{{ meeting.responsible_group.parent.acronym }}{% endif %}-{{ meeting.responsible_group.acronym }}">
|
||||
<td>{{ meeting.date }}</td>
|
||||
<td>
|
||||
{% if meeting.responsible_group.type_id != 'ietf' %}
|
||||
<a href="{% url 'ietf.group.views.group_home' meeting.responsible_group.acronym %}">{{ meeting.responsible_group.acronym }}</a>
|
||||
{% else %}
|
||||
<td>ietf</td>
|
||||
{{ meeting.responsible_group.acronym }}
|
||||
{% endif %}
|
||||
<td>
|
||||
{% if meeting.type.slug == "interim" %}
|
||||
<a href="{% url 'ietf.meeting.views.session_details' num=meeting.number acronym=meeting.session_set.all.0.group.acronym %}">{{ meeting.number }}{% if meeting.session_set.all.0.status.slug == "canceled" %} <span class="label label-warning">CANCELLED</span>{% endif %}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if meeting.type_id == "interim" %}
|
||||
<a href="{% url 'ietf.meeting.views.session_details' num=meeting.number acronym=meeting.responsible_group.acronym %}">{{ meeting.number }}{% if meeting.interim_meeting_cancelled %} <span class="label label-warning">CANCELLED</span>{% endif %}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}">IETF - {{ meeting.number }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{% if meeting.type.slug == "interim" %}
|
||||
<td>
|
||||
{% if meeting.type_id == "interim" %}
|
||||
{% else %}
|
||||
{% if meeting.get_number > 97 %}
|
||||
<a href="{% url 'ietf.meeting.views.important_dates' num=meeting.number %}">Important dates</a>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<div id="read_only">
|
||||
<p>You do not have access this agenda. It belongs to {{ schedule.owner }}.</p>
|
||||
|
||||
<p><a href="{% url "ietf.meeting.views.list_agendas" meeting.number %}">List your meetings</a>.</p>
|
||||
<p><a href="{% url "ietf.meeting.views.list_schedules" meeting.number %}">List your meetings</a>.</p>
|
||||
|
||||
<div class="wrapper custom_text_stuff"></div>
|
||||
</div>
|
|
@ -68,60 +68,58 @@
|
|||
{% endif %}
|
||||
|
||||
<!-- Working groups -->
|
||||
{% regroup ietf|dictsort:"group.parent.acronym" by group.parent.name as areas %}
|
||||
{% for sessions in areas %}
|
||||
<h2 class="anchor-target" id="{{sessions.list.0.group.parent.acronym}}">{{sessions.list.0.group.parent.acronym|upper}} <small>{{ sessions.grouper }}</small></h2>
|
||||
{% regroup sessions.list by not_meeting as meet_or_not %}
|
||||
{% for batch in meet_or_not %}
|
||||
{% if not batch.grouper %}
|
||||
<table class="table table-condensed table-striped tablesorter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-md-1">Group</th>
|
||||
<th class="col-md-1">Artifacts</th>
|
||||
<th class="col-md-2">Recordings</th>
|
||||
<th class="col-md-4">Slides</th>
|
||||
<th class="col-md-3">Drafts</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for session in batch.list|dictsort:"group.acronym" %}
|
||||
{% ifchanged session.group.acronym %}
|
||||
{% include "meeting/group_proceedings.html" %}
|
||||
{% endifchanged %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p><small>{{sessions.grouper }} groups not meeting: </small>
|
||||
{% for session in batch.list|dictsort:"group.acronym" %}
|
||||
{% ifchanged session.group.acronym %}
|
||||
<a href="{% url 'ietf.group.views.group_home' acronym=session.group.acronym %}">{{session.group.acronym}}</a>{% if not forloop.last %},{% endif %}
|
||||
{% endifchanged %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-md-1"> </th>
|
||||
<th class="col-md-1"> </th>
|
||||
<th class="col-md-2"> </th>
|
||||
<th class="col-md-4"> </th>
|
||||
<th class="col-md-3"> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for session in batch.list|dictsort:"group.acronym" %}
|
||||
{% ifchanged session.group.acronym %}
|
||||
{% if session.sessionpresentation_set.exists %}
|
||||
{% include "meeting/group_proceedings.html" %}
|
||||
{% endif %}
|
||||
{% endifchanged %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for area, meeting_sessions, not_meeting_sessions in ietf_areas %}
|
||||
<h2 class="anchor-target" id="{{ area.acronym }}">{{ area.acronym|upper }} <small>{{ area.name }}</small></h2>
|
||||
{% if meeting_sessions %}
|
||||
<table class="table table-condensed table-striped tablesorter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-md-1">Group</th>
|
||||
<th class="col-md-1">Artifacts</th>
|
||||
<th class="col-md-2">Recordings</th>
|
||||
<th class="col-md-4">Slides</th>
|
||||
<th class="col-md-3">Drafts</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for session in meeting_sessions %}
|
||||
{% ifchanged session.group.acronym %}
|
||||
{% include "meeting/group_proceedings.html" %}
|
||||
{% endifchanged %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if not_meeting_sessions %}
|
||||
<p><small>{{ area.name }} groups not meeting: </small>
|
||||
{% for session in not_meeting_sessions %}
|
||||
{% ifchanged session.group.acronym %}
|
||||
<a href="{% url 'ietf.group.views.group_home' acronym=session.group.acronym %}">{{ session.group.acronym }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% endifchanged %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-md-1"> </th>
|
||||
<th class="col-md-1"> </th>
|
||||
<th class="col-md-2"> </th>
|
||||
<th class="col-md-4"> </th>
|
||||
<th class="col-md-3"> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for session in not_meeting_sessions %}
|
||||
{% ifchanged session.group.acronym %}
|
||||
{% if session.sessionpresentation_set.exists %}
|
||||
{% include "meeting/group_proceedings.html" %}
|
||||
{% endif %}
|
||||
{% endifchanged %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<!-- Training Sessions -->
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<input class="btn btn-primary" type="submit" value="Save" name="save">
|
||||
<a href="{% url 'ietf.meeting.views.list_agendas' num=meeting.number %}" class="btn btn-default">Cancel</a>
|
||||
<a href="{% url 'ietf.meeting.views.list_schedules' num=meeting.number %}" class="btn btn-default">Cancel</a>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
|
||||
|
|
|
@ -52,14 +52,14 @@
|
|||
{% ifchanged %}
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr><th class="warning" colspan="7">{{session.status|capfirst}}</th></tr>
|
||||
<tr><th class="warning" colspan="7">{{session.current_status_name|capfirst}}</th></tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
{% endifchanged %}
|
||||
|
||||
<tr>
|
||||
<th>
|
||||
<a href="{% url "ietf.secr.sreq.views.edit_mtg" num=meeting.number acronym=session.group.acronym %}">
|
||||
<a href="{% url "ietf.secr.sreq.views.edit" num=meeting.number acronym=session.group.acronym %}">
|
||||
{{session.group.acronym}}
|
||||
</a>
|
||||
</th>
|
||||
|
@ -73,7 +73,7 @@
|
|||
<td class="text-right">{{session.attendees|default:""}}</td>
|
||||
|
||||
<td>
|
||||
<a href="mailto:{{session.requested_by.email_address}}">{{session.requested_by}}</a>
|
||||
<a href="mailto:{{session.requested_by_person.email_address}}">{{session.requested_by_person}}</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
|
|
@ -30,14 +30,14 @@
|
|||
<th class="col-md-1">Public</th>
|
||||
<th class="col-md-1"></th>
|
||||
</tr>
|
||||
{% for agenda in class.list %}
|
||||
{% for schedule in class.list %}
|
||||
<tr>
|
||||
<td><a href="{% url "ietf.meeting.views.edit_agenda" agenda.meeting.number agenda.owner_email agenda.name %}">
|
||||
{{ agenda.name }}</a></td>
|
||||
<td>{{ agenda.owner }}</td>
|
||||
<td>{{ agenda.visible_token }}</td>
|
||||
<td>{{ agenda.public_token }}</td>
|
||||
<td><a class="btn btn-default" href="{% url "ietf.meeting.views.edit_agenda_properties" agenda.meeting.number agenda.owner_email agenda.name %}">
|
||||
<td><a href="{% url "ietf.meeting.views.edit_schedule" schedule.meeting.number schedule.owner_email schedule.name %}">
|
||||
{{ schedule.name }}</a></td>
|
||||
<td>{{ schedule.owner }}</td>
|
||||
<td>{{ schedule.visible_token }}</td>
|
||||
<td>{{ schedule.public_token }}</td>
|
||||
<td><a class="btn btn-default" href="{% url "ietf.meeting.views.edit_schedule_properties" schedule.meeting.number schedule.owner_email schedule.name %}">
|
||||
EDIT</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
|
@ -9,9 +9,9 @@
|
|||
{% if session.agenda_note %}<h3>{{session.agenda_note}}</h3>{% endif %}
|
||||
|
||||
{% if can_manage_materials %}
|
||||
{% if session.status.slug == 'sched' or session.status.slug == 'schedw' %}
|
||||
{% if session.current_status == 'sched' or session.current_status == 'schedw' %}
|
||||
<div class="buttonlist">
|
||||
{% if meeting.type.slug == 'interim' and user|has_role:"Secretariat" %}
|
||||
{% if meeting.type_id == 'interim' and user|has_role:"Secretariat" %}
|
||||
<a class="btn btn-default" href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">Meeting Details</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -114,20 +114,12 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
{% for meeting in meetings %}
|
||||
{% if meeting.type.slug == 'interim' %}
|
||||
<tr id="row-{{ forloop.counter }}-{{ meeting.session_set.first.group.parent.acronym }}-{{ meeting.session_set.first.group.acronym }}">
|
||||
{% else %}
|
||||
<tr id="row-{{ forloop.counter }}-ietf">
|
||||
{% endif %}
|
||||
<tr id="row-{{ forloop.counter }}{% if meeting.responsible_group.parent %}-{{ meeting.responsible_group.parent.acronym }}{% endif %}-{{ meeting.responsible_group.acronym }}">
|
||||
<td>{{ meeting.date }}</td>
|
||||
{% if meeting.type.slug == 'interim' %}
|
||||
<td>{{ meeting.session_set.all.0.group.acronym }}</td>
|
||||
{% else %}
|
||||
<td>ietf</td>
|
||||
{% endif %}
|
||||
<td>{{ meeting.responsible_group.acronym }}</td>
|
||||
<td>
|
||||
{% if meeting.type.slug == "interim" %}
|
||||
<a href="{% url 'ietf.meeting.views.session_details' num=meeting.number acronym=meeting.session_set.all.0.group.acronym %}">{{ meeting.number }}{% if meeting.session_set.all.0.status.slug == "canceled" %} <span class="label label-warning">CANCELLED</span>{% endif %}</a>
|
||||
{% if meeting.type_id == "interim" %}
|
||||
<a href="{% url 'ietf.meeting.views.session_details' num=meeting.number acronym=meeting.session_set.all.0.group.acronym %}">{{ meeting.number }}{% if meeting.interim_meeting_cancelled %} <span class="label label-warning">CANCELLED</span>{% endif %}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}">IETF - {{ meeting.number }}</a>
|
||||
{% endif %}
|
||||
|
|
Loading…
Reference in a new issue