Skip to content

Commit e686c5c

Browse files
committed
Allow upload of bluesheets via materials upload interface
- Legacy-Id: 8857
1 parent 9cf30a0 commit e686c5c

12 files changed

Lines changed: 658 additions & 63 deletions

File tree

ietf/doc/models.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ def get_file_path(self):
7979

8080
if self.type_id == "draft":
8181
return settings.INTERNET_DRAFT_PATH
82-
elif self.type_id in ("agenda", "minutes", "slides") and self.meeting_related():
83-
meeting = self.name.split("-")[1]
84-
return os.path.join(settings.AGENDA_PATH, meeting, self.type_id) + "/"
82+
elif self.type_id in ("agenda", "minutes", "slides", "bluesheets") and self.meeting_related():
83+
meeting = self.session_set.first().meeting
84+
return os.path.join(meeting.get_materials_path(), self.type_id) + "/"
8585
elif self.type_id == "charter":
8686
return settings.CHARTER_PATH
8787
elif self.type_id == "conflrev":
@@ -186,7 +186,7 @@ def active_ballot(self):
186186
return None
187187

188188
def meeting_related(self):
189-
return(self.type_id in ("agenda", "minutes", "slides") and (
189+
return(self.type_id in ("agenda", "minutes", "slides", "bluesheets") and (
190190
self.name.split("-")[1] == "interim"
191191
or (self.session_set.exists() if isinstance(self, Document) else self.doc.session_set.exists())))
192192

@@ -263,7 +263,7 @@ def get_absolute_url(self):
263263
name = self.name
264264
if self.type_id == "draft" and self.get_state_slug() == "rfc":
265265
name = self.canonical_name()
266-
elif self.type_id in ('slides','agenda','minutes'):
266+
elif self.type_id in ('slides','agenda','minutes','bluesheets'):
267267
session = self.session_set.first()
268268
if session:
269269
meeting = session.meeting

ietf/meeting/models.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,19 @@ def get_ietf_monday(cls):
100100
date = cls.objects.all().filter(type="ietf").order_by('-date')[0].date
101101
return date + datetime.timedelta(days=-date.weekday(), weeks=1)
102102

103+
def get_materials_path(self):
104+
path = ''
105+
if self.type_id == 'ietf':
106+
path = os.path.join(settings.AGENDA_PATH,self.number)
107+
elif self.type_id == 'interim':
108+
path = os.path.join(settings.AGENDA_PATH,
109+
'interim',
110+
self.date.strftime('%Y'),
111+
self.date.strftime('%m'),
112+
self.date.strftime('%d'),
113+
self.session_set.all()[0].group.acronym)
114+
return path
115+
103116
# the various dates are currently computed
104117
def get_submission_start_date(self):
105118
return self.date + datetime.timedelta(days=settings.SUBMISSION_START_DAYS)

ietf/name/fixtures/names.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,16 @@
744744
"desc": ""
745745
}
746746
},
747+
{
748+
"pk": "bluesheets",
749+
"model": "name.doctypename",
750+
"fields": {
751+
"order": 0,
752+
"used": true,
753+
"name": "Bluesheets",
754+
"desc": ""
755+
}
756+
},
747757
{
748758
"pk": "uploaded",
749759
"model": "name.draftsubmissionstatename",
@@ -1905,6 +1915,13 @@
19051915
"label": "Recording State"
19061916
}
19071917
},
1918+
{
1919+
"pk": "bluesheets",
1920+
"model": "doc.statetype",
1921+
"fields": {
1922+
"label": "Bluesheet State"
1923+
}
1924+
},
19081925
{
19091926
"pk": 81,
19101927
"model": "doc.state",
@@ -3800,6 +3817,32 @@
38003817
"desc": "The RFC status changes have been abandoned"
38013818
}
38023819
},
3820+
{
3821+
"pk": 137,
3822+
"model": "doc.state",
3823+
"fields": {
3824+
"used": true,
3825+
"name": "Active",
3826+
"next_states": [],
3827+
"slug": "active",
3828+
"type": "bluesheets",
3829+
"order": 0,
3830+
"desc": ""
3831+
}
3832+
},
3833+
{
3834+
"pk": 138,
3835+
"model": "doc.state",
3836+
"fields": {
3837+
"used": true,
3838+
"name": "Deleted",
3839+
"next_states": [],
3840+
"slug": "deleted",
3841+
"type": "bluesheets",
3842+
"order": 1,
3843+
"desc": ""
3844+
}
3845+
},
38033846
{
38043847
"pk": 5,
38053848
"model": "doc.ballottype",

ietf/name/migrations/0030_add_doctype.py

Lines changed: 471 additions & 0 deletions
Large diffs are not rendered by default.

ietf/secr/proceedings/forms.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
VALID_SLIDE_EXTENSIONS = ('.doc','.docx','.pdf','.ppt','.pptx','.txt','.zip')
1818
VALID_MINUTES_EXTENSIONS = ('.txt','.html','.htm','.pdf')
1919
VALID_AGENDA_EXTENSIONS = ('.txt','.html','.htm')
20+
VALID_BLUESHEET_EXTENSIONS = ('.pdf','.jpg','.jpeg')
2021

2122
#----------------------------------------------------------
2223
# Forms
@@ -100,7 +101,7 @@ def clean_file(self):
100101
class UnifiedUploadForm(forms.Form):
101102
acronym = forms.CharField(widget=forms.HiddenInput())
102103
meeting_id = forms.CharField(widget=forms.HiddenInput())
103-
material_type = forms.ModelChoiceField(queryset=DocTypeName.objects.filter(name__in=('minutes','agenda','slides')),empty_label=None)
104+
material_type = forms.ModelChoiceField(queryset=DocTypeName.objects.filter(slug__in=('minutes','agenda','slides','bluesheets')),empty_label=None)
104105
slide_name = forms.CharField(label='Name of Presentation',max_length=255,required=False,help_text="For presentations only")
105106
file = forms.FileField(label='Select File',help_text='<div id="id_file_help">Note 1: You can only upload a presentation file in txt, pdf, doc, or ppt/pptx. System will not accept presentation files in any other format.<br><br>Note 2: All uploaded files will be available to the public immediately on the Preliminary Page. However, for the Proceedings, ppt/pptx files will be converted to html format and doc files will be converted to pdf format manually by the Secretariat staff.</div>')
106107

@@ -128,7 +129,7 @@ def clean(self):
128129
#if material_type == 1 and not file_ext[1] == '.pdf':
129130
# raise forms.ValidationError('Presentations must be a PDF file')
130131

131-
# validate file extensions based on material type (presentation,agenda,minutes)
132+
# validate file extensions based on material type (slides,agenda,minutes,bluesheets)
132133
# valid extensions per online documentation: meeting-materials.html
133134
# 09-14-11 added ppt, pdf per Alexa
134135
# 04-19-12 txt/html for agenda, +pdf for minutes per Russ
@@ -138,7 +139,9 @@ def clean(self):
138139
raise forms.ValidationError('Only these file types supported for agendas: %s' % ','.join(VALID_AGENDA_EXTENSIONS))
139140
if material_type.slug == 'minutes' and ext not in VALID_MINUTES_EXTENSIONS:
140141
raise forms.ValidationError('Only these file types supported for minutes: %s' % ','.join(VALID_MINUTES_EXTENSIONS))
141-
142+
if material_type.slug == 'bluesheets' and ext not in VALID_BLUESHEET_EXTENSIONS:
143+
raise forms.ValidationError('Only these file types supported for bluesheets: %s' % ','.join(VALID_BLUESHEET_EXTENSIONS))
144+
142145
return cleaned_data
143146

144147

ietf/secr/proceedings/proc_utils.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def create_recording(session,meeting,group,url):
8383
desc='New revision available',
8484
time=doc.time)
8585
session.materials.add(doc)
86-
86+
8787
def mycomp(timeslot):
8888
'''
8989
This takes a timeslot object and returns a key to sort by the area acronym or None
@@ -330,20 +330,24 @@ def create_proceedings(meeting, group, is_final=False):
330330
updated_list = ['RFC %s' % get_rfc_num(x.source) for x in rdocs.filter(relationship='updates')]
331331
if updated_list:
332332
rfc.msg += 'updated by ' + ','.join(updated_list)
333-
# ----------------------------------------------------------------------
334-
# check for blue sheets
335-
pattern = os.path.join(meeting_root,'bluesheets','bluesheets-%s-%s-*' % (meeting.number,group.acronym.lower()))
336-
files = glob.glob(pattern)
337-
bluesheets = []
338-
for name in files:
339-
basename = os.path.basename(name)
340-
obj = {'name': basename,
341-
'url': url_root + "bluesheets/" + basename}
342-
bluesheets.append(obj)
343-
bluesheets = sorted(bluesheets, key = lambda x: x['name'])
344-
# ----------------------------------------------------------------------
345333
else:
346-
drafts = rfcs = bluesheets = None
334+
drafts = rfcs = None
335+
336+
# ----------------------------------------------------------------------
337+
# check for blue sheets
338+
if meeting.number.startswith('interim'):
339+
pattern = os.path.join(meeting_root,'bluesheets','bluesheets-%s*' % (meeting.number))
340+
else:
341+
pattern = os.path.join(meeting_root,'bluesheets','bluesheets-%s-%s-*' % (meeting.number,group.acronym.lower()))
342+
files = glob.glob(pattern)
343+
bluesheets = []
344+
for name in files:
345+
basename = os.path.basename(name)
346+
obj = {'name': basename,
347+
'url': url_root + "bluesheets/" + basename}
348+
bluesheets.append(obj)
349+
bluesheets = sorted(bluesheets, key = lambda x: x['name'])
350+
347351

348352
# the simplest way to display the charter is to place it in a <pre> block
349353
# however, because this forces a fixed-width font, different than the rest of

ietf/secr/proceedings/tests.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
import debug # pyflakes:ignore
2+
import os
3+
import shutil
4+
5+
from StringIO import StringIO
26

37
from django.core.urlresolvers import reverse
8+
from django.conf import settings
49

10+
from ietf.doc.models import Document
511
from ietf.group.models import Group
612
from ietf.meeting.models import Meeting, Session
713
from ietf.meeting.test_data import make_meeting_test_data
814
from ietf.utils.test_data import make_test_data
915
from ietf.utils.test_utils import TestCase
1016

17+
from ietf.secr.utils.meeting import get_proceedings_path
18+
1119
SECR_USER='secretary'
1220

1321
class MainTestCase(TestCase):
@@ -45,4 +53,41 @@ def test_post(self):
4553
url = reverse('proceedings_recording_edit', kwargs={'meeting_num':meeting.number,'name':doc.name})
4654
response = self.client.post(url,dict(external_url=external_url),follow=True)
4755
self.assertEqual(response.status_code, 200)
48-
self.failUnless(external_url in response.content)
56+
self.failUnless(external_url in response.content)
57+
58+
class BluesheetTestCase(TestCase):
59+
def setUp(self):
60+
self.proceedings_dir = os.path.abspath("tmp-proceedings-dir")
61+
os.mkdir(self.proceedings_dir)
62+
settings.AGENDA_PATH = self.proceedings_dir
63+
64+
self.interim_listing_dir = os.path.abspath("tmp-interim-listing-dir")
65+
os.mkdir(self.interim_listing_dir)
66+
settings.SECR_INTERIM_LISTING_DIR = self.interim_listing_dir
67+
68+
def tearDown(self):
69+
shutil.rmtree(self.proceedings_dir)
70+
shutil.rmtree(self.interim_listing_dir)
71+
72+
def test_upload(self):
73+
make_test_data()
74+
meeting = Meeting.objects.filter(type='interim').first()
75+
group = Group.objects.get(acronym='mars')
76+
Session.objects.create(meeting=meeting,group=group,requested_by_id=1,status_id='sched')
77+
url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting.number,'acronym':'mars'})
78+
upfile = StringIO('dummy file')
79+
upfile.name = "scan1.pdf"
80+
self.client.login(username="marschairman", password="marschairman+password")
81+
r = self.client.post(url,
82+
dict(acronym='mars',meeting_id=meeting.id,material_type='bluesheets',file=upfile),follow=True)
83+
self.assertEqual(r.status_code, 200)
84+
doc = Document.objects.get(type='bluesheets')
85+
self.failUnless(doc.external_url in r.content)
86+
self.failUnless(os.path.exists(os.path.join(doc.get_file_path(),doc.external_url)))
87+
# test that proceedings has bluesheets on it
88+
path = get_proceedings_path(meeting,group)
89+
self.failUnless(os.path.exists(path))
90+
with open(path) as f:
91+
data = f.read()
92+
self.failUnless(doc.external_url in data)
93+

ietf/secr/proceedings/views.py

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,16 @@
2121
from ietf.secr.utils.decorators import check_permissions, sec_only
2222
from ietf.secr.utils.document import get_full_path
2323
from ietf.secr.utils.group import get_my_groups, groups_by_session
24-
from ietf.secr.utils.meeting import get_upload_root, get_materials, get_timeslot
24+
from ietf.secr.utils.meeting import get_upload_root, get_materials, get_timeslot, get_proceedings_path, get_proceedings_url
2525
from ietf.doc.models import Document, DocAlias, DocEvent, State, NewRevisionDocEvent
2626
from ietf.group.models import Group
2727
from ietf.ietfauth.utils import has_role, role_required
2828
from ietf.meeting.models import Meeting, Session, TimeSlot, ScheduledSession
2929
from ietf.secr.proceedings.forms import EditSlideForm, InterimMeetingForm, RecordingForm, RecordingEditForm, ReplaceSlideForm, UnifiedUploadForm
30-
from ietf.secr.proceedings.proc_utils import ( gen_acknowledgement, gen_agenda, gen_areas, gen_attendees,
31-
gen_group_pages, gen_index, gen_irtf, gen_overview, gen_plenaries, gen_progress, gen_research,
32-
gen_training, create_proceedings, create_interim_directory, create_recording )
30+
from ietf.secr.proceedings.proc_utils import ( gen_acknowledgement, gen_agenda, gen_areas,
31+
gen_attendees, gen_group_pages, gen_index, gen_irtf, gen_overview, gen_plenaries,
32+
gen_progress, gen_research, gen_training, create_proceedings, create_interim_directory,
33+
create_recording )
3334

3435
from ietf.secr.proceedings.models import InterimMeeting # proxy model
3536

@@ -137,27 +138,6 @@ def get_next_order_num(session):
137138

138139
return max_order + 1 if max_order else 1
139140

140-
# --- These could be properties/methods on meeting
141-
def get_proceedings_path(meeting,group):
142-
if meeting.type_id == 'ietf':
143-
path = os.path.join(get_upload_root(meeting),group.acronym + '.html')
144-
elif meeting.type_id == 'interim':
145-
path = os.path.join(get_upload_root(meeting),'proceedings.html')
146-
return path
147-
148-
def get_proceedings_url(meeting,group=None):
149-
if meeting.type_id == 'ietf':
150-
url = "%sproceedings/%s/" % (settings.MEDIA_URL,meeting.number)
151-
if group:
152-
url = url + "%s.html" % group.acronym
153-
154-
elif meeting.type_id == 'interim':
155-
url = "%sproceedings/interim/%s/%s/proceedings.html" % (
156-
settings.MEDIA_URL,
157-
meeting.date.strftime('%Y/%m/%d'),
158-
group.acronym)
159-
return url
160-
161141
def handle_upload_file(file,filename,meeting,subdir):
162142
'''
163143
This function takes a file object, a filename and a meeting object and subdir as string.
@@ -173,6 +153,8 @@ def handle_upload_file(file,filename,meeting,subdir):
173153
os.mkdir(path)
174154
else:
175155
path = os.path.join(get_upload_root(meeting),subdir)
156+
if not os.path.exists(path):
157+
os.makedirs(path)
176158

177159
# agendas and minutes can only have one file instance so delete file if it already exists
178160
if subdir in ('agenda','minutes'):
@@ -324,7 +306,7 @@ def build(request,meeting_num,acronym):
324306
meeting = Meeting.objects.get(number=meeting_num)
325307
group = get_object_or_404(Group,acronym=acronym)
326308

327-
create_proceedings(meeting,group)
309+
create_proceedings(meeting,group,is_final=True)
328310

329311
messages.success(request,'proceedings.html was rebuilt')
330312
url = reverse('proceedings_upload_unified', kwargs={'meeting_num':meeting_num,'acronym':acronym})
@@ -877,8 +859,8 @@ def redirection_back(meeting, group):
877859
session = sessions[0]
878860
session_name = ''
879861
elif session_id:
880-
sessions = None
881862
session = get_object_or_404(Session, id=int(session_id))
863+
sessions = [session]
882864
group = session.group
883865
session_name = session.name
884866

@@ -945,11 +927,13 @@ def redirection_back(meeting, group):
945927

946928
# create session relationship, per Henrik we should associate documents to all sessions
947929
# for the current meeting (until tools support different materials for diff sessions)
948-
if sessions:
949-
for s in sessions:
950-
s.materials.add(doc)
951-
else:
952-
session.materials.add(doc)
930+
for s in sessions:
931+
try:
932+
sp = s.sessionpresentation_set.get(document=doc)
933+
sp.rev = doc.rev
934+
sp.save()
935+
except ObjectDoesNotExist:
936+
s.sessionpresentation_set.create(document=doc,rev=doc.rev)
953937

954938
# create NewRevisionDocEvent instead of uploaded, per Ole
955939
NewRevisionDocEvent.objects.create(type='new_revision',

ietf/secr/templates/proceedings/proceedings.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ <h3>Technical Advisor(s)</h3>
8585
<br /><br /></td></tr></table>
8686

8787
<h3>Recordings:</h3>
88-
{% if materials.record %}
88+
{% if materials.recording %}
8989
<ul>
90-
{% for record in materials.record %}
91-
<li><a href="{{ record.href }}" target="_blank">{{ record.title }}</a></li>
90+
{% for recording in materials.recording %}
91+
<li><a href="{{ recording.href }}" target="_blank">{{ recording.title }}</a></li>
9292
{% endfor %}
9393
</ul>
9494
{% else %}

ietf/secr/templates/proceedings/upload_unified.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{% extends "base_site.html" %}
2+
{% load ietf_filters %}
23

34
{% block title %}Proceedings{% endblock %}
45

@@ -71,7 +72,16 @@ <h2>{{ meeting }} - Upload Material - {% if session_name %}{{ session_name }}{%
7172
<td>(not uploaded)</td>
7273
<td></td>
7374
{% endif %}
75+
</tr>
76+
{% if materials.bluesheets %}
77+
{% for item in materials.bluesheets %}
78+
<tr>
79+
<td><a href="{{ item.get_absolute_url }}" target="_blank">Bluesheet</a></td>
80+
<td>{{ item.external_url }}</td>
81+
<td>{% if user|has_role:"Secretariat" %}<a href="{% url "proceedings_delete_material" slide_id=item.name %}">Delete</a>{% endif %}</td>
7482
</tr>
83+
{% endfor %}
84+
{% endif %}
7585
</tbody>
7686
</table>
7787

0 commit comments

Comments
 (0)