Move Session.status, .requested, and .requested_by to a new SchedulingEvent
- Legacy-Id: 17122
This commit is contained in:
parent
0555879777
commit
3957743b85
|
@ -69,7 +69,7 @@ class PersonalInformationExportView(DetailView, JsonExportMixin):
|
|||
person = get_object_or_404(self.model, user=request.user)
|
||||
expand = ['searchrule', 'documentauthor', 'ad_document_set', 'ad_dochistory_set', 'docevent',
|
||||
'ballotpositiondocevent', 'deletedevent', 'email_set', 'groupevent', 'role', 'rolehistory', 'iprdisclosurebase',
|
||||
'iprevent', 'liaisonstatementevent', 'whitelisted', 'schedule', 'constraint', 'session', 'message',
|
||||
'iprevent', 'liaisonstatementevent', 'whitelisted', 'schedule', 'constraint', 'schedulingevent', 'message',
|
||||
'sendqueue', 'nominee', 'topicfeedbacklastseen', 'alias', 'email', 'apikeys', 'personevent',
|
||||
'reviewersettings', 'reviewsecretarysettings', 'unavailableperiod', 'reviewwish',
|
||||
'nextreviewerinteam', 'reviewrequest', 'meetingregistration', 'submissionevent', 'preapproval',
|
||||
|
|
|
@ -39,7 +39,7 @@ from ietf.doc.utils import create_ballot_if_not_open
|
|||
from ietf.group.models import Group
|
||||
from ietf.group.factories import GroupFactory, RoleFactory
|
||||
from ietf.ipr.factories import HolderIprDisclosureFactory
|
||||
from ietf.meeting.models import Meeting, Session, SessionPresentation
|
||||
from ietf.meeting.models import Meeting, Session, SessionPresentation, SchedulingEvent
|
||||
from ietf.meeting.factories import MeetingFactory, SessionFactory
|
||||
from ietf.name.models import SessionStatusName, BallotPositionName
|
||||
from ietf.person.models import Person
|
||||
|
@ -712,11 +712,14 @@ class DocTestCase(TestCase):
|
|||
name = "session-72-mars-1",
|
||||
meeting = Meeting.objects.get(number='72'),
|
||||
group = Group.objects.get(acronym='mars'),
|
||||
status = SessionStatusName.objects.create(slug='scheduled', name='Scheduled'),
|
||||
modified = datetime.datetime.now(),
|
||||
requested_by = Person.objects.get(user__username="marschairman"),
|
||||
type_id = "session",
|
||||
)
|
||||
)
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.create(slug='scheduled'),
|
||||
by = Person.objects.get(user__username="marschairman"),
|
||||
)
|
||||
SessionPresentation.objects.create(session=session, document=doc, rev=doc.rev)
|
||||
|
||||
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=doc.name)))
|
||||
|
|
|
@ -20,7 +20,7 @@ from ietf.doc.models import Document, State, DocAlias, NewRevisionDocEvent
|
|||
from ietf.group.factories import RoleFactory
|
||||
from ietf.group.models import Group
|
||||
from ietf.meeting.factories import MeetingFactory
|
||||
from ietf.meeting.models import Meeting, Session, SessionPresentation
|
||||
from ietf.meeting.models import Meeting, Session, SessionPresentation, SchedulingEvent
|
||||
from ietf.name.models import SessionStatusName
|
||||
from ietf.person.models import Person
|
||||
from ietf.utils.test_utils import TestCase, login_testing_unauthorized
|
||||
|
@ -158,11 +158,14 @@ class GroupMaterialTests(TestCase):
|
|||
name = "session-42-mars-1",
|
||||
meeting = Meeting.objects.get(number='42'),
|
||||
group = Group.objects.get(acronym='mars'),
|
||||
status = SessionStatusName.objects.create(slug='scheduled', name='Scheduled'),
|
||||
modified = datetime.datetime.now(),
|
||||
requested_by = Person.objects.get(user__username="marschairman"),
|
||||
type_id="session",
|
||||
)
|
||||
)
|
||||
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"))
|
||||
|
|
|
@ -68,13 +68,13 @@ from ietf.group.models import Role, Group
|
|||
from ietf.group.utils import can_manage_group_type, can_manage_materials, group_features_role_filter
|
||||
from ietf.ietfauth.utils import ( has_role, is_authorized_in_doc_stream, user_is_person,
|
||||
role_required, is_individual_draft_author)
|
||||
from ietf.name.models import StreamName, BallotPositionName
|
||||
from ietf.name.models import StreamName, BallotPositionName, SessionStatusName
|
||||
from ietf.utils.history import find_history_active_at
|
||||
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
|
||||
|
@ -1345,9 +1345,13 @@ def add_sessionpresentation(request,name):
|
|||
def all_presentations(request, name):
|
||||
doc = get_object_or_404(Document, name=name)
|
||||
|
||||
sessions = add_event_info_to_session_qs(
|
||||
doc.session_set.filter(type__in=['session','plenary','other'])
|
||||
).filter(current_status__in=['sched','schedw','appr','canceled'])
|
||||
|
||||
sessions = doc.session_set.filter(status__in=['sched','schedw','appr','canceled'],
|
||||
type__in=['session','plenary','other'])
|
||||
status_names = {n.slug: n.name for n in SessionStatusName.objects.all()}
|
||||
for session in sessions:
|
||||
session.current_status_name = status_names.get(session.current_status, session.current_status)
|
||||
|
||||
future, in_progress, past = group_sessions(sessions)
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ class GroupInfo(models.Model):
|
|||
return list(set([ role for role in self.parent.role_set.filter(name__in=['ad', 'chair']) ]))
|
||||
|
||||
def is_bof(self):
|
||||
return (self.state.slug in ["bof", "bof-conc"])
|
||||
return self.state_id in ["bof", "bof-conc"]
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
|
|
@ -88,7 +88,7 @@ from ietf.group.utils import (get_charter_text, can_manage_group_type,
|
|||
from ietf.ietfauth.utils import has_role, is_authorized_in_group
|
||||
from ietf.mailtrigger.utils import gather_relevant_expansions
|
||||
from ietf.meeting.helpers import get_meeting
|
||||
from ietf.meeting.utils import group_sessions
|
||||
from ietf.meeting.utils import group_sessions, add_event_info_to_session_qs
|
||||
from ietf.name.models import GroupTypeName, StreamName
|
||||
from ietf.person.models import Email
|
||||
from ietf.review.models import ReviewRequest, ReviewAssignment, ReviewerSettings, ReviewSecretarySettings
|
||||
|
@ -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=['session','plenary','other']
|
||||
)
|
||||
).filter(
|
||||
current_status__in=['sched','schedw','appr','canceled'],
|
||||
)
|
||||
|
||||
future, in_progress, past = group_sessions(sessions)
|
||||
|
||||
|
|
|
@ -355,7 +355,10 @@ class Recipient(models.Model):
|
|||
addrs=[]
|
||||
if 'session' in kwargs:
|
||||
session = kwargs['session']
|
||||
addrs.append(session.requested_by.role_email('chair').address)
|
||||
from ietf.meeting.models import SchedulingEvent
|
||||
first_event = SchedulingEvent.objects.filter(session=session).select_related('by').order_by('time', 'id').first()
|
||||
if first_event and first_event.status_id in ['appw', 'schedw']:
|
||||
addrs.append(first_event.by.role_email('chair').address)
|
||||
return addrs
|
||||
|
||||
def gather_review_team_ads(self, **kwargs):
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.contrib import admin
|
|||
|
||||
from ietf.meeting.models import (Meeting, Room, Session, TimeSlot, Constraint, Schedule,
|
||||
SchedTimeSessAssignment, ResourceAssociation, FloorPlan, UrlResource,
|
||||
SessionPresentation, ImportantDate, SlideSubmission, )
|
||||
SessionPresentation, ImportantDate, SlideSubmission, SchedulingEvent)
|
||||
|
||||
|
||||
class UrlResourceAdmin(admin.ModelAdmin):
|
||||
|
@ -80,13 +80,37 @@ class ConstraintAdmin(admin.ModelAdmin):
|
|||
|
||||
admin.site.register(Constraint, ConstraintAdmin)
|
||||
|
||||
class SessionAdmin(admin.ModelAdmin):
|
||||
list_display = ["meeting", "name", "group", "attendees", "requested", "status"]
|
||||
list_filter = ["meeting", ]
|
||||
raw_id_fields = ["meeting", "group", "requested_by", "materials"]
|
||||
search_fields = ["meeting__number", "name", "group__name", "group__acronym", ]
|
||||
ordering = ["-requested"]
|
||||
class SchedulingEventInline(admin.TabularInline):
|
||||
model = SchedulingEvent
|
||||
raw_id_fields = ["by"]
|
||||
|
||||
class SessionAdmin(admin.ModelAdmin):
|
||||
list_display = ["meeting", "name", "group", "attendees", "requested", "current_status"]
|
||||
list_filter = ["meeting", ]
|
||||
raw_id_fields = ["meeting", "group", "materials"]
|
||||
search_fields = ["meeting__number", "name", "group__name", "group__acronym", ]
|
||||
ordering = ["-id"]
|
||||
inlines = [SchedulingEventInline]
|
||||
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super(SessionAdmin, self).get_queryset(request)
|
||||
return qs.prefetch_related('schedulingevent_set')
|
||||
|
||||
def current_status(self, instance):
|
||||
events = sorted(instance.schedulingevent_set.all(), key=lambda e: (e.time, e.id))
|
||||
if events:
|
||||
return events[-1].time
|
||||
else:
|
||||
return None
|
||||
|
||||
def requested(self, instance):
|
||||
events = sorted(instance.schedulingevent_set.all(), key=lambda e: (e.time, e.id))
|
||||
if events:
|
||||
return events[0].time
|
||||
else:
|
||||
return None
|
||||
|
||||
def name_lower(self, instance):
|
||||
return instance.name.name.lower()
|
||||
|
||||
|
@ -94,6 +118,13 @@ class SessionAdmin(admin.ModelAdmin):
|
|||
|
||||
admin.site.register(Session, SessionAdmin)
|
||||
|
||||
class SchedulingEventAdmin(admin.ModelAdmin):
|
||||
list_display = ["session", "status", "time", "by"]
|
||||
raw_id_fields = ["session", "by"]
|
||||
ordering = ["-id"]
|
||||
|
||||
admin.site.register(SchedulingEvent, SchedulingEventAdmin)
|
||||
|
||||
class ScheduleAdmin(admin.ModelAdmin):
|
||||
list_display = ["name", "meeting", "owner", "visible", "public", "badness"]
|
||||
list_filter = ["meeting", ]
|
||||
|
|
|
@ -10,7 +10,9 @@ 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, 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_schedule
|
||||
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
|
||||
|
||||
|
@ -431,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),
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -83,9 +84,28 @@ class SessionFactory(factory.DjangoModelFactory):
|
|||
meeting = factory.SubFactory(MeetingFactory)
|
||||
type_id='session'
|
||||
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
|
||||
'''
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
return session
|
||||
|
||||
def save_agenda(self):
|
||||
|
|
|
@ -27,7 +27,8 @@ from ietf.ietfauth.utils import has_role, user_is_person
|
|||
from ietf.liaisons.utils import get_person_for_user
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
from ietf.person.models import Person
|
||||
from ietf.meeting.models import Meeting, Schedule, TimeSlot, SchedTimeSessAssignment, ImportantDate
|
||||
from ietf.meeting.models import Meeting, Schedule, TimeSlot, SchedTimeSessAssignment, ImportantDate, SchedulingEvent
|
||||
from ietf.meeting.utils import session_requested_by
|
||||
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
|
||||
|
@ -205,6 +206,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):
|
||||
|
@ -435,12 +441,9 @@ 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):
|
||||
from ietf.meeting.utils import add_event_info_to_session_qs
|
||||
return add_event_info_to_session_qs(meeting.session_set.all()).first().current_status == 'apprw'
|
||||
|
||||
def get_next_interim_number(acronym,date):
|
||||
'''
|
||||
|
@ -493,8 +496,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)
|
||||
|
@ -524,8 +528,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)
|
||||
|
@ -553,10 +558,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,
|
||||
|
@ -590,12 +593,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:
|
||||
|
|
31
ietf/meeting/migrations/0022_schedulingevent.py
Normal file
31
ietf/meeting/migrations/0022_schedulingevent.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Copyright The IETF Trust 2019, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.26 on 2019-11-19 02:41
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import ietf.utils.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('person', '0009_auto_20190118_0725'),
|
||||
('name', '0007_fix_m2m_slug_id_length'),
|
||||
('meeting', '0021_rename_meeting_agenda_to_schedule'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SchedulingEvent',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('time', models.DateTimeField(default=datetime.datetime.now, help_text='When the event happened')),
|
||||
('by', ietf.utils.models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='person.Person')),
|
||||
('session', ietf.utils.models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='meeting.Session')),
|
||||
('status', ietf.utils.models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='name.SessionStatusName')),
|
||||
],
|
||||
),
|
||||
]
|
67
ietf/meeting/migrations/0023_create_scheduling_events.py
Normal file
67
ietf/meeting/migrations/0023_create_scheduling_events.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
# Copyright The IETF Trust 2019, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.26 on 2019-11-19 02:42
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
import datetime
|
||||
|
||||
def create_scheduling_events(apps, schema_editor):
|
||||
Session = apps.get_model('meeting', 'Session')
|
||||
SchedulingEvent = apps.get_model('meeting', 'SchedulingEvent')
|
||||
Person = apps.get_model('person', 'Person')
|
||||
SessionStatusName = apps.get_model('name', 'SessionStatusName')
|
||||
|
||||
system_person = Person.objects.get(name='(System)')
|
||||
session_status_names = { n.slug: n for n in SessionStatusName.objects.all() }
|
||||
|
||||
epoch_time = datetime.datetime(1970, 1, 1, 0, 0, 0)
|
||||
|
||||
for s in Session.objects.select_related('requested_by').filter(schedulingevent=None).iterator():
|
||||
# temporarily fix up weird timestamps for the migration
|
||||
if s.requested == epoch_time:
|
||||
s.requested = s.modified
|
||||
|
||||
requested_event = SchedulingEvent()
|
||||
requested_event.session = s
|
||||
requested_event.time = s.requested
|
||||
requested_event.by = s.requested_by
|
||||
requested_event.status = session_status_names[s.status_id if s.status_id == 'apprw' or (s.status_id == 'notmeet' and not s.scheduled) else 'schedw']
|
||||
requested_event.save()
|
||||
|
||||
scheduled_event = None
|
||||
if s.status_id != requested_event.status_id:
|
||||
if s.scheduled or s.status_id in ['sched', 'scheda']:
|
||||
scheduled_event = SchedulingEvent()
|
||||
scheduled_event.session = s
|
||||
if s.scheduled:
|
||||
scheduled_event.time = s.scheduled
|
||||
else:
|
||||
# we don't know when this happened
|
||||
scheduled_event.time = s.modified
|
||||
scheduled_event.by = system_person # we don't know who did it
|
||||
scheduled_event.status = session_status_names[s.status_id if s.status_id == 'scheda' else 'sched']
|
||||
scheduled_event.save()
|
||||
|
||||
final_event = None
|
||||
if s.status_id not in ['apprw', 'schedw', 'notmeet', 'sched', 'scheda']:
|
||||
final_event = SchedulingEvent()
|
||||
final_event.session = s
|
||||
final_event.time = s.modified
|
||||
final_event.by = system_person # we don't know who did it
|
||||
final_event.status = session_status_names[s.status_id]
|
||||
final_event.save()
|
||||
|
||||
def noop(apps, schema_editor):
|
||||
pass
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('meeting', '0022_schedulingevent'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_scheduling_events, noop),
|
||||
]
|
28
ietf/meeting/migrations/0024_auto_20191204_1731.py
Normal file
28
ietf/meeting/migrations/0024_auto_20191204_1731.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Copyright The IETF Trust 2019, All Rights Reserved
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.26 on 2019-12-04 17:31
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('meeting', '0023_create_scheduling_events'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='session',
|
||||
name='requested',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='session',
|
||||
name='requested_by',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='session',
|
||||
name='status',
|
||||
),
|
||||
]
|
|
@ -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, )
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
|
@ -960,17 +941,17 @@ class Session(models.Model):
|
|||
return list(self.materials.filter(type='draft'))
|
||||
|
||||
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 +1009,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.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
|
||||
|
@ -1110,16 +1099,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 +1157,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 +1178,16 @@ class Session(models.Model):
|
|||
else:
|
||||
return self.group.acronym
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class SchedulingEvent(models.Model):
|
||||
session = ForeignKey(Session)
|
||||
time = models.DateTimeField(default=datetime.datetime.now, help_text="When the event happened")
|
||||
status = ForeignKey(SessionStatusName)
|
||||
by = ForeignKey(Person)
|
||||
|
||||
def __str__(self):
|
||||
return u'%s : %s : %s : %s' % (self.session, self.status, self.time, self.by)
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ImportantDate(models.Model):
|
||||
meeting = ForeignKey(Meeting)
|
||||
|
|
|
@ -13,7 +13,7 @@ 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):
|
||||
|
@ -163,14 +163,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 +201,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')
|
||||
|
|
|
@ -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,10 +25,11 @@ 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="session")
|
||||
SchedulingEvent.objects.create(session=session, status_id=status, by=system_person)
|
||||
slot = TimeSlot.objects.create(
|
||||
meeting=meeting,
|
||||
type_id="session",
|
||||
|
@ -117,43 +118,44 @@ 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="session")
|
||||
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="session")
|
||||
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.schedule = schedule
|
||||
|
|
|
@ -28,13 +28,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
|
||||
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
|
||||
|
@ -195,8 +198,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)
|
||||
|
@ -874,8 +880,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)
|
||||
|
@ -894,12 +903,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)
|
||||
|
@ -914,26 +923,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()
|
||||
|
@ -951,8 +960,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)
|
||||
|
@ -1078,7 +1088,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)
|
||||
|
@ -1322,7 +1332,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)
|
||||
|
@ -1343,7 +1353,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')
|
||||
|
@ -1363,7 +1373,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')
|
||||
|
@ -1383,7 +1393,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)
|
||||
|
@ -1413,17 +1423,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")
|
||||
|
@ -1448,17 +1458,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'])
|
||||
|
@ -1466,7 +1476,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
|
||||
|
@ -1504,7 +1514,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
|
||||
|
@ -1550,7 +1560,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
|
||||
|
@ -1560,7 +1570,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)
|
||||
|
@ -1568,7 +1578,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)
|
||||
|
|
|
@ -11,28 +11,49 @@ 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.person.models import Email
|
||||
from ietf.secr.proceedings.proc_utils import import_audio_files
|
||||
|
||||
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 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):
|
||||
|
||||
def sort_key(session):
|
||||
official_sessions = session.timeslotassignments.filter(schedule=session.meeting.schedule)
|
||||
if official_sessions:
|
||||
return official_sessions.first().timeslot.time
|
||||
elif session.meeting.date:
|
||||
return datetime.datetime.combine(session.meeting.date,datetime.datetime.min.time())
|
||||
else:
|
||||
return session.requested
|
||||
|
||||
for s in sessions:
|
||||
s.time=sort_key(s)
|
||||
s.time = session_time_for_sorting(s, use_meeting_date=True)
|
||||
|
||||
sessions = sorted(sessions,key=lambda s:s.time)
|
||||
|
||||
|
@ -65,29 +86,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.schedule)
|
||||
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'''
|
||||
|
@ -178,3 +194,100 @@ def sort_accept_tuple(accept):
|
|||
tup.append((keys[0], q))
|
||||
return sorted(tup, key = lambda x: float(x[1]), reverse = True)
|
||||
return tup
|
||||
|
||||
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 'session' for now
|
||||
qs = qs.filter(type__slug='session')
|
||||
|
||||
return qs
|
||||
|
||||
def data_for_meetings_overview(meetings, interim_status=None):
|
||||
"""Return filtered meetings with sessions and group hierarchy (for the
|
||||
interim menu)."""
|
||||
|
||||
# extract sessions
|
||||
for m in meetings:
|
||||
m.sessions = []
|
||||
|
||||
sessions = add_event_info_to_session_qs(
|
||||
Session.objects.filter(meeting__in=meetings).order_by('meeting', 'pk')
|
||||
).select_related('group', 'group__parent')
|
||||
|
||||
meeting_dict = {m.pk: m for m in meetings}
|
||||
for s in sessions.iterator():
|
||||
meeting_dict[s.meeting_id].sessions.append(s)
|
||||
|
||||
# filter
|
||||
if interim_status == 'apprw':
|
||||
meetings = [
|
||||
m for m in meetings
|
||||
if not m.type_id == 'interim' or any(s.current_status == 'apprw' for s in m.sessions)
|
||||
]
|
||||
|
||||
elif interim_status == 'scheda':
|
||||
meetings = [
|
||||
m for m in meetings
|
||||
if not m.type_id == 'interim' or any(s.current_status == 'scheda' for s in m.sessions)
|
||||
]
|
||||
|
||||
else:
|
||||
meetings = [
|
||||
m for m in meetings
|
||||
if not m.type_id == 'interim' or not all(s.current_status in ['apprw', 'scheda', 'canceledpa'] for s in m.sessions)
|
||||
]
|
||||
|
||||
# group hierarchy
|
||||
ietf_group = Group.objects.get(acronym='ietf')
|
||||
|
||||
group_hierarchy = [ietf_group]
|
||||
|
||||
parents = {}
|
||||
for m in meetings:
|
||||
if m.type_id == 'interim' and m.sessions:
|
||||
for s in m.sessions:
|
||||
parent = parents.get(s.group.parent_id)
|
||||
if not parent:
|
||||
parent = s.group.parent
|
||||
parent.group_list = []
|
||||
group_hierarchy.append(parent)
|
||||
|
||||
parent.group_list.append(s.group)
|
||||
|
||||
for p in parents.values():
|
||||
p.group_list.sort(key=lambda g: g.acronym)
|
||||
|
||||
# set some useful attributes
|
||||
for m in meetings:
|
||||
m.end = m.date + datetime.timedelta(days=m.days)
|
||||
m.responsible_group = (m.sessions[0].group if m.sessions else None) if m.type_id == 'interim' else ietf_group
|
||||
m.interim_meeting_cancelled = m.type_id == 'interim' and all(s.current_status == 'canceled' for s in m.sessions)
|
||||
|
||||
return meetings, group_hierarchy
|
||||
|
|
|
@ -8,6 +8,7 @@ import csv
|
|||
import datetime
|
||||
import glob
|
||||
import io
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import pytz
|
||||
|
@ -34,7 +35,7 @@ from django.contrib.auth.decorators import login_required
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import URLValidator
|
||||
from django.urls import reverse,reverse_lazy
|
||||
from django.db.models import Min, Max, Q
|
||||
from django.db.models import Min, Max, Q, F
|
||||
from django.forms.models import modelform_factory, inlineformset_factory
|
||||
from django.template import TemplateDoesNotExist
|
||||
from django.template.loader import render_to_string
|
||||
|
@ -49,9 +50,10 @@ 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_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
|
||||
|
@ -63,12 +65,17 @@ 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
|
||||
from ietf.meeting.utils import sort_accept_tuple
|
||||
from ietf.meeting.utils import add_event_info_to_session_qs
|
||||
from ietf.meeting.utils import session_time_for_sorting
|
||||
from ietf.meeting.utils import session_requested_by
|
||||
from ietf.meeting.utils import current_session_status
|
||||
from ietf.meeting.utils import data_for_meetings_overview
|
||||
from ietf.message.utils import infer_message
|
||||
from ietf.secr.proceedings.utils import handle_upload_file
|
||||
from ietf.secr.proceedings.proc_utils import (get_progress_stats, post_process, import_audio_files,
|
||||
|
@ -85,7 +92,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')):
|
||||
|
@ -129,22 +136,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__schedule','status','group__state','group__parent', )
|
||||
)
|
||||
for session in sessions:
|
||||
session.past_cutoff_date = past_cutoff_date
|
||||
|
||||
sessions = 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', ])
|
||||
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_id__in=['session'], group__type__features__has_meetings=True).exclude(pk__in=session_pks)
|
||||
|
||||
for topic in [plenaries, ietf, training, irtf, iab]:
|
||||
for event in topic:
|
||||
|
@ -152,6 +158,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,
|
||||
|
@ -819,7 +829,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)
|
||||
|
@ -887,6 +897,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)
|
||||
|
@ -933,6 +949,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,
|
||||
|
@ -1007,7 +1027,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 = []
|
||||
|
@ -1060,7 +1080,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='session',
|
||||
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")
|
||||
|
||||
|
@ -1075,36 +1115,32 @@ def get_sessions(num, acronym):
|
|||
if not sessions:
|
||||
sessions = Session.objects.filter(meeting=meeting,short=acronym,type__in=['session','plenary','other'])
|
||||
|
||||
def sort_key(session):
|
||||
official_sessions = session.timeslotassignments.filter(schedule=session.meeting.schedule)
|
||||
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.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))
|
||||
|
@ -1115,12 +1151,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_materials()
|
||||
can_manage = can_manage_materials(request.user, session.group)
|
||||
|
||||
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:
|
||||
|
@ -1790,8 +1826,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", {
|
||||
|
@ -1812,7 +1848,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)
|
||||
|
@ -1832,7 +1873,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)
|
||||
|
||||
|
@ -1843,12 +1889,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
|
||||
|
@ -1891,7 +1937,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])
|
||||
|
@ -1922,7 +1968,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)
|
||||
|
@ -1947,7 +1993,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")
|
||||
|
||||
|
@ -1956,11 +2004,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:
|
||||
|
@ -1968,7 +2025,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')
|
||||
|
@ -1981,7 +2040,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)
|
||||
|
@ -1989,13 +2053,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})
|
||||
|
||||
|
@ -2018,7 +2092,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__,
|
||||
|
@ -2031,10 +2105,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)
|
||||
|
@ -2053,32 +2128,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,
|
||||
|
@ -2087,35 +2138,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
|
||||
|
@ -2138,14 +2165,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.schedule.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:
|
||||
|
@ -2155,6 +2185,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:
|
||||
|
@ -2198,17 +2236,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=['session', '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,
|
||||
|
@ -2424,12 +2481,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.schedule.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,
|
||||
|
|
|
@ -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
|
||||
|
@ -152,10 +152,9 @@ class SecrMeetingTestCase(TestCase):
|
|||
mars_group = Group.objects.get(acronym='mars')
|
||||
ames_group = Group.objects.get(acronym='ames')
|
||||
ames_stsa = meeting.schedule.assignments.get(session__group=ames_group)
|
||||
assert ames_stsa.session.status_id == 'schedw'
|
||||
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)
|
||||
mars_stsa.session.status = SessionStatusName.objects.get(slug='appr')
|
||||
mars_stsa.session.save()
|
||||
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)
|
||||
|
@ -185,9 +184,9 @@ class SecrMeetingTestCase(TestCase):
|
|||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(len(outbox), mailbox_before + 1)
|
||||
ames_stsa = meeting.schedule.assignments.get(session__group=ames_group)
|
||||
assert ames_stsa.session.status_id == 'sched'
|
||||
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)
|
||||
assert mars_stsa.session.status_id == 'sched'
|
||||
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()
|
||||
|
@ -363,7 +362,7 @@ class SecrMeetingTestCase(TestCase):
|
|||
response = self.client.post(url, {'post':'yes'})
|
||||
self.assertRedirects(response, redirect_url)
|
||||
session = slot.sessionassignments.filter(schedule=meeting.schedule).first().session
|
||||
self.assertEqual(session.status_id, 'canceled')
|
||||
self.assertEqual(SchedulingEvent.objects.filter(session=session).order_by('-id')[0].status_id, 'canceled')
|
||||
|
||||
def test_meetings_session_edit(self):
|
||||
meeting = make_meeting_test_data()
|
||||
|
|
|
@ -17,7 +17,9 @@ 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
|
||||
|
@ -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.schedule
|
||||
SchedTimeSessAssignment.objects.create(schedule=schedule,
|
||||
session=session,
|
||||
timeslot=timeslot)
|
||||
session.status_id = 'sched'
|
||||
session.save()
|
||||
|
||||
def build_timeslots(meeting,room=None):
|
||||
'''
|
||||
|
@ -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,
|
||||
|
@ -411,16 +407,18 @@ 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,
|
||||
|
@ -434,6 +432,14 @@ def non_session(request, meeting_id, schedule_name):
|
|||
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')
|
||||
|
||||
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/non_session.html', {
|
||||
'assignments': assignments,
|
||||
'form': form,
|
||||
|
@ -453,8 +459,12 @@ 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)
|
||||
|
@ -569,12 +579,13 @@ def notifications(request, meeting_id):
|
|||
|
||||
if request.method == "POST":
|
||||
# ensure session state is scheduled
|
||||
for ss in meeting.schedule.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")
|
||||
|
@ -638,16 +649,27 @@ def sessions(request, meeting_id, schedule_name):
|
|||
'''
|
||||
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,
|
||||
|
@ -664,7 +686,7 @@ def session_edit(request, meeting_id, schedule_name, session_id):
|
|||
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)
|
||||
|
@ -676,11 +698,17 @@ def session_edit(request, meeting_id, schedule_name, session_id):
|
|||
else:
|
||||
form = SessionEditForm(instance=session)
|
||||
|
||||
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/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},
|
||||
)
|
||||
|
||||
|
@ -821,8 +849,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')
|
||||
|
|
|
@ -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=('session','plenary','other'))
|
||||
).filter(current_status='sched').order_by('group__acronym')
|
||||
|
||||
class RecordingEditForm(forms.ModelForm):
|
||||
class Meta:
|
||||
|
|
|
@ -22,7 +22,7 @@ from django.core.exceptions import ObjectDoesNotExist
|
|||
|
||||
from ietf.doc.models import Document, DocAlias, DocEvent, NewRevisionDocEvent, State
|
||||
from ietf.group.models import Group
|
||||
from ietf.meeting.models import Meeting, SessionPresentation, TimeSlot, SchedTimeSessAssignment
|
||||
from ietf.meeting.models import Meeting, SessionPresentation, TimeSlot, SchedTimeSessAssignment, Session
|
||||
from ietf.person.models import Person
|
||||
from ietf.utils.log import log
|
||||
from ietf.utils.mail import send_mail
|
||||
|
@ -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.schedule,
|
||||
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:
|
||||
|
@ -104,9 +111,10 @@ def get_timeslot_for_filename(filename):
|
|||
location__name=room_mapping[match.groupdict()['room']],
|
||||
time=time,
|
||||
sessionassignments__schedule=meeting.schedule,
|
||||
).exclude(sessions__status_id='canceled').distinct()
|
||||
return slots.get()
|
||||
except (ObjectDoesNotExist, KeyError):
|
||||
).distinct()
|
||||
uncancelled_slots = [t for t in slots if not add_event_info_to_session_qs(t.sessions.all()).filter(current_status='canceled').exists()]
|
||||
return uncancelled_slots[0]
|
||||
except (ObjectDoesNotExist, KeyError, IndexError):
|
||||
return None
|
||||
|
||||
def attach_recording(doc, sessions):
|
||||
|
|
|
@ -15,8 +15,9 @@ from django.urls import reverse
|
|||
|
||||
from ietf.doc.models import Document
|
||||
from ietf.group.factories import RoleFactory
|
||||
from ietf.meeting.models import SchedTimeSessAssignment
|
||||
from ietf.meeting.models import SchedTimeSessAssignment, SchedulingEvent
|
||||
from ietf.meeting.factories import MeetingFactory, SessionFactory
|
||||
from ietf.person.models import Person
|
||||
from ietf.name.models import SessionStatusName
|
||||
from ietf.utils.test_utils import TestCase
|
||||
from ietf.utils.mail import outbox
|
||||
|
@ -129,10 +130,16 @@ 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.schedule)
|
||||
self.create_audio_file_for_timeslot(timeslot)
|
||||
|
|
|
@ -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)
|
||||
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# Copyright The IETF Trust 2007-2019, All Rights Reserved
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from ietf.secr.sreq import views
|
||||
|
@ -14,5 +16,5 @@ urlpatterns = [
|
|||
url(r'^%(acronym)s/edit/$' % settings.URL_REGEXPS, views.edit),
|
||||
url(r'^%(acronym)s/new/$' % settings.URL_REGEXPS, views.new),
|
||||
url(r'^%(acronym)s/no_session/$' % settings.URL_REGEXPS, views.no_session),
|
||||
url(r'^(?P<num>[A-Za-z0-9_\-\+]+)/%(acronym)s/edit/$' % settings.URL_REGEXPS, views.edit_mtg),
|
||||
url(r'^(?P<num>[A-Za-z0-9_\-\+]+)/%(acronym)s/edit/$' % settings.URL_REGEXPS, views.edit),
|
||||
]
|
||||
|
|
|
@ -5,22 +5,25 @@
|
|||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import datetime
|
||||
from collections import defaultdict
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.http import Http404
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.group.models import Group
|
||||
from ietf.group.models import Group, GroupFeatures
|
||||
from ietf.ietfauth.utils import has_role, role_required
|
||||
from ietf.meeting.models import Meeting, Session, Constraint, ResourceAssociation
|
||||
from ietf.meeting.models import Meeting, Session, Constraint, ResourceAssociation, SchedulingEvent
|
||||
from ietf.meeting.helpers import get_meeting
|
||||
from ietf.meeting.utils import add_event_info_to_session_qs
|
||||
from ietf.name.models import SessionStatusName, ConstraintName
|
||||
from ietf.secr.sreq.forms import SessionForm, ToolStatusForm
|
||||
from ietf.secr.utils.decorators import check_permissions
|
||||
from ietf.secr.utils.group import groups_by_session
|
||||
from ietf.secr.utils.group import get_my_groups
|
||||
from ietf.utils.mail import send_mail
|
||||
from ietf.person.models import Person
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
|
@ -171,11 +174,18 @@ def approve(request, acronym):
|
|||
'''
|
||||
meeting = get_meeting()
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
session = Session.objects.get(meeting=meeting,group=group,status='apprw')
|
||||
|
||||
session = add_event_info_to_session_qs(Session.objects.filter(meeting=meeting, group=group)).filter(current_status='apprw').first()
|
||||
if session is None:
|
||||
raise Http404
|
||||
|
||||
if has_role(request.user,'Secretariat') or group.parent.role_set.filter(name='ad',person=request.user.person):
|
||||
session.status = SessionStatusName.objects.get(slug='appr')
|
||||
session_save(session)
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='appr'),
|
||||
by=request.user.person,
|
||||
)
|
||||
session_changed(session)
|
||||
|
||||
messages.success(request, 'Third session approved')
|
||||
return redirect('ietf.secr.sreq.views.view', acronym=acronym)
|
||||
|
@ -205,8 +215,12 @@ def cancel(request, acronym):
|
|||
|
||||
# mark sessions as deleted
|
||||
for session in sessions:
|
||||
session.status_id = 'deleted'
|
||||
session_save(session)
|
||||
SchedulingEvent.objects.create(
|
||||
session=session,
|
||||
status=SessionStatusName.objects.get(slug='deleted'),
|
||||
by=request.user.person,
|
||||
)
|
||||
session_changed(session)
|
||||
|
||||
# clear schedule assignments if already scheduled
|
||||
session.timeslotassignments.all().delete()
|
||||
|
@ -236,7 +250,7 @@ def confirm(request, acronym):
|
|||
login = request.user.person
|
||||
|
||||
# check if request already exists for this group
|
||||
if Session.objects.filter(group=group,meeting=meeting).exclude(status__in=('deleted','notmeet')):
|
||||
if add_event_info_to_session_qs(Session.objects.filter(group=group, meeting=meeting)).filter(Q(current_status__isnull=True) | ~Q(current_status__in=['deleted', 'notmeet'])):
|
||||
messages.warning(request, 'Sessions for working group %s have already been requested once.' % group.acronym)
|
||||
return redirect('ietf.secr.sreq.views.main')
|
||||
|
||||
|
@ -258,7 +272,7 @@ def confirm(request, acronym):
|
|||
|
||||
if request.method == 'POST' and button_text == 'Submit':
|
||||
# delete any existing session records with status = canceled or notmeet
|
||||
Session.objects.filter(group=group,meeting=meeting,status__in=('canceled','notmeet')).delete()
|
||||
add_event_info_to_session_qs(Session.objects.filter(group=group, meeting=meeting)).filter(current_status__in=['canceled', 'notmeet']).delete()
|
||||
|
||||
# create new session records
|
||||
count = 0
|
||||
|
@ -268,19 +282,22 @@ def confirm(request, acronym):
|
|||
count += 1
|
||||
if duration:
|
||||
slug = 'apprw' if count == 3 else 'schedw'
|
||||
new_session = Session(meeting=meeting,
|
||||
group=group,
|
||||
attendees=form.data['attendees'],
|
||||
requested=datetime.datetime.now(),
|
||||
requested_by=login,
|
||||
requested_duration=datetime.timedelta(0,int(duration)),
|
||||
comments=form.data['comments'],
|
||||
status=SessionStatusName.objects.get(slug=slug),
|
||||
type_id='session',
|
||||
)
|
||||
session_save(new_session)
|
||||
new_session = Session.objects.create(
|
||||
meeting=meeting,
|
||||
group=group,
|
||||
attendees=form.data['attendees'],
|
||||
requested_duration=datetime.timedelta(0,int(duration)),
|
||||
comments=form.data['comments'],
|
||||
type_id='session',
|
||||
)
|
||||
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.schedule != 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='session',
|
||||
)
|
||||
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='session',
|
||||
)
|
||||
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)).filter(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='session',
|
||||
)
|
||||
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
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
{% endif %}
|
||||
</td>
|
||||
<td>{{ session.agenda_note }}</td>
|
||||
<td>{{ session.status }}</td>
|
||||
<td>{{ session.current_status_name }}</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>
|
||||
<form method="post">
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<div class="button-group">
|
||||
<ul>
|
||||
<li><button name="edit" onclick="window.location='{% url "ietf.secr.sreq.views.edit_mtg" acronym=group.acronym num=meeting.number %}'"{% if is_locked %} disabled{% endif %}>Edit</button></li>
|
||||
<li><button name="edit" onclick="window.location='{% url "ietf.secr.sreq.views.edit" acronym=group.acronym num=meeting.number %}'"{% if is_locked %} disabled{% endif %}>Edit</button></li>
|
||||
{% if show_approve_button %}
|
||||
<li><button onclick="window.location='approve/'">Approve Third Session</button></li>
|
||||
{% endif %}
|
||||
|
|
|
@ -13,8 +13,7 @@ from django.conf import settings
|
|||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
# Datatracker imports
|
||||
from ietf.group.models import Group, GroupFeatures
|
||||
from ietf.meeting.models import Session
|
||||
from ietf.group.models import Group
|
||||
from ietf.ietfauth.utils import has_role
|
||||
|
||||
|
||||
|
@ -75,35 +74,3 @@ def get_my_groups(user,conclude=False):
|
|||
continue
|
||||
|
||||
return list(my_groups)
|
||||
|
||||
def groups_by_session(user, meeting, types=None):
|
||||
'''
|
||||
Takes a Django User object, Meeting object and optionally string of meeting types to
|
||||
include. Returns a tuple scheduled_groups, unscheduled groups. sorted lists of those
|
||||
groups that the user has access to, secretariat defaults to all groups
|
||||
If user=None than all groups are returned.
|
||||
|
||||
For groups with a session, we must include "concluded" groups because we still want to know
|
||||
who had a session at a particular meeting even if they are concluded after. This is not true
|
||||
for groups without a session because this function is often used to build select lists (ie.
|
||||
Session Request Tool) and you don't want concluded groups appearing as options.
|
||||
'''
|
||||
groups_session = []
|
||||
groups_no_session = []
|
||||
my_groups = get_my_groups(user,conclude=True)
|
||||
sessions = Session.objects.filter(meeting=meeting,status__in=('schedw','apprw','appr','sched'))
|
||||
groups_with_sessions = [ s.group for s in sessions ]
|
||||
for group in my_groups:
|
||||
if group in groups_with_sessions:
|
||||
groups_session.append(group)
|
||||
else:
|
||||
if group.state_id not in ('conclude','bof-conc'):
|
||||
groups_no_session.append(group)
|
||||
|
||||
if not types:
|
||||
types = GroupFeatures.objects.filter(has_meetings=True).values_list('type', flat=True)
|
||||
|
||||
groups_session = [x for x in groups_session if x.type_id in types]
|
||||
groups_no_session = [x for x in groups_no_session if x.type_id in types]
|
||||
|
||||
return groups_session, groups_no_session
|
||||
|
|
|
@ -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',
|
||||
]
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
||||
|
@ -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 %}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
{% endif %}{% if item.timeslot.type.slug == "session" %}{% 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 %}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -14,54 +14,46 @@
|
|||
{% origin %}
|
||||
<h1>Pending Interim Meetings</h1>
|
||||
|
||||
{% if menu_entries %}
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
{% for name, url in menu_entries %}
|
||||
<li {% if selected_menu_entry == name.lower %}class="active"{% endif %}>
|
||||
<a href="{{ url }}">{{ name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if menu_entries %}
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
{% for name, url in menu_entries %}
|
||||
<li {% if selected_menu_entry == name.lower %}class="active"{% endif %}>
|
||||
<a href="{{ url }}">{{ name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if meetings %}
|
||||
<table id="pending-interim-meetings-table" class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Group</th>
|
||||
<th>Name</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for meeting in meetings %}
|
||||
{% if meeting.type.slug == 'interim' %}
|
||||
<tr id="row-{{ forloop.counter }}-{{ meeting.session_set.all.0.group.acronym }}">
|
||||
{% else %}
|
||||
<tr id="row-{{ forloop.counter }}-ietf">
|
||||
{% endif %}
|
||||
<td>{{ meeting.date }}</td>
|
||||
{% if meeting.type.slug == 'interim' %}
|
||||
<td>{{ meeting.session_set.all.0.group.acronym }}</td>
|
||||
{% else %}
|
||||
<td>ietf</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
{% if meeting.type.slug == "interim" %}
|
||||
<a href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">{{ meeting.number }}{% if meeting.session_set.all.0.status.slug == "canceled" %} -- CANCELLED --{% endif %}</a>
|
||||
{% if meetings %}
|
||||
<table id="pending-interim-meetings-table" class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Group</th>
|
||||
<th>Name</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for meeting in meetings %}
|
||||
<tr id="row-{{ forloop.counter }}{% if meeting.responsible_group.parent %}-{{ meeting.responsible_group.parent.acronym }}{% endif %}-{{ meeting.responsible_group.acronym }}">
|
||||
<td>{{ meeting.date }}</td>
|
||||
<td>{{ meeting.responsible_group.acronym }}</td>
|
||||
<td>
|
||||
{% if meeting.type_id == "interim" %}
|
||||
<a href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">{{ meeting.number }}{% if meeting.interim_meeting_cancelled %} <span class="label label-warning">CANCELLED</span>{% endif %}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">IETF - {{ meeting.number }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% if meeting.can_approve %}<span class="label label-success">can be approved</span>{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<h3>No pending interim meetings</h3>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% if meeting.can_approve %}<span class="label label-success">can be approved</span>{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<h3>No pending interim meetings</h3>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% load origin %}
|
||||
{% load staticfiles bootstrap3 widget_tweaks %}
|
||||
|
||||
{% block title %}Cancel Interim Meeting {% if meeting.session_set.first.status.slug != "sched" %}Request{% endif %}{% endblock %}
|
||||
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static 'select2/select2.css' %}">
|
||||
|
@ -13,7 +13,7 @@
|
|||
|
||||
{% block content %}
|
||||
{% origin %}
|
||||
<h1>Cancel Interim Meeting {% if meeting.session_set.first.status.slug != "sched" %}Request{% endif %}</h1>
|
||||
<h1>{% block title %}Cancel Interim Meeting {% if session_status != "sched" %}Request{% endif %}{% endblock %}</h1>
|
||||
|
||||
<form id="interim-request-cancel-form" role="form" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
<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>
|
||||
|
@ -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>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
{% load ietf_filters staticfiles %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="{% static "jquery.tablesorter/css/theme.bootstrap.min.css" %}">
|
||||
<link rel="stylesheet" href="{% static "jquery.tablesorter/css/theme.bootstrap.min.css" %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block bodyAttrs %}data-spy="scroll" data-target="#affix"{% endblock %}
|
||||
|
@ -75,47 +75,43 @@
|
|||
{% else %}
|
||||
<blockquote><i>No past meetings are available.</i></blockquote>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div> <!-- panel-body -->
|
||||
</div> <!-- panel-collapse -->
|
||||
</div> <!-- panel -->
|
||||
</div> <!-- panel-group -->
|
||||
|
||||
{% if meetings %}
|
||||
<h3></h3>
|
||||
<table class="table table-condensed table-striped tablesorter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Group</th>
|
||||
<th>Name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for meeting in meetings %}
|
||||
{% if meeting.type.slug == 'interim' %}
|
||||
<tr id="row-{{ forloop.counter }}-{{ meeting.session_set.first.group.parent.acronym }}-{{ meeting.session_set.first.group.acronym }}">
|
||||
{% else %}
|
||||
<tr id="row-{{ forloop.counter }}-ietf">
|
||||
{% endif %}
|
||||
<td>{{ meeting.date }}</td>
|
||||
{% if meeting.type.slug == 'interim' %}
|
||||
<td>
|
||||
<a href="{% url 'ietf.group.views.group_home' meeting.session_set.all.0.group.acronym %}">{{ meeting.session_set.all.0.group.acronym }}</a>
|
||||
</td>
|
||||
{% if meetings %}
|
||||
<h3></h3>
|
||||
<table class="table table-condensed table-striped tablesorter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Group</th>
|
||||
<th>Name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for meeting in meetings %}
|
||||
<tr id="row-{{ forloop.counter }}{% if meeting.responsible_group.parent %}-{{ meeting.responsible_group.parent.acronym }}{% endif %}-{{ meeting.responsible_group.acronym }}">
|
||||
<td>{{ meeting.date }}</td>
|
||||
<td>
|
||||
{% if meeting.responsible_group.type_id != 'ietf' %}
|
||||
<a href="{% url 'ietf.group.views.group_home' meeting.responsible_group.acronym %}">{{ meeting.responsible_group.acronym }}</a>
|
||||
{% else %}
|
||||
<td>ietf</td>
|
||||
{{ meeting.responsible_group.acronym }}
|
||||
{% endif %}
|
||||
<td>
|
||||
{% if meeting.type.slug == "interim" %}
|
||||
<a href="{% url 'ietf.meeting.views.session_details' num=meeting.number acronym=meeting.session_set.all.0.group.acronym %}">{{ meeting.number }}{% if meeting.session_set.all.0.status.slug == "canceled" %} <span class="label label-warning">CANCELLED</span>{% endif %}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if meeting.type_id == "interim" %}
|
||||
<a href="{% url 'ietf.meeting.views.session_details' num=meeting.number acronym=meeting.responsible_group.acronym %}">{{ meeting.number }}{% if meeting.interim_meeting_cancelled %} <span class="label label-warning">CANCELLED</span>{% endif %}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}">IETF - {{ meeting.number }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{% if meeting.type.slug == "interim" %}
|
||||
<td>
|
||||
{% if meeting.type_id == "interim" %}
|
||||
{% else %}
|
||||
{% if meeting.get_number > 97 %}
|
||||
<a href="{% url 'ietf.meeting.views.important_dates' num=meeting.number %}">Important dates</a>
|
||||
|
|
|
@ -68,60 +68,58 @@
|
|||
{% endif %}
|
||||
|
||||
<!-- Working groups -->
|
||||
{% regroup ietf|dictsort:"group.parent.acronym" by group.parent.name as areas %}
|
||||
{% for sessions in areas %}
|
||||
<h2 class="anchor-target" id="{{sessions.list.0.group.parent.acronym}}">{{sessions.list.0.group.parent.acronym|upper}} <small>{{ sessions.grouper }}</small></h2>
|
||||
{% regroup sessions.list by not_meeting as meet_or_not %}
|
||||
{% for batch in meet_or_not %}
|
||||
{% if not batch.grouper %}
|
||||
<table class="table table-condensed table-striped tablesorter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-md-1">Group</th>
|
||||
<th class="col-md-1">Artifacts</th>
|
||||
<th class="col-md-2">Recordings</th>
|
||||
<th class="col-md-4">Slides</th>
|
||||
<th class="col-md-3">Drafts</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for session in batch.list|dictsort:"group.acronym" %}
|
||||
{% ifchanged session.group.acronym %}
|
||||
{% include "meeting/group_proceedings.html" %}
|
||||
{% endifchanged %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p><small>{{sessions.grouper }} groups not meeting: </small>
|
||||
{% for session in batch.list|dictsort:"group.acronym" %}
|
||||
{% ifchanged session.group.acronym %}
|
||||
<a href="{% url 'ietf.group.views.group_home' acronym=session.group.acronym %}">{{session.group.acronym}}</a>{% if not forloop.last %},{% endif %}
|
||||
{% endifchanged %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-md-1"> </th>
|
||||
<th class="col-md-1"> </th>
|
||||
<th class="col-md-2"> </th>
|
||||
<th class="col-md-4"> </th>
|
||||
<th class="col-md-3"> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for session in batch.list|dictsort:"group.acronym" %}
|
||||
{% ifchanged session.group.acronym %}
|
||||
{% if session.sessionpresentation_set.exists %}
|
||||
{% include "meeting/group_proceedings.html" %}
|
||||
{% endif %}
|
||||
{% endifchanged %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for area, meeting_sessions, not_meeting_sessions in ietf_areas %}
|
||||
<h2 class="anchor-target" id="{{ area.acronym }}">{{ area.acronym|upper }} <small>{{ area.name }}</small></h2>
|
||||
{% if meeting_sessions %}
|
||||
<table class="table table-condensed table-striped tablesorter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-md-1">Group</th>
|
||||
<th class="col-md-1">Artifacts</th>
|
||||
<th class="col-md-2">Recordings</th>
|
||||
<th class="col-md-4">Slides</th>
|
||||
<th class="col-md-3">Drafts</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for session in meeting_sessions %}
|
||||
{% ifchanged session.group.acronym %}
|
||||
{% include "meeting/group_proceedings.html" %}
|
||||
{% endifchanged %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if not_meeting_sessions %}
|
||||
<p><small>{{ area.name }} groups not meeting: </small>
|
||||
{% for session in not_meeting_sessions %}
|
||||
{% ifchanged session.group.acronym %}
|
||||
<a href="{% url 'ietf.group.views.group_home' acronym=session.group.acronym %}">{{ session.group.acronym }}</a>{% if not forloop.last %},{% endif %}
|
||||
{% endifchanged %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-md-1"> </th>
|
||||
<th class="col-md-1"> </th>
|
||||
<th class="col-md-2"> </th>
|
||||
<th class="col-md-4"> </th>
|
||||
<th class="col-md-3"> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for session in not_meeting_sessions %}
|
||||
{% ifchanged session.group.acronym %}
|
||||
{% if session.sessionpresentation_set.exists %}
|
||||
{% include "meeting/group_proceedings.html" %}
|
||||
{% endif %}
|
||||
{% endifchanged %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<!-- Training Sessions -->
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
{% if session.agenda_note %}<h3>{{session.agenda_note}}</h3>{% endif %}
|
||||
|
||||
{% if can_manage_materials %}
|
||||
{% if session.status.slug == 'sched' or session.status.slug == 'schedw' %}
|
||||
{% if session.current_status == 'sched' or session.current_status == 'schedw' %}
|
||||
<div class="buttonlist">
|
||||
{% if meeting.type.slug == 'interim' and user|has_role:"Secretariat" %}
|
||||
{% if meeting.type_id == 'interim' and user|has_role:"Secretariat" %}
|
||||
<a class="btn btn-default" href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">Meeting Details</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -114,20 +114,12 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
{% for meeting in meetings %}
|
||||
{% if meeting.type.slug == 'interim' %}
|
||||
<tr id="row-{{ forloop.counter }}-{{ meeting.session_set.first.group.parent.acronym }}-{{ meeting.session_set.first.group.acronym }}">
|
||||
{% else %}
|
||||
<tr id="row-{{ forloop.counter }}-ietf">
|
||||
{% endif %}
|
||||
<tr id="row-{{ forloop.counter }}{% if meeting.responsible_group.parent %}-{{ meeting.responsible_group.parent.acronym }}{% endif %}-{{ meeting.responsible_group.acronym }}">
|
||||
<td>{{ meeting.date }}</td>
|
||||
{% if meeting.type.slug == 'interim' %}
|
||||
<td>{{ meeting.session_set.all.0.group.acronym }}</td>
|
||||
{% else %}
|
||||
<td>ietf</td>
|
||||
{% endif %}
|
||||
<td>{{ meeting.responsible_group.acronym }}</td>
|
||||
<td>
|
||||
{% if meeting.type.slug == "interim" %}
|
||||
<a href="{% url 'ietf.meeting.views.session_details' num=meeting.number acronym=meeting.session_set.all.0.group.acronym %}">{{ meeting.number }}{% if meeting.session_set.all.0.status.slug == "canceled" %} <span class="label label-warning">CANCELLED</span>{% endif %}</a>
|
||||
{% if meeting.type_id == "interim" %}
|
||||
<a href="{% url 'ietf.meeting.views.session_details' num=meeting.number acronym=meeting.session_set.all.0.group.acronym %}">{{ meeting.number }}{% if meeting.interim_meeting_cancelled %} <span class="label label-warning">CANCELLED</span>{% endif %}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}">IETF - {{ meeting.number }}</a>
|
||||
{% endif %}
|
||||
|
|
Loading…
Reference in a new issue