Allow non-official schedules to be viewed in the same ways the official schedule for a meeting can be viewed. Fixes #1959. Commit ready for merge.

- Legacy-Id: 11500
This commit is contained in:
Robert Sparks 2016-06-29 20:56:08 +00:00
parent 9318efa13a
commit b3883b6ac4
11 changed files with 109 additions and 39 deletions

View file

@ -37,6 +37,7 @@ def make_meeting_test_data():
meeting = Meeting.objects.get(number="42", type="ietf")
schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="test-agenda", visible=True, public=True)
unofficial_schedule = Schedule.objects.create(meeting=meeting, owner=plainman, name="test-unofficial-agenda", visible=True, public=True)
pname = RoomResourceName.objects.create(name='projector',slug='proj')
projector = ResourceAssociation.objects.create(name=pname,icon="notfound.png",desc="Basic projector")
room = Room.objects.create(meeting=meeting, name="Test Room", capacity=123)
@ -45,40 +46,48 @@ def make_meeting_test_data():
breakfast_room.session_types.add("lead")
room.resources = [projector]
# slots
slot1 = TimeSlot.objects.create(meeting=meeting, type_id="session", duration=30 * 60, location=room,
time=datetime.datetime.combine(datetime.date.today(), datetime.time(9, 30)))
slot2 = TimeSlot.objects.create(meeting=meeting, type_id="session", duration=30 * 60, location=room,
time=datetime.datetime.combine(datetime.date.today(), datetime.time(10, 30)))
breakfast_slot = TimeSlot.objects.create(meeting=meeting, type_id="lead", duration=90 * 60,
location=breakfast_room,
time=datetime.datetime.combine(datetime.date.today(),datetime.time(7,0)))
# mars WG
mars = Group.objects.get(acronym='mars')
slot = TimeSlot.objects.create(meeting=meeting, type_id="session", duration=30 * 60, location=room,
time=datetime.datetime.combine(datetime.date.today(), datetime.time(9, 30)))
mars_session = Session.objects.create(meeting=meeting, group=mars,
attendees=10, requested_by=system_person,
requested_duration=20, status_id="schedw",
scheduled=datetime.datetime.now(),type_id="session")
mars_session.resources = [projector]
SchedTimeSessAssignment.objects.create(timeslot=slot, session=mars_session, schedule=schedule)
SchedTimeSessAssignment.objects.create(timeslot=slot1, session=mars_session, schedule=schedule)
SchedTimeSessAssignment.objects.create(timeslot=slot2, session=mars_session, schedule=unofficial_schedule)
# ames WG
slot = TimeSlot.objects.create(meeting=meeting, type_id="session", duration=30 * 60, location=room,
time=datetime.datetime.combine(datetime.date.today(), datetime.time(10, 30)))
ames_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="ames"),
attendees=10, requested_by=system_person,
requested_duration=20, status_id="schedw",
scheduled=datetime.datetime.now(),type_id="session")
SchedTimeSessAssignment.objects.create(timeslot=slot, session=ames_session, schedule=schedule)
SchedTimeSessAssignment.objects.create(timeslot=slot2, session=ames_session, schedule=schedule)
SchedTimeSessAssignment.objects.create(timeslot=slot1, session=ames_session, schedule=unofficial_schedule)
# IESG breakfast
breakfast_slot = TimeSlot.objects.create(meeting=meeting, type_id="lead", duration=90 * 60,
location=breakfast_room,
time=datetime.datetime.combine(datetime.date.today(),datetime.time(7,0)))
iesg_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="iesg"),
name="IESG Breakfast",
attendees=25, requested_by=system_person,
requested_duration=20, status_id="schedw",
scheduled=datetime.datetime.now(),type_id="lead")
SchedTimeSessAssignment.objects.create(timeslot=breakfast_slot, session=iesg_session, schedule=schedule)
# No breakfast on unofficial schedule
meeting.agenda = schedule
meeting.save()
# Convenience for the tests
meeting.unofficial_schedule = unofficial_schedule
doc = Document.objects.create(name='agenda-mars-ietf-42', type_id='agenda', title="Agenda", external_url="agenda-mars.txt",group=mars,rev='00')
doc.set_state(State.objects.get(type=doc.type_id, slug="active"))
mars_session.sessionpresentation_set.add(SessionPresentation(session=mars_session,document=doc,rev=doc.rev))

View file

@ -19,10 +19,10 @@ class ApiTests(TestCase):
mars_session = Session.objects.filter(meeting=meeting, group__acronym="mars").first()
ames_session = Session.objects.filter(meeting=meeting, group__acronym="ames").first()
mars_scheduled = SchedTimeSessAssignment.objects.get(session=mars_session)
mars_scheduled = SchedTimeSessAssignment.objects.get(session=mars_session,schedule__name='test-agenda')
mars_slot = mars_scheduled.timeslot
ames_scheduled = SchedTimeSessAssignment.objects.get(session=ames_session)
ames_scheduled = SchedTimeSessAssignment.objects.get(session=ames_session,schedule__name='test-agenda')
ames_slot = ames_scheduled.timeslot
def do_unschedule(assignment):
@ -88,16 +88,16 @@ class ApiTests(TestCase):
r = do_extend(schedule,mars_scheduled)
self.assertEqual(r.status_code, 201)
self.assertTrue("error" not in json.loads(r.content))
self.assertEqual(mars_session.timeslotassignments.count(),2)
self.assertEqual(mars_session.timeslotassignments.filter(schedule__name='test-agenda').count(),2)
# Unschedule mars
r = do_unschedule(mars_scheduled)
self.assertEqual(r.status_code, 200)
self.assertTrue("error" not in json.loads(r.content))
# Make sure it got both the original and extended session
self.assertEqual(mars_session.timeslotassignments.count(),0)
self.assertEqual(mars_session.timeslotassignments.filter(schedule__name='test-agenda').count(),0)
self.assertEqual(SchedTimeSessAssignment.objects.get(session=ames_session).timeslot, mars_slot)
self.assertEqual(SchedTimeSessAssignment.objects.get(session=ames_session,schedule__name='test-agenda').timeslot, mars_slot)
def test_constraints_json(self):

View file

@ -69,7 +69,7 @@ class ScheduleEditTests(StaticLiveServerTestCase):
def testUnschedule(self):
self.assertEqual(SchedTimeSessAssignment.objects.filter(session__meeting__number=42,session__group__acronym='mars').count(),1)
self.assertEqual(SchedTimeSessAssignment.objects.filter(session__meeting__number=42,session__group__acronym='mars',schedule__name='test-agenda').count(),1)
self.login()
url = self.absreverse('ietf.meeting.views.edit_agenda',kwargs=dict(num='42',name='test-agenda',owner='plain@example.com'))
@ -86,7 +86,7 @@ class ScheduleEditTests(StaticLiveServerTestCase):
self.assertTrue(len(q('#sortable-list #session_1'))>0)
time.sleep(0.1) # The API that modifies the database runs async
self.assertEqual(SchedTimeSessAssignment.objects.filter(session__meeting__number=42,session__group__acronym='mars').count(),0)
self.assertEqual(SchedTimeSessAssignment.objects.filter(session__meeting__number=42,session__group__acronym='mars',schedule__name='test-agenda').count(),0)
# The following are useful debugging tools

View file

@ -68,7 +68,7 @@ class MeetingTests(TestCase):
def test_agenda(self):
meeting = make_meeting_test_data()
session = Session.objects.filter(meeting=meeting, group__acronym="mars").first()
slot = TimeSlot.objects.get(sessionassignments__session=session)
slot = TimeSlot.objects.get(sessionassignments__session=session,sessionassignments__schedule=meeting.agenda)
self.write_materials_files(meeting, session)
@ -106,6 +106,10 @@ class MeetingTests(TestCase):
self.assertTrue(time_interval in agenda_content)
r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number,name=meeting.unofficial_schedule.name,owner=meeting.unofficial_schedule.owner.email())))
self.assertEqual(r.status_code, 200)
self.assertTrue('not the official schedule' in unicontent(r))
# CSV
r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number, ext=".csv")))
self.assertEqual(r.status_code, 200)
@ -149,6 +153,11 @@ class MeetingTests(TestCase):
r = self.client.get(url)
self.assertTrue(all([x in unicontent(r) for x in ['mars','IESG Breakfast','Test Room','Breakfast Room']]))
url = urlreverse("ietf.meeting.views.agenda_by_room",kwargs=dict(num=meeting.number,name=meeting.unofficial_schedule.name,owner=meeting.unofficial_schedule.owner.email()))
r = self.client.get(url)
self.assertTrue(all([x in unicontent(r) for x in ['mars','Test Room',]]))
self.assertFalse('IESG Breakfast' in unicontent(r))
def test_agenda_by_type(self):
meeting = make_meeting_test_data()
@ -157,6 +166,11 @@ class MeetingTests(TestCase):
r = self.client.get(url)
self.assertTrue(all([x in unicontent(r) for x in ['mars','IESG Breakfast','Test Room','Breakfast Room']]))
url = urlreverse("ietf.meeting.views.agenda_by_type",kwargs=dict(num=meeting.number,name=meeting.unofficial_schedule.name,owner=meeting.unofficial_schedule.owner.email()))
r = self.client.get(url)
self.assertTrue(all([x in unicontent(r) for x in ['mars','Test Room',]]))
self.assertFalse('IESG Breakfast' in unicontent(r))
url = urlreverse("ietf.meeting.views.agenda_by_type",kwargs=dict(num=meeting.number,type='session'))
r = self.client.get(url)
self.assertTrue(all([x in unicontent(r) for x in ['mars','Test Room']]))
@ -167,12 +181,23 @@ class MeetingTests(TestCase):
self.assertFalse(any([x in unicontent(r) for x in ['mars','Test Room']]))
self.assertTrue(all([x in unicontent(r) for x in ['IESG Breakfast','Breakfast Room']]))
url = urlreverse("ietf.meeting.views.agenda_by_type",kwargs=dict(num=meeting.number,type='lead',name=meeting.unofficial_schedule.name,owner=meeting.unofficial_schedule.owner.email()))
r = self.client.get(url)
self.assertFalse(any([x in unicontent(r) for x in ['IESG Breakfast','Breakfast Room']]))
def test_agenda_room_view(self):
meeting = make_meeting_test_data()
url = urlreverse("ietf.meeting.views.room_view",kwargs=dict(num=meeting.number))
login_testing_unauthorized(self,"secretary",url)
r = self.client.get(url)
self.assertEqual(r.status_code,200)
self.assertTrue(all([x in unicontent(r) for x in ['mars','IESG Breakfast','Test Room','Breakfast Room']]))
url = urlreverse("ietf.meeting.views.room_view",kwargs=dict(num=meeting.number,name=meeting.unofficial_schedule.name,owner=meeting.unofficial_schedule.owner.email()))
r = self.client.get(url)
self.assertEqual(r.status_code,200)
self.assertTrue(all([x in unicontent(r) for x in ['mars','Test Room','Breakfast Room']]))
self.assertFalse('IESG Breakfast' in unicontent(r))
def test_materials(self):
meeting = make_meeting_test_data()
@ -338,12 +363,12 @@ class EditTests(TestCase):
def test_slot_to_the_right(self):
meeting = make_meeting_test_data()
session = Session.objects.filter(meeting=meeting, group__acronym="mars").first()
mars_scheduled = session.timeslotassignments.get()
mars_slot = TimeSlot.objects.get(sessionassignments__session=session)
mars_scheduled = session.timeslotassignments.get(schedule__name='test-agenda')
mars_slot = TimeSlot.objects.get(sessionassignments__session=session,sessionassignments__schedule__name='test-agenda')
mars_ends = mars_slot.time + mars_slot.duration
session = Session.objects.filter(meeting=meeting, group__acronym="ames").first()
ames_slot_qs = TimeSlot.objects.filter(sessionassignments__session=session)
ames_slot_qs = TimeSlot.objects.filter(sessionassignments__session=session,sessionassignments__schedule__name='test-agenda')
ames_slot_qs.update(time=mars_ends + datetime.timedelta(seconds=11 * 60))
self.assertTrue(not mars_slot.slot_to_the_right)

View file

@ -16,8 +16,12 @@ type_ietf_only_patterns = [
url(r'^agenda/(?P<owner>[-A-Za-z0-9\'+._]+@[A-Za-z0-9-._]+)/(?P<name>[A-Za-z0-9-:_]+)/details$', views.edit_agenda_properties),
url(r'^agenda/(?P<owner>[-A-Za-z0-9\'+._]+@[A-Za-z0-9-._]+)/(?P<name>[A-Za-z0-9-:_]+)/delete$', views.delete_schedule),
url(r'^agenda/(?P<owner>[-A-Za-z0-9\'+._]+@[A-Za-z0-9-._]+)/(?P<name>[A-Za-z0-9-:_]+)/make_official$', views.make_schedule_official),
# The following view is broken?
url(r'^agenda/(?P<owner>[-A-Za-z0-9\'+._]+@[A-Za-z0-9-._]+)/(?P<name>[A-Za-z0-9-:_]+)(\.(?P<ext>.html))?/?$', views.agenda),
url(r'^agenda/(?P<owner>[-A-Za-z0-9\'+._]+@[A-Za-z0-9-._]+)/(?P<name>[A-Za-z0-9-:_]+)/week-view(?:.html)?/?$', views.week_view),
url(r'^agenda/(?P<owner>[-A-Za-z0-9\'+._]+@[A-Za-z0-9-._]+)/(?P<name>[A-Za-z0-9-:_]+)/room-view(?:.html)?/?$', views.room_view),
url(r'^agenda/(?P<owner>[-A-Za-z0-9\'+._]+@[A-Za-z0-9-._]+)/(?P<name>[A-Za-z0-9-:_]+)/by-room/?$', views.agenda_by_room),
url(r'^agenda/(?P<owner>[-A-Za-z0-9\'+._]+@[A-Za-z0-9-._]+)/(?P<name>[A-Za-z0-9-:_]+)/by-type/?$', views.agenda_by_type),
url(r'^agenda/(?P<owner>[-A-Za-z0-9\'+._]+@[A-Za-z0-9-._]+)/(?P<name>[A-Za-z0-9-:_]+)/by-type/(?P<type>[a-z]+)$', views.agenda_by_type),
url(r'^agenda/(?P<owner>[-A-Za-z0-9\'+._]+@[A-Za-z0-9-._]+)/(?P<name>[A-Za-z0-9-:_]+)/permissions$', ajax.agenda_permission_api),
url(r'^agenda/(?P<owner>[-A-Za-z0-9\'+._]+@[A-Za-z0-9-._]+)/(?P<name>[A-Za-z0-9-:_]+)/session/(?P<assignment_id>\d+).json$', ajax.assignment_json),
url(r'^agenda/(?P<owner>[-A-Za-z0-9\'+._]+@[A-Za-z0-9-._]+)/(?P<name>[A-Za-z0-9-:_]+)/sessions.json$', ajax.assignments_json),

View file

@ -545,25 +545,33 @@ def agenda_csv(schedule, filtered_assignments):
return response
@role_required('Area Director','Secretariat','IAB')
def agenda_by_room(request,num=None):
def agenda_by_room(request, num=None, name=None, owner=None):
meeting = get_meeting(num)
schedule = get_schedule(meeting)
if name is None:
schedule = get_schedule(meeting)
else:
person = get_person_by_email(owner)
schedule = get_schedule_by_name(meeting, person, name)
ss_by_day = OrderedDict()
for day in schedule.assignments.dates('timeslot__time','day'):
ss_by_day[day]=[]
for ss in schedule.assignments.order_by('timeslot__location__functional_name','timeslot__location__name','timeslot__time'):
day = ss.timeslot.time.date()
ss_by_day[day].append(ss)
return render(request,"meeting/agenda_by_room.html",{"meeting":meeting,"ss_by_day":ss_by_day})
return render(request,"meeting/agenda_by_room.html",{"meeting":meeting,"schedule":schedule,"ss_by_day":ss_by_day})
@role_required('Area Director','Secretariat','IAB')
def agenda_by_type(request,num=None,type=None):
def agenda_by_type(request, num=None, type=None, name=None, owner=None):
meeting = get_meeting(num)
schedule = get_schedule(meeting)
if name is None:
schedule = get_schedule(meeting)
else:
person = get_person_by_email(owner)
schedule = get_schedule_by_name(meeting, person, name)
assignments = schedule.assignments.order_by('session__type__slug','timeslot__time')
if type:
assignments = assignments.filter(session__type__slug=type)
return render(request,"meeting/agenda_by_type.html",{"meeting":meeting,"assignments":assignments})
return render(request,"meeting/agenda_by_type.html",{"meeting":meeting,"schedule":schedule,"assignments":assignments})
@role_required('Area Director','Secretariat','IAB')
def agenda_by_type_ics(request,num=None,type=None):
@ -712,9 +720,14 @@ def session_draft_pdf(request, num, session):
os.unlink(pdfn)
return HttpResponse(pdf_contents, content_type="application/pdf")
def week_view(request, num=None):
def week_view(request, num=None, name=None, owner=None):
meeting = get_meeting(num)
schedule = get_schedule(meeting)
if name is None:
schedule = get_schedule(meeting)
else:
person = get_person_by_email(owner)
schedule = get_schedule_by_name(meeting, person, name)
if not schedule:
raise Http404
@ -772,14 +785,20 @@ def week_view(request, num=None):
})
@role_required('Area Director','Secretariat','IAB')
def room_view(request, num=None):
def room_view(request, num=None, name=None, owner=None):
meeting = get_meeting(num)
rooms = meeting.room_set.order_by('functional_name','name')
if rooms.count() == 0:
raise Http404
assignments = meeting.agenda.assignments.all()
if name is None:
schedule = get_schedule(meeting)
else:
person = get_person_by_email(owner)
schedule = get_schedule_by_name(meeting, person, name)
assignments = schedule.assignments.all()
unavailable = meeting.timeslot_set.filter(type__slug='unavail')
if (unavailable.count() + assignments.count()) == 0 :
raise Http404
@ -822,7 +841,7 @@ def room_view(request, num=None):
ss.day = (ss.timeslot.time-base_day).days
template = "meeting/room-view.html"
return render(request, template,{"meeting":meeting,"unavailable":unavailable,"assignments":assignments,"rooms":rooms,"days":days})
return render(request, template,{"meeting":meeting,"schedule":schedule,"unavailable":unavailable,"assignments":assignments,"rooms":rooms,"days":days})
def ical_agenda(request, num=None, name=None, ext=None):
meeting = get_meeting(num)

View file

@ -4,6 +4,8 @@ import shutil
from pyquery import PyQuery
from StringIO import StringIO
import debug # pyflakes:ignore
from django.conf import settings
from django.core.urlresolvers import reverse
@ -13,7 +15,7 @@ from ietf.meeting.test_data import make_meeting_test_data
from ietf.person.models import Person
from ietf.secr.meetings.forms import get_times
from ietf.utils.mail import outbox
from ietf.utils.test_utils import TestCase
from ietf.utils.test_utils import TestCase
class SecrMeetingTestCase(TestCase):
@ -60,7 +62,7 @@ class SecrMeetingTestCase(TestCase):
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
q = PyQuery(response.content)
self.assertEqual(len(q('#id_schedule_selector option')),2)
self.assertEqual(len(q('#id_schedule_selector option')),3)
def test_add_meeting(self):
"Add Meeting"
@ -181,6 +183,7 @@ class SecrMeetingTestCase(TestCase):
# test delete
# first unschedule sessions so we can delete
SchedTimeSessAssignment.objects.filter(schedule=meeting.agenda).delete()
SchedTimeSessAssignment.objects.filter(schedule=meeting.unofficial_schedule).delete()
self.client.login(username="secretary", password="secretary+password")
post_dict = {
'room-TOTAL_FORMS': q('input[name="room-TOTAL_FORMS"]').val(),

View file

@ -46,9 +46,15 @@
<a href="https://tools.ietf.org/agenda/{{schedule.meeting.number}}/">Tools-style agenda</a>
{% if user|has_role:"Secretariat,Area Director,IAB" %}
|
<a href="{% url 'ietf.meeting.views.agenda_by_room' num=schedule.meeting.number%}">List by Room</a> |
<a href="{% url 'ietf.meeting.views.agenda_by_type' num=schedule.meeting.number%}">List by Type</a> |
<a href="{% url 'ietf.meeting.views.room_view' num=schedule.meeting.number%}">Room Grid</a>
{% if schedule != meeting.agenda %}
<a href="{% url 'ietf.meeting.views.agenda_by_room' num=schedule.meeting.number name=schedule.name owner=schedule.owner.email %}">List by Room</a> |
<a href="{% url 'ietf.meeting.views.agenda_by_type' num=schedule.meeting.number name=schedule.name owner=schedule.owner.email %}">List by Type</a> |
<a href="{% url 'ietf.meeting.views.room_view' num=schedule.meeting.number name=schedule.name owner=schedule.owner.email %}">Room Grid</a>
{% else %}
<a href="{% url 'ietf.meeting.views.agenda_by_room' num=schedule.meeting.number%}">List by Room</a> |
<a href="{% url 'ietf.meeting.views.agenda_by_type' num=schedule.meeting.number%}">List by Type</a> |
<a href="{% url 'ietf.meeting.views.room_view' num=schedule.meeting.number%}">Room Grid</a>
{% endif %}
{% endif %}
</p>

View file

@ -17,6 +17,7 @@ li.sessionlistentry { font-size:62%; }
{% block title %}Agenda for {{meeting}} by Room{% endblock %}
{% block content %}
{% include "meeting/meeting_heading.html" with meeting=schedule.meeting updated=meeting.updated %}
<h1>Agenda for {{meeting}} by Room</h1>
<ul class="daylist">
{% for day,sessions in ss_by_day.items %}

View file

@ -25,12 +25,13 @@ li.daylistentry { margin-left:2em; font-weight: 400; }
{% block title %}Agenda for {{meeting}} by Session Type{% endblock %}
{% block content %}
{% include "meeting/meeting_heading.html" with meeting=schedule.meeting updated=meeting.updated %}
<h1>Agenda for {{meeting}} by Session Type</h1>
{% regroup assignments by session.type.slug as type_list %}
<ul class="typelist">
{% for type in type_list %}
<li class="typelistentry {% cycle 'even' 'odd' %}">
{{type.grouper}} <a id="ical-link" class="btn btn-primary" href="{% url "ietf.meeting.views.agenda_by_type_ics" num=meeting.number type=type.grouper %}">Download to Calendar</a>
{{type.grouper}} {% if schedule == meeting.agenda %}<a id="ical-link" class="btn btn-primary" href="{% url "ietf.meeting.views.agenda_by_type_ics" num=meeting.number type=type.grouper %}">Download to Calendar</a>{% endif %}
<ul class="daylist">
{% regroup type.list by timeslot.time|date:"l Y-M-d" as daylist %}
{% for day in daylist %}

View file

@ -169,7 +169,8 @@
var timelabel_width = width * 0.020;
var header_height = height * 0.05;
var header_offset = $('#daytabs').height();
var header_offset = $('#daytabs').outerHeight(true)+$('#mtgheader').outerHeight(true);
console.log($('#mtgheader').outerHeight(true))
var num_days = {{days|length}};
var num_minutes = day_end - day_start;
@ -581,6 +582,7 @@
</script>
</head>
<body onload="draw_calendar()" onresize="draw_calendar()" id="body">
<div id="mtgheader" style="overflow:auto">{% include "meeting/meeting_heading.html" with meeting=schedule.meeting updated=schedule.meeting.updated %}</div>
<div id="daycontainer" role="tabpanel">
<ul id="daytabs" class="nav nav-tabs" role="tablist">
{% for day in days %}