Skip to content

Commit

Permalink
Improve proceedings display with new title block, configurable host l…
Browse files Browse the repository at this point in the history
…ogos, and additional PDF or URL materials. Fixes #3147. Commit ready for merge.

 - Legacy-Id: 19306
  • Loading branch information
jennifer-richards committed Aug 30, 2021
1 parent ca78da6 commit 2060173
Show file tree
Hide file tree
Showing 46 changed files with 2,988 additions and 186 deletions.
1 change: 1 addition & 0 deletions docker/settings_local.py
Expand Up @@ -32,6 +32,7 @@
RFC_PATH = "test/rfc/"

AGENDA_PATH = 'data/developers/www6s/proceedings/'
MEETINGHOST_LOGO_PATH = AGENDA_PATH

USING_DEBUG_EMAIL_SERVER=True
EMAIL_HOST='localhost'
Expand Down
15 changes: 15 additions & 0 deletions ietf/doc/factories.py
Expand Up @@ -439,3 +439,18 @@ def states(obj, create, extracted, **kwargs):
else:
obj.set_state(State.objects.get(type_id='bofreq',slug='proposed'))


class ProceedingsMaterialDocFactory(BaseDocumentFactory):
type_id = 'procmaterials'
abstract = ''
expires = None

@factory.post_generation
def states(obj, create, extracted, **kwargs):
if not create:
return
if extracted:
for (state_type_id,state_slug) in extracted:
obj.set_state(State.objects.get(type_id=state_type_id,slug=state_slug))
else:
obj.set_state(State.objects.get(type_id='procmaterials', slug='active'))
34 changes: 34 additions & 0 deletions ietf/doc/migrations/0044_procmaterials_states.py
@@ -0,0 +1,34 @@
# Copyright The IETF Trust 2021 All Rights Reserved

# Generated by Django 2.2.23 on 2021-05-21 13:29

from django.db import migrations

def forward(apps, schema_editor):
StateType = apps.get_model('doc', 'StateType')
State = apps.get_model('doc', 'State')

StateType.objects.create(slug='procmaterials', label='Proceedings Materials State')
active = State.objects.create(type_id='procmaterials', slug='active', name='Active', used=True, desc='The material is active', order=0)
removed = State.objects.create(type_id='procmaterials', slug='removed', name='Removed', used=True, desc='The material is removed', order=1)

active.next_states.set([removed])
removed.next_states.set([active])

def reverse(apps, schema_editor):
StateType = apps.get_model('doc', 'StateType')
State = apps.get_model('doc', 'State')
State.objects.filter(type_id='procmaterials').delete()
StateType.objects.filter(slug='procmaterials').delete()


class Migration(migrations.Migration):

dependencies = [
('doc', '0043_bofreq_docevents'),
('name', '0030_add_procmaterials'),
]

operations = [
migrations.RunPython(forward, reverse)
]
82 changes: 66 additions & 16 deletions ietf/doc/models.py
Expand Up @@ -9,6 +9,8 @@
import rfc2html
import time

from typing import Optional, TYPE_CHECKING

from django.db import models
from django.core import checks
from django.core.cache import caches
Expand All @@ -34,6 +36,9 @@
from ietf.utils.validators import validate_no_control_chars
from ietf.utils.mail import formataddr
from ietf.utils.models import ForeignKey
if TYPE_CHECKING:
# importing other than for type checking causes errors due to cyclic imports
from ietf.meeting.models import ProceedingsMaterial, Session

logger = logging.getLogger('django')

Expand Down Expand Up @@ -129,10 +134,11 @@ def get_file_path(self):
self._cached_file_path = settings.INTERNET_DRAFT_PATH
else:
self._cached_file_path = settings.INTERNET_ALL_DRAFTS_ARCHIVE_DIR
elif self.type_id in ("agenda", "minutes", "slides", "bluesheets") and self.meeting_related():
doc = self.doc if isinstance(self, DocHistory) else self
if doc.session_set.exists():
meeting = doc.session_set.first().meeting
elif self.meeting_related() and self.type_id in (
"agenda", "minutes", "slides", "bluesheets", "procmaterials"
):
meeting = self.get_related_meeting()
if meeting is not None:
self._cached_file_path = os.path.join(meeting.get_materials_path(), self.type_id) + "/"
else:
self._cached_file_path = ""
Expand Down Expand Up @@ -160,8 +166,9 @@ def get_base_name(self):
self._cached_base_name = "%s.txt" % self.canonical_name()
else:
self._cached_base_name = "%s-%s.txt" % (self.name, self.rev)
elif self.type_id in ["slides", "agenda", "minutes", "bluesheets", ] and self.meeting_related():
self._cached_base_name = "%s-%s.txt" % (self.canonical_name(), self.rev)
elif self.type_id in ["slides", "agenda", "minutes", "bluesheets", "procmaterials", ] and self.meeting_related():
ext = 'pdf' if self.type_id == 'procmaterials' else 'txt'
self._cached_base_name = f'{self.canonical_name()}-{self.rev}.{ext}'
elif self.type_id == 'review':
# 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)
self._cached_base_name = "%s.txt" % self.name
Expand Down Expand Up @@ -218,7 +225,6 @@ def _get_ref(self, meeting=None, meeting_doc_refs=settings.MEETING_DOC_HREFS):
log.unreachable('2018-12-28')
pass


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

if self.is_meeting_related:
if not meeting:
# we need to do this because DocHistory items don't have
# any session_set entry:
doc = self.doc if isinstance(self, DocHistory) else self
sess = doc.session_set.first()
if not sess:
return ""
meeting = sess.meeting
meeting = self.get_related_meeting()
if meeting is None:
return ''

# After IETF 96, meeting materials acquired revision
# handling, and the document naming changed.
if meeting.number.isdigit() and int(meeting.number) <= 96:
if meeting.proceedings_format_version == 1:
format = settings.MEETING_DOC_OLD_HREFS[self.type_id]
else:
# This branch includes interims
Expand Down Expand Up @@ -420,10 +423,44 @@ def has_rfc_editor_note(self):
return e != None and (e.text != "")

def meeting_related(self):
if self.type_id in ("agenda","minutes","bluesheets","slides","recording"):
if self.type_id in ("agenda","minutes","bluesheets","slides","recording","procmaterials"):
return self.type_id != "slides" or self.get_state_slug('reuse_policy')=='single'
return False

def get_related_session(self) -> Optional['Session']:
"""Get the meeting session related to this document
Return None if there is no related session.
Must define this in DocumentInfo subclasses.
"""
raise NotImplementedError(f'Class {self.__class__} must define get_related_session()')

def get_related_proceedings_material(self) -> Optional['ProceedingsMaterial']:
"""Get the proceedings material related to this document
Return None if there is no related proceedings material.
Must define this in DocumentInfo subclasses.
"""
raise NotImplementedError(f'Class {self.__class__} must define get_related_proceedings_material()')

def get_related_meeting(self):
"""Get the meeting this document relates to"""
if not self.meeting_related():
return None # no related meeting if not meeting_related!
elif self.type_id in ("agenda", "minutes", "slides", "bluesheets",):
# session-related
session = self.get_related_session()
if session is not None:
return session.meeting
elif self.type_id == "procmaterials":
# proceedings-related
material = self.get_related_proceedings_material()
if material is not None:
return material.meeting
else:
log.unreachable('2021-08-29') # if meeting_related, there must be a way to retrieve the meeting!
return None

def relations_that(self, relationship):
"""Return the related-document objects that describe a given relationship targeting self."""
if isinstance(relationship, str):
Expand Down Expand Up @@ -713,6 +750,13 @@ def get_absolute_url(self):
self._cached_absolute_url = url
return self._cached_absolute_url

def get_related_session(self):
sessions = self.session_set.all()
return sessions.first()

def get_related_proceedings_material(self):
return self.proceedingsmaterial_set.first()

def file_tag(self):
return "<%s>" % self.filename_with_rev()

Expand Down Expand Up @@ -1003,6 +1047,12 @@ class DocHistory(DocumentInfo):
def __str__(self):
return force_text(self.doc.name)

def get_related_session(self):
return self.doc.get_related_session()

def get_related_proceedings_material(self):
return self.doc.get_related_proceedings_material()

def canonical_name(self):
if hasattr(self, '_canonical_name'):
return self._canonical_name
Expand Down
14 changes: 12 additions & 2 deletions ietf/doc/views_doc.py
Expand Up @@ -614,7 +614,7 @@ def document_main(request, name, rev=None):

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

# determine whether uploads are allowed
can_upload = can_manage_material and not snapshot
if doc.group is None:
can_upload = can_upload and (doc.type_id == 'procmaterials')
else:
can_upload = (
can_upload
and doc.group.features.has_nonsession_materials
and doc.type_id in doc.group.features.material_types
)
return render(request, "doc/document_material.html",
dict(doc=doc,
top=top,
Expand All @@ -654,7 +664,7 @@ def document_main(request, name, rev=None):
latest_rev=latest_rev,
snapshot=snapshot,
can_manage_material=can_manage_material,
in_group_materials_types = doc.group and doc.group.features.has_nonsession_materials and doc.type_id in doc.group.features.material_types,
can_upload = can_upload,
other_types=other_types,
presentations=presentations,
))
Expand Down
1 change: 1 addition & 0 deletions ietf/doc/views_help.py
Expand Up @@ -18,6 +18,7 @@ def state_help(request, type):
"conflict-review": ("conflrev", "Conflict Review States"),
"status-change": ("statchg", "RFC Status Change States"),
"bofreq": ("bofreq", "BOF Request States"),
"procmaterials": ("procmaterials", "Proceedings Materials States"),
}.get(type, (None, None))
state_type = get_object_or_404(StateType, slug=slug)

Expand Down
35 changes: 30 additions & 5 deletions ietf/doc/views_material.py
Expand Up @@ -54,7 +54,7 @@ def __init__(self, doc_type, action, group, doc, *args, **kwargs):
self.fields["state"].widget = forms.HiddenInput()
self.fields["state"].queryset = self.fields["state"].queryset.filter(slug="active")
self.fields["state"].initial = self.fields["state"].queryset[0].pk
self.fields["name"].initial = "%s-%s-" % (doc_type.slug, group.acronym)
self.fields["name"].initial = self._default_name()
else:
del self.fields["name"]

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

if doc_type.slug == 'procmaterials' and 'abstract' in self.fields:
del self.fields['abstract']

def _default_name(self):
return "%s-%s-" % (self.doc_type.slug, self.group.acronym)

def clean_name(self):
name = self.cleaned_data["name"].strip().rstrip("-")

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

if (document_type not in DocTypeName.objects.filter(slug__in=group.features.material_types)
and document_type.slug not in ['minutes','agenda','bluesheets',]):
valid_doctypes = ['procmaterials']
if group is not None:
valid_doctypes.extend(['minutes','agenda','bluesheets'])
valid_doctypes.extend(group.features.material_types)

if document_type.slug not in valid_doctypes:
raise Http404


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

# decide where to go if upload is canceled
if doc:
back_href = urlreverse('ietf.doc.views_doc.document_main', kwargs={'name': doc.name})
else:
back_href = urlreverse('ietf.group.views.materials', kwargs={'acronym': group.acronym})

if document_type.slug == 'procmaterials':
name_prefix = 'proceedings-'
else:
name_prefix = f'{document_type.slug}-{group.acronym}-'

return render(request, 'doc/material/edit_material.html', {
'group': group,
'form': form,
'action': action,
'document_type': document_type,
'material_type': document_type,
'name_prefix': name_prefix,
'doc': doc,
'doc_name': doc.name if doc else "",
'back_href': back_href,
})


2 changes: 1 addition & 1 deletion ietf/group/utils.py
Expand Up @@ -125,7 +125,7 @@ def milestone_reviewer_for_group_type(group_type):
return "Area Director"

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

def can_manage_session_materials(user, group, session):
return has_role(user, 'Secretariat') or (group.has_role(user, group.features.matman_roles) and not session.is_material_submission_cutoff())
Expand Down
17 changes: 16 additions & 1 deletion ietf/meeting/admin.py
Expand Up @@ -6,7 +6,8 @@

from ietf.meeting.models import (Meeting, Room, Session, TimeSlot, Constraint, Schedule,
SchedTimeSessAssignment, ResourceAssociation, FloorPlan, UrlResource,
SessionPresentation, ImportantDate, SlideSubmission, SchedulingEvent, BusinessConstraint)
SessionPresentation, ImportantDate, SlideSubmission, SchedulingEvent, BusinessConstraint,
ProceedingsMaterial, MeetingHost)


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

admin.site.register(SlideSubmission, SlideSubmissionAdmin)


class ProceedingsMaterialAdmin(admin.ModelAdmin):
model = ProceedingsMaterial
list_display = ['meeting', 'type', 'document']
raw_id_fields = ['meeting', 'document']
admin.site.register(ProceedingsMaterial, ProceedingsMaterialAdmin)


class MeetingHostAdmin(admin.ModelAdmin):
model = MeetingHost
list_display = ['name', 'meeting']
raw_id_fields = ['meeting']
admin.site.register(MeetingHost, MeetingHostAdmin)

0 comments on commit 2060173

Please sign in to comment.