forked from adamlaska/datatracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathviews_proceedings.py
More file actions
314 lines (273 loc) · 11.3 KB
/
views_proceedings.py
File metadata and controls
314 lines (273 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# Copyright The IETF Trust 2021 All Rights Reserved
from pathlib import Path
from django import forms
from django.http import Http404, FileResponse, HttpResponseBadRequest
from django.shortcuts import render, redirect, get_object_or_404
import debug # pyflakes:ignore
from ietf.doc.utils import add_state_change_event
from ietf.doc.models import DocAlias, DocEvent, Document, NewRevisionDocEvent, State
from ietf.ietfauth.utils import role_required
from ietf.meeting.forms import FileUploadForm
from ietf.meeting.models import Meeting, MeetingHost
from ietf.meeting.helpers import get_meeting
from ietf.name.models import ProceedingsMaterialTypeName
from ietf.meeting.utils import handle_upload_file
from ietf.utils.text import xslugify
class UploadProceedingsMaterialForm(FileUploadForm):
doc_type = 'procmaterials'
use_url = forms.BooleanField(
required=False,
label='Use an external URL instead of uploading a document',
)
external_url = forms.URLField(
required=False,
help_text='External URL to link from the proceedings'
)
field_order = ['use_url', 'external_url'] # will precede superclass fields
class Media:
js = (
'ietf/js/upload-material.js',
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['file'].label = 'Select a file to upload. Allowed format{}: {}'.format(
'' if len(self.mime_types) == 1 else 's',
', '.join(self.mime_types),
)
self.fields['file'].required = False
def clean_file(self):
if self.cleaned_data.get('file', None) is None:
return None # bypass cleaning the file if none was provided
return super().clean_file()
def clean(self):
if self.cleaned_data['use_url']:
if not self.cleaned_data['external_url']:
self.add_error('external_url', 'This field is required')
else:
self.cleaned_data['external_url'] = None # make sure this is empty
if self.cleaned_data['file'] is None:
self.add_error('file', 'This field is required')
class EditProceedingsMaterialForm(forms.Form):
"""Form to edit proceedings material properties"""
# A note: we use Document._meta to get the max length of a model field.
# The leading underscore makes this look like accessing a private member,
# but it is in fact part of Django's API.
# noinspection PyProtectedMember
title = forms.CharField(
help_text='Label that will appear on the proceedings page',
max_length=Document._meta.get_field("title").max_length,
required=True,
)
def save_proceedings_material_doc(meeting, material_type, title, request, file=None, external_url=None, state=None):
events = []
by = request.user.person
if not (file is None or external_url is None):
raise ValueError('One of file or external_url must be None')
# doc naming duplicates naming of docs elsewhere - use dashes instead of underscores
doc_name = '-'.join([
'proceedings',
meeting.number,
xslugify(
getattr(material_type, 'slug', material_type)
).replace('_', '-')[:128],
])
created = False
doc = Document.objects.filter(type_id='procmaterials', name=doc_name).first()
if doc is None:
if file is None and external_url is None:
raise ValueError('Cannot create a new document without a file or external URL')
doc = Document.objects.create(
type_id='procmaterials',
name=doc_name,
rev="00",
)
created = True
# do this even if we did not create the document, just to be sure the alias exists
alias, _ = DocAlias.objects.get_or_create(name=doc.name)
alias.docs.add(doc)
if file:
if not created:
doc.rev = '{:02}'.format(int(doc.rev) + 1)
filename = f'{doc.name}-{doc.rev}{Path(file.name).suffix}'
save_error = handle_upload_file(file, filename, meeting, 'procmaterials', )
if save_error is not None:
raise RuntimeError(save_error)
doc.uploaded_filename = filename
doc.external_url = ''
e = NewRevisionDocEvent.objects.create(
type="new_revision",
doc=doc,
rev=doc.rev,
by=by,
desc="New version available: <b>%s-%s</b>" % (doc.name, doc.rev),
)
events.append(e)
elif (external_url is not None) and external_url != doc.external_url:
if not created:
doc.rev = '{:02}'.format(int(doc.rev) + 1)
doc.uploaded_filename = ''
doc.external_url = external_url
e = NewRevisionDocEvent.objects.create(
type="new_revision",
doc=doc,
rev=doc.rev,
by=by,
desc="Set external URL to <b>{}</b>".format(external_url),
)
events.append(e)
if doc.title != title and title is not None:
e = DocEvent(doc=doc, rev=doc.rev, by=by, type='changed_document')
e.desc = f'Changed title to <b>{title}</b>'
if doc.title:
e.desc += f' from {doc.title}'
e.save()
events.append(e)
doc.title = title
# Set the state and create a change event if necessary
prev_state = doc.get_state('procmaterials')
new_state = state if state is not None else State.objects.get(type_id='procmaterials', slug='active')
if prev_state != new_state:
if not created:
e = add_state_change_event(doc, by, prev_state, new_state)
events.append(e)
doc.set_state(new_state)
if events:
doc.save_with_history(events)
return doc
@role_required('Secretariat')
def upload_material(request, num, material_type):
meeting = get_meeting(num)
# turn the material_type slug into the actual instance
material_type = get_object_or_404(ProceedingsMaterialTypeName, slug=material_type)
material = meeting.proceedings_materials.filter(type=material_type).first()
if request.method == 'POST':
form = UploadProceedingsMaterialForm(request.POST, request.FILES)
if form.is_valid():
doc = save_proceedings_material_doc(
meeting,
material_type,
request=request,
file=form.cleaned_data.get('file', None),
external_url=form.cleaned_data.get('external_url', None),
title=str(material if material is not None else material_type),
)
if material is None:
meeting.proceedings_materials.create(type=material_type, document=doc)
return redirect('ietf.meeting.views_proceedings.material_details', num=num)
else:
initial = dict()
if material is not None:
ext_url = material.document.external_url
if ext_url != '':
initial['use_url'] = True
initial['external_url'] = ext_url
form = UploadProceedingsMaterialForm(initial=initial)
return render(request, 'meeting/proceedings/upload_material.html', {
'form': form,
'material': material,
'material_type': material_type,
'meeting': meeting,
'submit_button_label': 'Upload',
})
@role_required('Secretariat')
def material_details(request, num):
meeting = get_meeting(num)
proceedings_materials = [
(type_name, meeting.proceedings_materials.filter(type=type_name).first())
for type_name in ProceedingsMaterialTypeName.objects.all()
]
return render(
request,
'meeting/proceedings/material_details.html',
dict(
meeting=meeting,
proceedings_materials=proceedings_materials,
)
)
@role_required('Secretariat')
def edit_material(request, num, material_type):
meeting = get_meeting(num)
material = meeting.proceedings_materials.filter(type_id=material_type).first()
if material is None:
raise Http404('No such material for this meeting')
if request.method == 'POST':
form = EditProceedingsMaterialForm(request.POST, request.FILES)
if form.is_valid():
save_proceedings_material_doc(
meeting,
material_type,
request=request,
title=form.cleaned_data['title'],
)
return redirect("ietf.meeting.views_proceedings.material_details", num=meeting.number)
else:
form = EditProceedingsMaterialForm(
initial=dict(
title=material.document.title,
),
)
return render(request, 'meeting/proceedings/edit_material.html', {
'form': form,
'material': material,
'material_type': material.type,
'meeting': meeting,
})
@role_required('Secretariat')
def remove_restore_material(request, num, material_type, action):
"""Remove or restore proceedings material"""
if action not in ['remove', 'restore']:
return HttpResponseBadRequest('Unsupported action')
meeting = get_meeting(num)
material = meeting.proceedings_materials.filter(type_id=material_type).first()
if material is None:
raise Http404('No such material for this meeting')
if request.method == 'POST':
prev_state = material.document.get_state('procmaterials')
new_state = State.objects.get(
type_id='procmaterials',
slug='active' if action == 'restore' else 'removed',
)
if new_state != prev_state:
material.document.set_state(new_state)
add_state_change_event(material.document, request.user.person, prev_state, new_state)
return redirect('ietf.meeting.views_proceedings.material_details', num=num)
return render(
request,
'meeting/proceedings/remove_restore_material.html',
dict(material=material, action=action)
)
@role_required('Secretariat')
def edit_meetinghosts(request, num):
meeting = get_meeting(num)
MeetingHostFormSet = forms.inlineformset_factory(
Meeting,
MeetingHost,
fields=('name', 'logo',),
extra=2,
)
if request.method == 'POST':
formset = MeetingHostFormSet(request.POST, request.FILES, instance=meeting)
if formset.is_valid():
# If we are removing a MeetingHost or replacing its logo, delete the
# old logo file.
for form in formset:
if form.instance.pk:
deleted = form.cleaned_data.get('DELETE', False)
logo_replaced = 'logo' in form.changed_data
if deleted or logo_replaced:
orig_instance = meeting.meetinghosts.get(pk=form.instance.pk)
orig_instance.logo.delete()
# this will update the DB and add any newly uploaded files
formset.save()
return redirect('ietf.meeting.views.materials', num=meeting.number)
else:
formset = MeetingHostFormSet(instance=meeting)
return render(request, 'meeting/proceedings/edit_meetinghosts.html', {
'formset': formset,
'meeting': meeting,
})
def meetinghost_logo(request, num, host_id):
host = get_object_or_404(MeetingHost, pk=host_id)
if host.meeting.number != num:
raise Http404()
return FileResponse(host.logo.open())