Skip to content

Commit d5f3691

Browse files
committed
Added support of xml-source only drafts submissions.
- Legacy-Id: 9858
1 parent 77b20a9 commit d5f3691

17 files changed

Lines changed: 404 additions & 196 deletions

ietf/doc/views_doc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ def document_main(request, name, rev=None):
478478
content = get_document_content(filename, pathname, split=False, markup=True)
479479

480480
ballot_summary = None
481-
if doc.get_state_slug() in ("iesgeval"):
481+
if doc.get_state_slug() in ("iesgeval") and doc.active_ballot():
482482
ballot_summary = needed_ballot_positions(doc, doc.active_ballot().active_ad_positions().values())
483483

484484
table_rows = dict(doc=4, wg=2, iesg=3)

ietf/settings.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -428,18 +428,23 @@ def skip_unreadable_post(record):
428428
IDSUBMIT_DEFAULT_CUTOFF_TIME_UTC = datetime.timedelta(hours=23, minutes=59, seconds=59)
429429
IDSUBMIT_DEFAULT_CUTOFF_WARNING_DAYS = datetime.timedelta(days=21)
430430

431-
MEETING_MATERIALS_SUBMISSION_START_DAYS = -90
432-
MEETING_MATERIALS_SUBMISSION_CUTOFF_DAYS = 26
433-
MEETING_MATERIALS_SUBMISSION_CORRECTION_DAYS = 50
434-
435-
INTERNET_DRAFT_DAYS_TO_EXPIRE = 185
436-
437431
IDSUBMIT_REPOSITORY_PATH = INTERNET_DRAFT_PATH
438432
IDSUBMIT_STAGING_PATH = '/a/www/www6s/staging/'
439433
IDSUBMIT_STAGING_URL = '//www.ietf.org/staging/'
440434
IDSUBMIT_IDNITS_BINARY = '/a/www/ietf-datatracker/scripts/idnits'
441435

442-
IDSUBMIT_MAX_PLAIN_DRAFT_SIZE = 6291456 # Max size of the txt draft in bytes
436+
IDSUBMIT_FILE_TYPES = (
437+
'txt',
438+
'xml',
439+
'pdf',
440+
'ps',
441+
)
442+
IDSUBMIT_MAX_DRAFT_SIZE = {
443+
'txt': 6*1024*1024, # Max size of txt draft file in bytes
444+
'xml': 10*1024*1024, # Max size of xml draft file in bytes
445+
'pdf': 10*1024*1024,
446+
'ps' : 10*1024*1024,
447+
}
443448

444449
IDSUBMIT_MAX_DAILY_SAME_DRAFT_NAME = 20
445450
IDSUBMIT_MAX_DAILY_SAME_DRAFT_NAME_SIZE = 50 # in MB
@@ -450,6 +455,14 @@ def skip_unreadable_post(record):
450455
IDSUBMIT_MAX_DAILY_SUBMISSIONS = 1000
451456
IDSUBMIT_MAX_DAILY_SUBMISSIONS_SIZE = 2000 # in MB
452457

458+
XML_LIBRARY = "/www/tools.ietf.org/tools/xml2rfc/web/public/rfc/"
459+
460+
MEETING_MATERIALS_SUBMISSION_START_DAYS = -90
461+
MEETING_MATERIALS_SUBMISSION_CUTOFF_DAYS = 26
462+
MEETING_MATERIALS_SUBMISSION_CORRECTION_DAYS = 50
463+
464+
INTERNET_DRAFT_DAYS_TO_EXPIRE = 185
465+
453466
DOT_BINARY = '/usr/bin/dot'
454467
UNFLATTEN_BINARY= '/usr/bin/unflatten'
455468
PS2PDF_BINARY = '/usr/bin/ps2pdf'

ietf/submit/forms.py

Lines changed: 88 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import os
2+
import re
23
import datetime
34
import pytz
5+
import xml2rfc
6+
import tempfile
47

58
from django import forms
69
from django.conf import settings
@@ -21,14 +24,14 @@
2124
from ietf.utils.draft import Draft
2225

2326

24-
class UploadForm(forms.Form):
25-
txt = forms.FileField(label=u'.txt format', required=True)
27+
class SubmissionUploadForm(forms.Form):
28+
txt = forms.FileField(label=u'.txt format', required=False)
2629
xml = forms.FileField(label=u'.xml format', required=False)
2730
pdf = forms.FileField(label=u'.pdf format', required=False)
28-
ps = forms.FileField(label=u'.ps format', required=False)
31+
ps = forms.FileField(label=u'.ps format', required=False)
2932

3033
def __init__(self, request, *args, **kwargs):
31-
super(UploadForm, self).__init__(*args, **kwargs)
34+
super(SubmissionUploadForm, self).__init__(*args, **kwargs)
3235

3336
self.remote_ip = request.META.get('REMOTE_ADDR', None)
3437

@@ -38,7 +41,13 @@ def __init__(self, request, *args, **kwargs):
3841
self.set_cutoff_warnings()
3942

4043
self.group = None
44+
self.filename = None
45+
self.revision = None
46+
self.title = None
47+
self.abstract = None
48+
self.authors = []
4149
self.parsed_draft = None
50+
self.file_types = []
4251

4352
def set_cutoff_warnings(self):
4453
now = datetime.datetime.now(pytz.utc)
@@ -90,15 +99,14 @@ def clean_file(self, field_name, parser_class):
9099

91100
return f
92101

93-
94102
def clean_txt(self):
95103
return self.clean_file("txt", PlainParser)
96104

97105
def clean_pdf(self):
98106
return self.clean_file("pdf", PDFParser)
99107

100108
def clean_ps(self):
101-
return self.clean_file("ps", PSParser)
109+
return self.clean_file("ps", PSParser)
102110

103111
def clean_xml(self):
104112
return self.clean_file("xml", XMLParser)
@@ -113,37 +121,100 @@ def clean(self):
113121
if not os.path.exists(getattr(settings, s)):
114122
raise forms.ValidationError('%s defined in settings.py does not exist' % s)
115123

124+
for ext in ['txt', 'pdf', 'xml', 'ps']:
125+
f = self.cleaned_data.get(ext, None)
126+
if not f:
127+
continue
128+
self.file_types.append('.%s' % ext)
129+
if not ('.txt' in self.file_types or '.xml' in self.file_types):
130+
raise forms.ValidationError('You must submit either a .txt or an .xml file; didn\'t find either.')
131+
132+
#debug.show('self.cleaned_data["xml"]')
133+
if self.cleaned_data.get('xml'):
134+
#if not self.cleaned_data.get('txt'):
135+
xml_file = self.cleaned_data.get('xml')
136+
tfh, tfn = tempfile.mkstemp(suffix='.xml')
137+
try:
138+
# We need to write the xml file to disk in order to hand it
139+
# over to the xml parser. XXX FIXME: investigate updating
140+
# xml2rfc to be able to work with file handles to in-memory
141+
# files.
142+
with open(tfn, 'wb+') as tf:
143+
for chunk in xml_file.chunks():
144+
tf.write(chunk)
145+
os.environ["XML_LIBRARY"] = settings.XML_LIBRARY
146+
parser = xml2rfc.XmlRfcParser(tfn, quiet=True)
147+
self.xmltree = parser.parse()
148+
ok, errors = self.xmltree.validate()
149+
if not ok:
150+
raise forms.ValidationError(errors)
151+
self.xmlroot = self.xmltree.getroot()
152+
draftname = self.xmlroot.attrib.get('docName')
153+
revmatch = re.search("-[0-9][0-9]$", draftname)
154+
if revmatch:
155+
self.revision = draftname[-2:]
156+
self.filename = draftname[:-3]
157+
else:
158+
self.revision = None
159+
self.filename = draftname
160+
self.title = self.xmlroot.find('front/title').text
161+
self.abstract = self.xmlroot.find('front/abstract').text
162+
self.author_list = []
163+
author_info = self.xmlroot.findall('front/author')
164+
for author in author_info:
165+
author_dict = dict(
166+
company = author.find('organization').text,
167+
last_name = author.attrib.get('surname'),
168+
full_name = author.attrib.get('fullname'),
169+
email = author.find('address/email').text,
170+
)
171+
self.author_list.append(author_dict)
172+
line = "%(full_name)s <%(email)s>" % author_dict
173+
self.authors.append(line)
174+
except Exception as e:
175+
raise forms.ValidationError("Exception: %s" % e)
176+
finally:
177+
os.close(tfh)
178+
os.unlink(tfn)
179+
116180
if self.cleaned_data.get('txt'):
117181
# try to parse it
118182
txt_file = self.cleaned_data['txt']
119183
txt_file.seek(0)
120184
self.parsed_draft = Draft(txt_file.read(), txt_file.name)
185+
self.filename = self.parsed_draft.filename
186+
self.revision = self.parsed_draft.revision
187+
self.title = self.parsed_draft.get_title()
121188
txt_file.seek(0)
122189

123-
if not self.parsed_draft.filename:
124-
raise forms.ValidationError("Draft parser could not extract a valid draft name from the .txt file")
190+
if not self.filename:
191+
raise forms.ValidationError("Draft parser could not extract a valid draft name from the upload")
192+
193+
if not self.revision:
194+
raise forms.ValidationError("Draft parser could not extract a valid draft revision from the upload")
125195

126-
if not self.parsed_draft.get_title():
127-
raise forms.ValidationError("Draft parser could not extract a valid title from the .txt file")
196+
if not self.title:
197+
raise forms.ValidationError("Draft parser could not extract a valid title from the upload")
128198

199+
if self.cleaned_data.get('txt') or self.cleaned_data.get('xml'):
129200
# check group
130201
self.group = self.deduce_group()
131202

132203
# check existing
133-
existing = Submission.objects.filter(name=self.parsed_draft.filename, rev=self.parsed_draft.revision).exclude(state__in=("posted", "cancel"))
204+
existing = Submission.objects.filter(name=self.filename, rev=self.revision).exclude(state__in=("posted", "cancel"))
134205
if existing:
135206
raise forms.ValidationError(mark_safe('Submission with same name and revision is currently being processed. <a href="%s">Check the status here.</a>' % urlreverse("submit_submission_status", kwargs={ 'submission_id': existing[0].pk })))
136207

137208
# cut-off
138-
if self.parsed_draft.revision == '00' and self.in_first_cut_off:
209+
if self.revision == '00' and self.in_first_cut_off:
139210
raise forms.ValidationError(mark_safe(self.cutoff_warning))
140211

141212
# check thresholds
142213
today = datetime.date.today()
143214

144215
self.check_submissions_tresholds(
145-
"for the draft %s" % self.parsed_draft.filename,
146-
dict(name=self.parsed_draft.filename, rev=self.parsed_draft.revision, submission_date=today),
216+
"for the draft %s" % self.filename,
217+
dict(name=self.filename, rev=self.revision, submission_date=today),
147218
settings.IDSUBMIT_MAX_DAILY_SAME_DRAFT_NAME, settings.IDSUBMIT_MAX_DAILY_SAME_DRAFT_NAME_SIZE,
148219
)
149220
self.check_submissions_tresholds(
@@ -163,19 +234,19 @@ def clean(self):
163234
settings.IDSUBMIT_MAX_DAILY_SUBMISSIONS, settings.IDSUBMIT_MAX_DAILY_SUBMISSIONS_SIZE,
164235
)
165236

166-
return super(UploadForm, self).clean()
237+
return super(SubmissionUploadForm, self).clean()
167238

168239
def check_submissions_tresholds(self, which, filter_kwargs, max_amount, max_size):
169240
submissions = Submission.objects.filter(**filter_kwargs)
170241

171242
if len(submissions) > max_amount:
172243
raise forms.ValidationError("Max submissions %s has been reached for today (maximum is %s submissions)." % (which, max_amount))
173-
if sum(s.file_size for s in submissions) > max_size * 1024 * 1024:
244+
if sum(s.file_size for s in submissions if s.file_size) > max_size * 1024 * 1024:
174245
raise forms.ValidationError("Max uploaded amount %s has been reached for today (maximum is %s MB)." % (which, max_size))
175246

176247
def deduce_group(self):
177248
"""Figure out group from name or previously submitted draft, returns None if individual."""
178-
name = self.parsed_draft.filename
249+
name = self.filename
179250
existing_draft = Document.objects.filter(name=name, type="draft")
180251
if existing_draft:
181252
group = existing_draft[0].group

ietf/submit/parsers/base.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1+
import os
12
import re
3+
import magic
4+
import datetime
5+
import debug # pyflakes:ignore
6+
7+
from django.conf import settings
8+
from django.template.defaultfilters import filesizeformat
29

310
class MetaData(object):
411
rev = None
@@ -42,6 +49,8 @@ def __init__(self, fd):
4249
# no other file parsing is recommended
4350
def critical_parse(self):
4451
self.parse_invalid_chars_in_filename()
52+
self.parse_max_size();
53+
self.parsed_info.metadata.submission_date = datetime.date.today()
4554
return self.parsed_info
4655

4756
def parse_invalid_chars_in_filename(self):
@@ -50,3 +59,22 @@ def parse_invalid_chars_in_filename(self):
5059
chars = regexp.findall(name)
5160
if chars:
5261
self.parsed_info.add_error('Invalid characters were found in the name of the file which was just submitted: %s' % ', '.join(set(chars)))
62+
63+
def parse_max_size(self):
64+
__, ext = os.path.splitext(self.fd.name)
65+
ext = ext.lstrip('.')
66+
max_size = settings.IDSUBMIT_MAX_DRAFT_SIZE[ext]
67+
if self.fd.size > max_size:
68+
self.parsed_info.add_error('File size is larger than the permitted maximum of %s' % filesizeformat(max_size))
69+
self.parsed_info.metadata.file_size = self.fd.size
70+
71+
def parse_filename_extension(self, ext):
72+
if not self.fd.name.lower().endswith('.'+ext):
73+
self.parsed_info.add_error('Expected the %s file to have extension ".%s", found "%s"' % (ext.upper(), ext, self.fd.name))
74+
75+
def parse_file_type(self, ext, expected):
76+
self.fd.file.seek(0)
77+
content = self.fd.file.read(4096)
78+
mimetype = magic.from_buffer(content, mime=True)
79+
if not mimetype == expected:
80+
self.parsed_info.add_error('Expected an %s file of type "%s", found one of type "%s"' % (expected, mimetype))

ietf/submit/parsers/pdf_parser.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ class PDFParser(FileParser):
77
# no other file parsing is recommended
88
def critical_parse(self):
99
super(PDFParser, self).critical_parse()
10-
self.parse_filename_extension()
10+
self.parse_filename_extension('pdf')
11+
self.parse_file_type('pdf', 'application/pdf')
1112
return self.parsed_info
12-
13-
def parse_filename_extension(self):
14-
if not self.fd.name.endswith('.pdf'):
15-
self.parsed_info.add_error('Format of this document must be PDF')

ietf/submit/parsers/plain_parser.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
import datetime
21
import re
32

4-
from django.conf import settings
5-
from django.template.defaultfilters import filesizeformat
63
from ietf.submit.parsers.base import FileParser
74

85

@@ -15,17 +12,12 @@ def __init__(self, fd):
1512
# no other file parsing is recommended
1613
def critical_parse(self):
1714
super(PlainParser, self).critical_parse()
18-
self.parse_max_size()
15+
self.parse_filename_extension('txt')
16+
self.parse_file_type('txt', 'text/plain')
1917
self.parse_file_charset()
2018
self.parse_name()
2119
return self.parsed_info
2220

23-
def parse_max_size(self):
24-
if self.fd.size > settings.IDSUBMIT_MAX_PLAIN_DRAFT_SIZE:
25-
self.parsed_info.add_error('File size is larger than %s' % filesizeformat(settings.IDSUBMIT_MAX_PLAIN_DRAFT_SIZE))
26-
self.parsed_info.metadata.file_size = self.fd.size
27-
self.parsed_info.metadata.submission_date = datetime.date.today()
28-
2921
def parse_file_charset(self):
3022
import magic
3123
self.fd.file.seek(0)

ietf/submit/parsers/ps_parser.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ class PSParser(FileParser):
77
# no other file parsing is recommended
88
def critical_parse(self):
99
super(PSParser, self).critical_parse()
10-
self.parse_filename_extension()
10+
self.parse_filename_extension('ps')
11+
self.parse_file_type('ps', 'application/postscript')
1112
return self.parsed_info
12-
13-
def parse_filename_extension(self):
14-
if not self.fd.name.endswith('.ps'):
15-
self.parsed_info.add_error('Format of this document must be PS')

ietf/submit/parsers/xml_parser.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ class XMLParser(FileParser):
77
# no other file parsing is recommended
88
def critical_parse(self):
99
super(XMLParser, self).critical_parse()
10-
self.parse_filename_extension()
10+
self.parse_filename_extension('xml')
11+
self.parse_file_type('xml', 'application/xml')
1112
return self.parsed_info
12-
13-
def parse_filename_extension(self):
14-
if not self.fd.name.endswith('.xml'):
15-
self.parsed_info.add_error('Format of this document must be XML')
13+

ietf/submit/test_submission.pdf

22 Bytes
Binary file not shown.

ietf/submit/test_submission.ps

35 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)