Skip to content

Commit 65f7f62

Browse files
committed
Merged in [19306] from jennifer@painless-security.com:
Improve proceedings display with new title block, configurable host logos, and additional PDF or URL materials. Fixes ietf-tools#3147. - Legacy-Id: 19327 Note: SVN reference [19306] has been migrated to Git commit 2060173
2 parents b80366b + 2060173 commit 65f7f62

46 files changed

Lines changed: 2997 additions & 189 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docker/settings_local.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
RFC_PATH = "test/rfc/"
3333

3434
AGENDA_PATH = 'data/developers/www6s/proceedings/'
35+
MEETINGHOST_LOGO_PATH = AGENDA_PATH
3536

3637
USING_DEBUG_EMAIL_SERVER=True
3738
EMAIL_HOST='localhost'

ietf/doc/factories.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,3 +439,18 @@ def states(obj, create, extracted, **kwargs):
439439
else:
440440
obj.set_state(State.objects.get(type_id='bofreq',slug='proposed'))
441441

442+
443+
class ProceedingsMaterialDocFactory(BaseDocumentFactory):
444+
type_id = 'procmaterials'
445+
abstract = ''
446+
expires = None
447+
448+
@factory.post_generation
449+
def states(obj, create, extracted, **kwargs):
450+
if not create:
451+
return
452+
if extracted:
453+
for (state_type_id,state_slug) in extracted:
454+
obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug))
455+
else:
456+
obj.set_state(State.objects.get(type_id='procmaterials', slug='active'))
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright The IETF Trust 2021 All Rights Reserved
2+
3+
# Generated by Django 2.2.23 on 2021-05-21 13:29
4+
5+
from django.db import migrations
6+
7+
def forward(apps, schema_editor):
8+
StateType = apps.get_model('doc', 'StateType')
9+
State = apps.get_model('doc', 'State')
10+
11+
StateType.objects.create(slug='procmaterials', label='Proceedings Materials State')
12+
active = State.objects.create(type_id='procmaterials', slug='active', name='Active', used=True, desc='The material is active', order=0)
13+
removed = State.objects.create(type_id='procmaterials', slug='removed', name='Removed', used=True, desc='The material is removed', order=1)
14+
15+
active.next_states.set([removed])
16+
removed.next_states.set([active])
17+
18+
def reverse(apps, schema_editor):
19+
StateType = apps.get_model('doc', 'StateType')
20+
State = apps.get_model('doc', 'State')
21+
State.objects.filter(type_id='procmaterials').delete()
22+
StateType.objects.filter(slug='procmaterials').delete()
23+
24+
25+
class Migration(migrations.Migration):
26+
27+
dependencies = [
28+
('doc', '0043_bofreq_docevents'),
29+
('name', '0030_add_procmaterials'),
30+
]
31+
32+
operations = [
33+
migrations.RunPython(forward, reverse)
34+
]

ietf/doc/models.py

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import rfc2html
1010
import time
1111

12+
from typing import Optional, TYPE_CHECKING
13+
1214
from django.db import models
1315
from django.core import checks
1416
from django.core.cache import caches
@@ -34,6 +36,9 @@
3436
from ietf.utils.validators import validate_no_control_chars
3537
from ietf.utils.mail import formataddr
3638
from ietf.utils.models import ForeignKey
39+
if TYPE_CHECKING:
40+
# importing other than for type checking causes errors due to cyclic imports
41+
from ietf.meeting.models import ProceedingsMaterial, Session
3742

3843
logger = logging.getLogger('django')
3944

@@ -129,10 +134,11 @@ def get_file_path(self):
129134
self._cached_file_path = settings.INTERNET_DRAFT_PATH
130135
else:
131136
self._cached_file_path = settings.INTERNET_ALL_DRAFTS_ARCHIVE_DIR
132-
elif self.type_id in ("agenda", "minutes", "slides", "bluesheets") and self.meeting_related():
133-
doc = self.doc if isinstance(self, DocHistory) else self
134-
if doc.session_set.exists():
135-
meeting = doc.session_set.first().meeting
137+
elif self.meeting_related() and self.type_id in (
138+
"agenda", "minutes", "slides", "bluesheets", "procmaterials"
139+
):
140+
meeting = self.get_related_meeting()
141+
if meeting is not None:
136142
self._cached_file_path = os.path.join(meeting.get_materials_path(), self.type_id) + "/"
137143
else:
138144
self._cached_file_path = ""
@@ -160,8 +166,9 @@ def get_base_name(self):
160166
self._cached_base_name = "%s.txt" % self.canonical_name()
161167
else:
162168
self._cached_base_name = "%s-%s.txt" % (self.name, self.rev)
163-
elif self.type_id in ["slides", "agenda", "minutes", "bluesheets", ] and self.meeting_related():
164-
self._cached_base_name = "%s-%s.txt" % (self.canonical_name(), self.rev)
169+
elif self.type_id in ["slides", "agenda", "minutes", "bluesheets", "procmaterials", ] and self.meeting_related():
170+
ext = 'pdf' if self.type_id == 'procmaterials' else 'txt'
171+
self._cached_base_name = f'{self.canonical_name()}-{self.rev}.{ext}'
165172
elif self.type_id == 'review':
166173
# TODO: This will be wrong if a review is updated on the same day it was created (or updated more than once on the same day)
167174
self._cached_base_name = "%s.txt" % self.name
@@ -218,7 +225,6 @@ def _get_ref(self, meeting=None, meeting_doc_refs=settings.MEETING_DOC_HREFS):
218225
log.unreachable('2018-12-28')
219226
pass
220227

221-
222228
if self.type_id in settings.DOC_HREFS and self.type_id in meeting_doc_refs:
223229
if self.meeting_related():
224230
self.is_meeting_related = True
@@ -239,16 +245,13 @@ def _get_ref(self, meeting=None, meeting_doc_refs=settings.MEETING_DOC_HREFS):
239245

240246
if self.is_meeting_related:
241247
if not meeting:
242-
# we need to do this because DocHistory items don't have
243-
# any session_set entry:
244-
doc = self.doc if isinstance(self, DocHistory) else self
245-
sess = doc.session_set.first()
246-
if not sess:
247-
return ""
248-
meeting = sess.meeting
248+
meeting = self.get_related_meeting()
249+
if meeting is None:
250+
return ''
251+
249252
# After IETF 96, meeting materials acquired revision
250253
# handling, and the document naming changed.
251-
if meeting.number.isdigit() and int(meeting.number) <= 96:
254+
if meeting.proceedings_format_version == 1:
252255
format = settings.MEETING_DOC_OLD_HREFS[self.type_id]
253256
else:
254257
# This branch includes interims
@@ -420,10 +423,44 @@ def has_rfc_editor_note(self):
420423
return e != None and (e.text != "")
421424

422425
def meeting_related(self):
423-
if self.type_id in ("agenda","minutes","bluesheets","slides","recording"):
426+
if self.type_id in ("agenda","minutes","bluesheets","slides","recording","procmaterials"):
424427
return self.type_id != "slides" or self.get_state_slug('reuse_policy')=='single'
425428
return False
426429

430+
def get_related_session(self) -> Optional['Session']:
431+
"""Get the meeting session related to this document
432+
433+
Return None if there is no related session.
434+
Must define this in DocumentInfo subclasses.
435+
"""
436+
raise NotImplementedError(f'Class {self.__class__} must define get_related_session()')
437+
438+
def get_related_proceedings_material(self) -> Optional['ProceedingsMaterial']:
439+
"""Get the proceedings material related to this document
440+
441+
Return None if there is no related proceedings material.
442+
Must define this in DocumentInfo subclasses.
443+
"""
444+
raise NotImplementedError(f'Class {self.__class__} must define get_related_proceedings_material()')
445+
446+
def get_related_meeting(self):
447+
"""Get the meeting this document relates to"""
448+
if not self.meeting_related():
449+
return None # no related meeting if not meeting_related!
450+
elif self.type_id in ("agenda", "minutes", "slides", "bluesheets",):
451+
# session-related
452+
session = self.get_related_session()
453+
if session is not None:
454+
return session.meeting
455+
elif self.type_id == "procmaterials":
456+
# proceedings-related
457+
material = self.get_related_proceedings_material()
458+
if material is not None:
459+
return material.meeting
460+
else:
461+
log.unreachable('2021-08-29') # if meeting_related, there must be a way to retrieve the meeting!
462+
return None
463+
427464
def relations_that(self, relationship):
428465
"""Return the related-document objects that describe a given relationship targeting self."""
429466
if isinstance(relationship, str):
@@ -713,6 +750,13 @@ def get_absolute_url(self):
713750
self._cached_absolute_url = url
714751
return self._cached_absolute_url
715752

753+
def get_related_session(self):
754+
sessions = self.session_set.all()
755+
return sessions.first()
756+
757+
def get_related_proceedings_material(self):
758+
return self.proceedingsmaterial_set.first()
759+
716760
def file_tag(self):
717761
return "<%s>" % self.filename_with_rev()
718762

@@ -1003,6 +1047,12 @@ class DocHistory(DocumentInfo):
10031047
def __str__(self):
10041048
return force_text(self.doc.name)
10051049

1050+
def get_related_session(self):
1051+
return self.doc.get_related_session()
1052+
1053+
def get_related_proceedings_material(self):
1054+
return self.doc.get_related_proceedings_material()
1055+
10061056
def canonical_name(self):
10071057
if hasattr(self, '_canonical_name'):
10081058
return self._canonical_name

ietf/doc/views_doc.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ def document_main(request, name, rev=None):
614614

615615
# TODO : Add "recording", and "bluesheets" here when those documents are appropriately
616616
# created and content is made available on disk
617-
if doc.type_id in ("slides", "agenda", "minutes", "bluesheets",):
617+
if doc.type_id in ("slides", "agenda", "minutes", "bluesheets","procmaterials",):
618618
can_manage_material = can_manage_materials(request.user, doc.group)
619619
presentations = doc.future_presentations()
620620
if doc.uploaded_filename:
@@ -645,6 +645,16 @@ def document_main(request, name, rev=None):
645645
t = "markdown"
646646
other_types.append((t, url))
647647

648+
# determine whether uploads are allowed
649+
can_upload = can_manage_material and not snapshot
650+
if doc.group is None:
651+
can_upload = can_upload and (doc.type_id == 'procmaterials')
652+
else:
653+
can_upload = (
654+
can_upload
655+
and doc.group.features.has_nonsession_materials
656+
and doc.type_id in doc.group.features.material_types
657+
)
648658
return render(request, "doc/document_material.html",
649659
dict(doc=doc,
650660
top=top,
@@ -654,7 +664,7 @@ def document_main(request, name, rev=None):
654664
latest_rev=latest_rev,
655665
snapshot=snapshot,
656666
can_manage_material=can_manage_material,
657-
in_group_materials_types = doc.group and doc.group.features.has_nonsession_materials and doc.type_id in doc.group.features.material_types,
667+
can_upload = can_upload,
658668
other_types=other_types,
659669
presentations=presentations,
660670
))

ietf/doc/views_help.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def state_help(request, type):
1818
"conflict-review": ("conflrev", "Conflict Review States"),
1919
"status-change": ("statchg", "RFC Status Change States"),
2020
"bofreq": ("bofreq", "BOF Request States"),
21+
"procmaterials": ("procmaterials", "Proceedings Materials States"),
2122
}.get(type, (None, None))
2223
state_type = get_object_or_404(StateType, slug=slug)
2324

ietf/doc/views_material.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def __init__(self, doc_type, action, group, doc, *args, **kwargs):
5454
self.fields["state"].widget = forms.HiddenInput()
5555
self.fields["state"].queryset = self.fields["state"].queryset.filter(slug="active")
5656
self.fields["state"].initial = self.fields["state"].queryset[0].pk
57-
self.fields["name"].initial = "%s-%s-" % (doc_type.slug, group.acronym)
57+
self.fields["name"].initial = self._default_name()
5858
else:
5959
del self.fields["name"]
6060

@@ -69,6 +69,12 @@ def __init__(self, doc_type, action, group, doc, *args, **kwargs):
6969
if fieldname != action:
7070
del self.fields[fieldname]
7171

72+
if doc_type.slug == 'procmaterials' and 'abstract' in self.fields:
73+
del self.fields['abstract']
74+
75+
def _default_name(self):
76+
return "%s-%s-" % (self.doc_type.slug, self.group.acronym)
77+
7278
def clean_name(self):
7379
name = self.cleaned_data["name"].strip().rstrip("-")
7480

@@ -101,10 +107,13 @@ def edit_material(request, name=None, acronym=None, action=None, doc_type=None):
101107
group = doc.group
102108
document_type = doc.type
103109

104-
if (document_type not in DocTypeName.objects.filter(slug__in=group.features.material_types)
105-
and document_type.slug not in ['minutes','agenda','bluesheets',]):
110+
valid_doctypes = ['procmaterials']
111+
if group is not None:
112+
valid_doctypes.extend(['minutes','agenda','bluesheets'])
113+
valid_doctypes.extend(group.features.material_types)
114+
115+
if document_type.slug not in valid_doctypes:
106116
raise Http404
107-
108117

109118
if not can_manage_materials(request.user, group):
110119
permission_denied(request, "You don't have permission to access this view")
@@ -186,10 +195,26 @@ def edit_material(request, name=None, acronym=None, action=None, doc_type=None):
186195
else:
187196
form = UploadMaterialForm(document_type, action, group, doc)
188197

198+
# decide where to go if upload is canceled
199+
if doc:
200+
back_href = urlreverse('ietf.doc.views_doc.document_main', kwargs={'name': doc.name})
201+
else:
202+
back_href = urlreverse('ietf.group.views.materials', kwargs={'acronym': group.acronym})
203+
204+
if document_type.slug == 'procmaterials':
205+
name_prefix = 'proceedings-'
206+
else:
207+
name_prefix = f'{document_type.slug}-{group.acronym}-'
208+
189209
return render(request, 'doc/material/edit_material.html', {
190210
'group': group,
191211
'form': form,
192212
'action': action,
193-
'document_type': document_type,
213+
'material_type': document_type,
214+
'name_prefix': name_prefix,
215+
'doc': doc,
194216
'doc_name': doc.name if doc else "",
217+
'back_href': back_href,
195218
})
219+
220+

ietf/group/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def milestone_reviewer_for_group_type(group_type):
125125
return "Area Director"
126126

127127
def can_manage_materials(user, group):
128-
return has_role(user, 'Secretariat') or group.has_role(user, group.features.matman_roles)
128+
return has_role(user, 'Secretariat') or (group is not None and group.has_role(user, group.features.matman_roles))
129129

130130
def can_manage_session_materials(user, group, session):
131131
return has_role(user, 'Secretariat') or (group.has_role(user, group.features.matman_roles) and not session.is_material_submission_cutoff())

ietf/meeting/admin.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
from ietf.meeting.models import (Meeting, Room, Session, TimeSlot, Constraint, Schedule,
88
SchedTimeSessAssignment, ResourceAssociation, FloorPlan, UrlResource,
9-
SessionPresentation, ImportantDate, SlideSubmission, SchedulingEvent, BusinessConstraint)
9+
SessionPresentation, ImportantDate, SlideSubmission, SchedulingEvent, BusinessConstraint,
10+
ProceedingsMaterial, MeetingHost)
1011

1112

1213
class UrlResourceAdmin(admin.ModelAdmin):
@@ -186,3 +187,17 @@ class SlideSubmissionAdmin(admin.ModelAdmin):
186187
raw_id_fields = ['submitter', 'session']
187188

188189
admin.site.register(SlideSubmission, SlideSubmissionAdmin)
190+
191+
192+
class ProceedingsMaterialAdmin(admin.ModelAdmin):
193+
model = ProceedingsMaterial
194+
list_display = ['meeting', 'type', 'document']
195+
raw_id_fields = ['meeting', 'document']
196+
admin.site.register(ProceedingsMaterial, ProceedingsMaterialAdmin)
197+
198+
199+
class MeetingHostAdmin(admin.ModelAdmin):
200+
model = MeetingHost
201+
list_display = ['name', 'meeting']
202+
raw_id_fields = ['meeting']
203+
admin.site.register(MeetingHost, MeetingHostAdmin)

0 commit comments

Comments
 (0)