Skip to content

Commit acda842

Browse files
committed
Summary: Make the meeting agenda code use the historic info available
for groups. In the process, added some select_related/prefetch_related code to cut the number of database queries in the agenda down from 4000-5000 to about 50. Rewritten agenda CSV code to use Python arrays rather than a template for improved readability/reuse. Rewritten week view to assemble its data in Python. Plus a few other minor cleanups, e.g. lame_description is now gone. Fixes ietf-tools#1088. Branch ready for merge. - Legacy-Id: 9669
1 parent 9067f88 commit acda842

10 files changed

Lines changed: 598 additions & 350 deletions

File tree

ietf/meeting/helpers.py

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,26 @@
22

33
import datetime
44
import pytz
5+
import os
6+
import re
7+
from tempfile import mkstemp
58

69
from django.http import HttpRequest
7-
from django.db.models import Max, Q
10+
from django.db.models import Max, Q, Prefetch, F
811
from django.conf import settings
912
from django.core.cache import cache
1013
from django.utils.cache import get_cache_key
1114
from django.shortcuts import get_object_or_404
1215

1316
import debug # pyflakes:ignore
1417

18+
from ietf.doc.models import Document
1519
from ietf.group.models import Group
1620
from ietf.ietfauth.utils import has_role, user_is_person
1721
from ietf.person.models import Person
1822
from ietf.meeting.models import Meeting
19-
from ietf.utils.history import find_history_active_at
23+
from ietf.utils.history import find_history_active_at, find_history_replacements_active_at
24+
from ietf.utils.pipe import pipe
2025

2126
def find_ads_for_meeting(meeting):
2227
ads = []
@@ -144,6 +149,109 @@ def meeting_updated(meeting):
144149
ts = tz.localize(ts)
145150
return ts
146151

152+
def preprocess_assignments_for_agenda(assignments_queryset, meeting):
153+
# prefetch some stuff to prevent a myriad of small, inefficient queries
154+
assignments_queryset = assignments_queryset.select_related(
155+
"timeslot", "timeslot__location", "timeslot__type",
156+
"session",
157+
"session__group", "session__group__charter", "session__group__charter__group",
158+
).prefetch_related(
159+
Prefetch("session__materials",
160+
queryset=Document.objects.exclude(states__type=F("type"),states__slug='deleted').select_related("group").order_by("order"),
161+
to_attr="prefetched_active_materials",
162+
),
163+
"timeslot__meeting",
164+
)
165+
166+
assignments = list(assignments_queryset) # make sure we're set in stone
167+
168+
meeting_time = datetime.datetime.combine(meeting.date, datetime.time())
169+
170+
# replace groups with historic counterparts
171+
for a in assignments:
172+
if a.session:
173+
a.session.historic_group = None
174+
175+
groups = [a.session.group for a in assignments if a.session and a.session.group]
176+
group_replacements = find_history_replacements_active_at(groups, meeting_time)
177+
178+
for a in assignments:
179+
if a.session and a.session.group:
180+
a.session.historic_group = group_replacements.get(a.session.group_id)
181+
182+
# replace group parents with historic counterparts
183+
for a in assignments:
184+
if a.session and a.session.historic_group:
185+
a.session.historic_group.historic_parent = None
186+
187+
parents = Group.objects.filter(pk__in=set(a.session.historic_group.parent_id for a in assignments if a.session and a.session.historic_group and a.session.historic_group.parent_id))
188+
parent_replacements = find_history_replacements_active_at(parents, meeting_time)
189+
190+
for a in assignments:
191+
if a.session and a.session.historic_group and a.session.historic_group.parent_id:
192+
a.session.historic_group.historic_parent = parent_replacements.get(a.session.historic_group.parent_id)
193+
194+
return assignments
195+
196+
def read_agenda_file(num, doc):
197+
# XXXX FIXME: the path fragment in the code below should be moved to
198+
# settings.py. The *_PATH settings should be generalized to format()
199+
# style python format, something like this:
200+
# DOC_PATH_FORMAT = { "agenda": "/foo/bar/agenda-{meeting.number}/agenda-{meeting-number}-{doc.group}*", }
201+
path = os.path.join(settings.AGENDA_PATH, "%s/agenda/%s" % (num, doc.external_url))
202+
if os.path.exists(path):
203+
with open(path) as f:
204+
return f.read()
205+
else:
206+
return None
207+
208+
def convert_draft_to_pdf(doc_name):
209+
inpath = os.path.join(settings.IDSUBMIT_REPOSITORY_PATH, doc_name + ".txt")
210+
outpath = os.path.join(settings.INTERNET_DRAFT_PDF_PATH, doc_name + ".pdf")
211+
212+
try:
213+
infile = open(inpath, "r")
214+
except IOError:
215+
return
216+
217+
t,tempname = mkstemp()
218+
os.close(t)
219+
tempfile = open(tempname, "w")
220+
221+
pageend = 0;
222+
newpage = 0;
223+
formfeed = 0;
224+
for line in infile:
225+
line = re.sub("\r","",line)
226+
line = re.sub("[ \t]+$","",line)
227+
if re.search("\[?[Pp]age [0-9ivx]+\]?[ \t]*$",line):
228+
pageend=1
229+
tempfile.write(line)
230+
continue
231+
if re.search("^[ \t]*\f",line):
232+
formfeed=1
233+
tempfile.write(line)
234+
continue
235+
if re.search("^ *INTERNET.DRAFT.+[0-9]+ *$",line) or re.search("^ *Internet.Draft.+[0-9]+ *$",line) or re.search("^draft-[-a-z0-9_.]+.*[0-9][0-9][0-9][0-9]$",line) or re.search("^RFC.+[0-9]+$",line):
236+
newpage=1
237+
if re.search("^[ \t]*$",line) and pageend and not newpage:
238+
continue
239+
if pageend and newpage and not formfeed:
240+
tempfile.write("\f")
241+
pageend=0
242+
formfeed=0
243+
newpage=0
244+
tempfile.write(line)
245+
246+
infile.close()
247+
tempfile.close()
248+
t,psname = mkstemp()
249+
os.close(t)
250+
pipe("enscript --margins 76::76: -B -q -p "+psname + " " +tempname)
251+
os.unlink(tempname)
252+
pipe("ps2pdf "+psname+" "+outpath)
253+
os.unlink(psname)
254+
147255
def agenda_permissions(meeting, schedule, user):
148256
# do this in positive logic.
149257
cansee = False

ietf/meeting/models.py

Lines changed: 52 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -638,16 +638,6 @@ def sessions_that_can_meet(self):
638638
self._cached_sessions_that_can_meet = self.meeting.sessions_that_can_meet.all()
639639
return self._cached_sessions_that_can_meet
640640

641-
def area_list(self):
642-
return ( self.assignments.filter(session__group__type__slug__in=['wg', 'rg', 'ag', 'iab'],
643-
session__group__parent__isnull=False)
644-
.order_by('session__group__parent__acronym')
645-
.values_list('session__group__parent__acronym', flat=True)
646-
.distinct() )
647-
648-
def groups(self):
649-
return Group.objects.filter(type__slug__in=['wg', 'rg', 'ag', 'iab'], parent__isnull=False, session__scheduledsession__schedule=self).exclude(session__scheduledsession__timeslot__type__in=['lead','offagenda']).distinct().order_by('parent__acronym', 'acronym')
650-
651641
# calculate badness of entire schedule
652642
def calc_badness(self):
653643
# now calculate badness
@@ -768,6 +758,35 @@ def json_dict(self, host_scheme):
768758
ss["pinned"] = self.pinned
769759
return ss
770760

761+
def slug(self):
762+
"""Return sensible id string for session, e.g. suitable for use as HTML anchor."""
763+
components = []
764+
765+
if not self.timeslot:
766+
components.append("unknown")
767+
768+
if not self.session or not (getattr(self.session, "historic_group") or self.session.group):
769+
components.append("unknown")
770+
else:
771+
components.append(self.timeslot.time.strftime("%a-%H%M"))
772+
773+
g = getattr(self.session, "historic_group", None) or self.session.group
774+
775+
if self.timeslot.type_id in ('break', 'reg', 'other'):
776+
components.append(g.acronym)
777+
778+
if self.timeslot.type_id in ('session', 'plenary'):
779+
if self.timeslot.type_id == "plenary":
780+
components.append("1plenary")
781+
else:
782+
p = getattr(g, "historic_parent", None) or g.parent
783+
if p and p.type_id in ("area", "irtf"):
784+
components.append(p.acronym)
785+
786+
components.append(g.acronym)
787+
788+
return u"-".join(components).lower()
789+
771790
class Constraint(models.Model):
772791
"""
773792
Specifies a constraint on the scheduling.
@@ -886,14 +905,30 @@ class Session(models.Model):
886905

887906
# Should work on how materials are captured so that deleted things are no longer associated with the session
888907
# (We can keep the information about something being added to and removed from a session in the document's history)
908+
def get_material(self, material_type, only_one):
909+
if hasattr(self, "prefetched_active_materials"):
910+
l = [d for d in self.prefetched_active_materials if d.type_id == material_type]
911+
for d in l:
912+
d.meeting_related = lambda: True
913+
else:
914+
l = self.materials.filter(type=material_type).exclude(states__type=material_type, states__slug='deleted').order_by("order")
915+
916+
if only_one:
917+
if l:
918+
return l[0]
919+
else:
920+
return None
921+
else:
922+
return l
923+
889924
def agenda(self):
890-
return self.materials.filter(type='agenda').exclude(states__type='agenda',states__slug='deleted').first()
925+
return self.get_material("agenda", only_one=True)
891926

892927
def minutes(self):
893-
return self.materials.filter(type='minutes').exclude(states__type='minutes',states__slug='deleted').first()
928+
return self.get_material("minutes", only_one=True)
894929

895930
def slides(self):
896-
return list(self.materials.filter(type='slides').exclude(states__type='slides',states__slug='deleted').order_by('order'))
931+
return list(self.get_material("slides", only_one=False))
897932

898933
def __unicode__(self):
899934
if self.meeting.type_id == "interim":
@@ -908,9 +943,6 @@ def __unicode__(self):
908943
ss0name = ','.join([x.timeslot.time.strftime("%a-%H%M") for x in ss])
909944
return u"%s: %s %s %s" % (self.meeting, self.group.acronym, self.name, ss0name)
910945

911-
def is_bof(self):
912-
return self.group.is_bof();
913-
914946
@property
915947
def short_name(self):
916948
if self.name:
@@ -994,7 +1026,7 @@ def json_dict(self, host_scheme):
9941026
sess1['name'] = str(self.name)
9951027
sess1['title'] = str(self.short_name)
9961028
sess1['short_name'] = str(self.short_name)
997-
sess1['bof'] = str(self.is_bof())
1029+
sess1['bof'] = str(self.group.is_bof())
9981030
sess1['agenda_note'] = str(self.agenda_note)
9991031
sess1['attendees'] = str(self.attendees)
10001032
sess1['status'] = str(self.status)
@@ -1024,18 +1056,6 @@ def agenda_text(self):
10241056
else:
10251057
return "The agenda has not been uploaded yet."
10261058

1027-
# FIXME - This used to be called 'type'. It is only used in agenda.csv and agenda.txt.
1028-
# It will return the _wrong thing_ if you look back at an agenda of an earlier meeting
1029-
# where group X was a BOF at the time, but is now a WG.
1030-
# It also doesn't return anything useful for RG, area sessions, or anything that's not group type 'wg'.
1031-
# A better thing to do is find a way to note when a meeting was a BoF meeting and use that, removing this
1032-
# function altogether.
1033-
def lame_description(self):
1034-
if self.group.type.slug in [ "wg" ]:
1035-
return "BOF" if self.group.state.slug in ["bof", "bof-conc"] else "WG"
1036-
else:
1037-
return ""
1038-
10391059
def ical_status(self):
10401060
if self.status.slug == 'canceled': # sic
10411061
return "CANCELLED"
@@ -1050,13 +1070,13 @@ def agenda_file(self):
10501070
if not hasattr(self, '_agenda_file'):
10511071
self._agenda_file = ""
10521072

1053-
docs = self.materials.filter(type="agenda", states__type="agenda", states__slug="active")
1054-
if not docs:
1073+
agenda = self.agenda()
1074+
if not agenda:
10551075
return ""
10561076

10571077
# we use external_url at the moment, should probably regularize
10581078
# the filenames to match the document name instead
1059-
filename = docs[0].external_url
1079+
filename = agenda.external_url
10601080
self._agenda_file = "%s/agenda/%s" % (self.meeting.number, filename)
10611081

10621082
return self._agenda_file

0 commit comments

Comments
 (0)