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:
Henrik Levkowetz 2019-12-17 23:43:07 +00:00
commit 06fe583351
77 changed files with 1653 additions and 1104 deletions

View file

@ -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',

View file

@ -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)))

View file

@ -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"))

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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):

View file

@ -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", ]

View file

@ -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")

View file

@ -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()
)
)

View file

@ -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):

View file

@ -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

View file

@ -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'),
),
]

View 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')),
],
),
]

View 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),
]

View 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',
),
]

View file

@ -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),
]

View file

@ -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)

View file

@ -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())

View file

@ -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

View file

@ -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')

View file

@ -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)

View file

@ -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)

View file

@ -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),

View file

@ -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

View file

@ -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'):

View file

@ -11643,7 +11643,7 @@
"used": true
},
"model": "name.timeslottypename",
"pk": "session"
"pk": "regular"
},
{
"fields": {

View file

@ -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)")

View file

@ -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']

View file

@ -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 ]

View file

@ -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),
]

View file

@ -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},
)

View file

@ -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:

View file

@ -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):

View file

@ -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()

View file

@ -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':

View file

@ -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())

View file

@ -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),
]

View file

@ -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

View file

@ -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;
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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 -->

View file

@ -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 %}

View file

@ -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 -->

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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

View file

@ -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

View file

@ -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',
]

View file

@ -907,7 +907,7 @@ td.ourconflicts, td.theirconflicts {
background-image: url('images/orange_hatch.png');
}
.agenda_slot_session {
.agenda_slot_regular {
background-color: #E0E0E0;
}

View file

@ -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>";

View file

@ -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")

View file

@ -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>

View file

@ -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"}} &nbsp; {{s.time|date:"Y-m-d"}}
{% if s.name %}{{ s.name }}<br>{% endif %}
{% if s.current_status == "sched" %}
{{s.time|date:"D"}} &nbsp; {{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"}}
&nbsp;<a href="{% url 'ietf.meeting.views.ical_agenda' num=s.meeting.number session_id=s.id %}"><span class="fa fa-calendar"></span></a>
{% else %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}&nbsp;{% 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 %}&nbsp;{% endif %}</td>
</tr>
{% endfor %}
</table>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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:

View file

@ -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 %}&nbsp;&nbsp;<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 %}

View file

@ -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 %}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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 %}>

View file

@ -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" %}&nbsp;&nbsp;<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 %}&nbsp;&nbsp;<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>

View file

@ -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>

View file

@ -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">&nbsp;</th>
<th class="col-md-1">&nbsp;</th>
<th class="col-md-2">&nbsp;</th>
<th class="col-md-4">&nbsp;</th>
<th class="col-md-3">&nbsp;</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">&nbsp;</th>
<th class="col-md-1">&nbsp;</th>
<th class="col-md-2">&nbsp;</th>
<th class="col-md-4">&nbsp;</th>
<th class="col-md-3">&nbsp;</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 -->

View file

@ -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>

View file

@ -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>

View file

@ -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 %}

View file

@ -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>

View file

@ -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" %}&nbsp;&nbsp;<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 %}&nbsp;&nbsp;<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 %}