Added an API for draft submission, at /api/submit. Added an urls.py file under api/ to hold api urls, and moved those from ietf/urls.py. Refactored out many parts of the regular submission forms and functions in submit/forms.py and submit/views.py in order to re-use the appropriate parts for the submission API. Moved support functions to submit/utils.py. Added a new validation errors for missing docName in xml-based submissions. Updated the submission test document templates to use insert additional values. Added failure and success test cases for automated API submissions, and refactored some test utility functions.
- Legacy-Id: 14125
This commit is contained in:
parent
da23da1e8e
commit
3af2554b2f
24
ietf/api/urls.py
Normal file
24
ietf/api/urls.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Copyright The IETF Trust 2017, All Rights Reserved
|
||||
|
||||
from django.conf.urls import include
|
||||
|
||||
from ietf import api
|
||||
from ietf.meeting import views as meeting_views
|
||||
from ietf.submit import views as submit_views
|
||||
from ietf.utils.urls import url
|
||||
|
||||
api.autodiscover()
|
||||
|
||||
urlpatterns = [
|
||||
# Top endpoint for Tastypie's REST API (this isn't standard Tastypie):
|
||||
url(r'^v1/?$', api.top_level),
|
||||
# Custom API endpoints
|
||||
url(r'^notify/meeting/import_recordings/(?P<number>[a-z0-9-]+)/?$', meeting_views.api_import_recordings),
|
||||
url(r'^submit/?$', submit_views.api_submit),
|
||||
]
|
||||
# Additional (standard) Tastypie endpoints
|
||||
for n,a in api._api_list:
|
||||
urlpatterns += [
|
||||
url(r'^v1/', include(a.urls)),
|
||||
]
|
||||
|
|
@ -31,14 +31,11 @@ from ietf.submit.parsers.ps_parser import PSParser
|
|||
from ietf.submit.parsers.xml_parser import XMLParser
|
||||
from ietf.utils.draft import Draft
|
||||
|
||||
class SubmissionUploadForm(forms.Form):
|
||||
txt = forms.FileField(label=u'.txt format', required=False)
|
||||
xml = forms.FileField(label=u'.xml format', required=False)
|
||||
pdf = forms.FileField(label=u'.pdf format', required=False)
|
||||
ps = forms.FileField(label=u'.ps format', required=False)
|
||||
class SubmissionBaseUploadForm(forms.Form):
|
||||
xml = forms.FileField(label=u'.xml format', required=True)
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(SubmissionUploadForm, self).__init__(*args, **kwargs)
|
||||
super(SubmissionBaseUploadForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.remote_ip = request.META.get('REMOTE_ADDR', None)
|
||||
|
||||
|
@ -56,6 +53,14 @@ class SubmissionUploadForm(forms.Form):
|
|||
self.authors = []
|
||||
self.parsed_draft = None
|
||||
self.file_types = []
|
||||
# No code currently (14 Sep 2017) uses this class directly; it is
|
||||
# only used through its subclasses. The two assignments below are
|
||||
# set to trigger an exception if it is used directly only to make
|
||||
# sure that adequate consideration is made if it is decided to use it
|
||||
# directly in the future. Feel free to set these appropriately to
|
||||
# avoid the exceptions in that case:
|
||||
self.formats = None # None will raise an exception in clean() if this isn't changed in a subclass
|
||||
self.base_formats = None # None will raise an exception in clean() if this isn't changed in a subclass
|
||||
|
||||
def set_cutoff_warnings(self):
|
||||
now = datetime.datetime.now(pytz.utc)
|
||||
|
@ -96,6 +101,7 @@ class SubmissionUploadForm(forms.Form):
|
|||
'The last submission time for the I-D submission was %s.<br/><br>'
|
||||
'The I-D submission tool will be reopened after %s (IETF-meeting local time).' % (cutoff_01_str, reopen_str))
|
||||
self.shutdown = True
|
||||
|
||||
def clean_file(self, field_name, parser_class):
|
||||
f = self.cleaned_data[field_name]
|
||||
if not f:
|
||||
|
@ -107,15 +113,6 @@ class SubmissionUploadForm(forms.Form):
|
|||
|
||||
return f
|
||||
|
||||
def clean_txt(self):
|
||||
return self.clean_file("txt", PlainParser)
|
||||
|
||||
def clean_pdf(self):
|
||||
return self.clean_file("pdf", PDFParser)
|
||||
|
||||
def clean_ps(self):
|
||||
return self.clean_file("ps", PSParser)
|
||||
|
||||
def clean_xml(self):
|
||||
return self.clean_file("xml", XMLParser)
|
||||
|
||||
|
@ -123,13 +120,13 @@ class SubmissionUploadForm(forms.Form):
|
|||
if self.shutdown and not has_role(self.request.user, "Secretariat"):
|
||||
raise forms.ValidationError('The submission tool is currently shut down')
|
||||
|
||||
for ext in ['txt', 'pdf', 'xml', 'ps']:
|
||||
for ext in self.formats:
|
||||
f = self.cleaned_data.get(ext, None)
|
||||
if not f:
|
||||
continue
|
||||
self.file_types.append('.%s' % ext)
|
||||
if not ('.txt' in self.file_types or '.xml' in self.file_types):
|
||||
raise forms.ValidationError('You must submit at least a valid .txt or a valid .xml file; didn\'t find either.')
|
||||
raise forms.ValidationError('Unexpected submission file types; found %s, but %s is required' % (', '.join(self.file_types), ' or '.join(self.base_formats)))
|
||||
|
||||
#debug.show('self.cleaned_data["xml"]')
|
||||
if self.cleaned_data.get('xml'):
|
||||
|
@ -168,6 +165,8 @@ class SubmissionUploadForm(forms.Form):
|
|||
)
|
||||
self.xmlroot = self.xmltree.getroot()
|
||||
draftname = self.xmlroot.attrib.get('docName')
|
||||
if draftname is None:
|
||||
raise forms.ValidationError("No docName attribute found in the xml root element")
|
||||
revmatch = re.search("-[0-9][0-9]$", draftname)
|
||||
if revmatch:
|
||||
self.revision = draftname[-2:]
|
||||
|
@ -273,7 +272,7 @@ class SubmissionUploadForm(forms.Form):
|
|||
settings.IDSUBMIT_MAX_DAILY_SUBMISSIONS, settings.IDSUBMIT_MAX_DAILY_SUBMISSIONS_SIZE,
|
||||
)
|
||||
|
||||
return super(SubmissionUploadForm, self).clean()
|
||||
return super(SubmissionBaseUploadForm, self).clean()
|
||||
|
||||
def check_submissions_tresholds(self, which, filter_kwargs, max_amount, max_size):
|
||||
submissions = Submission.objects.filter(**filter_kwargs)
|
||||
|
@ -332,6 +331,34 @@ class SubmissionUploadForm(forms.Form):
|
|||
else:
|
||||
return None
|
||||
|
||||
class SubmissionManualUploadForm(SubmissionBaseUploadForm):
|
||||
xml = forms.FileField(label=u'.xml format', required=False) # xml field with required=False instead of True
|
||||
txt = forms.FileField(label=u'.txt format', required=False)
|
||||
pdf = forms.FileField(label=u'.pdf format', required=False)
|
||||
ps = forms.FileField(label=u'.ps format', required=False)
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(SubmissionManualUploadForm, self).__init__(request, *args, **kwargs)
|
||||
self.formats = ['txt', 'pdf', 'xml', 'ps', ]
|
||||
self.base_formats = ['txt', 'xml', ]
|
||||
|
||||
def clean_txt(self):
|
||||
return self.clean_file("txt", PlainParser)
|
||||
|
||||
def clean_pdf(self):
|
||||
return self.clean_file("pdf", PDFParser)
|
||||
|
||||
def clean_ps(self):
|
||||
return self.clean_file("ps", PSParser)
|
||||
|
||||
class SubmissionAutoUploadForm(SubmissionBaseUploadForm):
|
||||
user = forms.EmailField(required=True)
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(SubmissionAutoUploadForm, self).__init__(request, *args, **kwargs)
|
||||
self.formats = ['xml', ]
|
||||
self.base_formats = ['xml', ]
|
||||
|
||||
class NameEmailForm(forms.Form):
|
||||
name = forms.CharField(required=True)
|
||||
email = forms.EmailField(label=u'Email address', required=True)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
|
||||
|
||||
Network Working Group A. Name
|
||||
Network Working Group %(initials)s %(surname)s
|
||||
Internet-Draft Test Centre Inc.
|
||||
Intended status: Informational %(month)s %(year)s
|
||||
Expires: %(expiration)s
|
||||
|
@ -180,13 +180,13 @@ Table of Contents
|
|||
|
||||
Author's Address
|
||||
|
||||
Author Name
|
||||
%(author)s
|
||||
Test Centre Inc.
|
||||
42 Some Road
|
||||
Some Where 12345
|
||||
UK
|
||||
|
||||
Email: author@example.com
|
||||
Email: %(email)s
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
<?rfc toc="yes"?>
|
||||
<rfc category="info" docName="%(name)s" ipr="trust200902">
|
||||
<front>
|
||||
<title>Testing Tests</title>
|
||||
<author fullname="Author Name" initials="A." surname="Name">
|
||||
<title>%(title)s</title>
|
||||
<author fullname="%(author)s" initials="%(initials)s" surname="%(surname)s">
|
||||
<organization>Test Centre Inc.</organization>
|
||||
|
||||
<address>
|
||||
|
@ -13,7 +13,7 @@
|
|||
<city>Some Where 12345</city>
|
||||
<country>UK</country>
|
||||
</postal>
|
||||
<email>author@example.com</email>
|
||||
<email>%(email)s</email>
|
||||
</address>
|
||||
</author>
|
||||
<date month="%(month)s" year="%(year)s" />
|
||||
|
|
|
@ -180,13 +180,13 @@ Table of Contents
|
|||
|
||||
Author's Address
|
||||
|
||||
Author Name
|
||||
%(author)s
|
||||
Test Centre Inc.
|
||||
42 Some Road
|
||||
Some Where 12345
|
||||
UK
|
||||
|
||||
Email: author@example.com
|
||||
Email: %(email)s
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright The IETF Trust 2011, All Rights Reserved
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import shutil
|
||||
import email
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
|
||||
from StringIO import StringIO
|
||||
from pyquery import PyQuery
|
||||
|
@ -19,6 +22,7 @@ from ietf.doc.models import Document, DocAlias, DocEvent, State, BallotDocEvent,
|
|||
from ietf.group.models import Group
|
||||
from ietf.group.utils import setup_default_community_list_for_group
|
||||
from ietf.meeting.models import Meeting
|
||||
from ietf.meeting.factories import MeetingFactory
|
||||
from ietf.message.models import Message
|
||||
from ietf.name.models import FormalLanguageName
|
||||
from ietf.person.models import Person
|
||||
|
@ -31,30 +35,37 @@ from ietf.utils.test_data import make_test_data
|
|||
from ietf.utils.test_utils import login_testing_unauthorized, unicontent, TestCase
|
||||
|
||||
|
||||
def submission_file(name, rev, group, format, templatename, author=None):
|
||||
def submission_file(name, rev, group, format, templatename, author=None, email=None, title=None, year=None, ascii=True):
|
||||
# construct appropriate text draft
|
||||
f = open(os.path.join(settings.BASE_DIR, "submit", templatename))
|
||||
template = f.read()
|
||||
f.close()
|
||||
|
||||
if not author:
|
||||
if author is None:
|
||||
author = PersonFactory()
|
||||
if email is None:
|
||||
email = author.email().address.lower()
|
||||
if title is None:
|
||||
title = "Test Document"
|
||||
if year is None:
|
||||
year = datetime.date.today().strftime("%Y")
|
||||
|
||||
submission_text = template % dict(
|
||||
date=datetime.date.today().strftime("%d %B %Y"),
|
||||
expiration=(datetime.date.today() + datetime.timedelta(days=100)).strftime("%d %B, %Y"),
|
||||
year=datetime.date.today().strftime("%Y"),
|
||||
year=year,
|
||||
month=datetime.date.today().strftime("%B"),
|
||||
name="%s-%s" % (name, rev),
|
||||
group=group or "",
|
||||
author=author.name,
|
||||
author=author.ascii if ascii else author.name,
|
||||
initials=author.initials(),
|
||||
surname=author.last_name(),
|
||||
email=author.email().address.lower(),
|
||||
surname=author.ascii_parts()[3] if ascii else author.name_parts()[3],
|
||||
email=email,
|
||||
title=title,
|
||||
)
|
||||
file = StringIO(submission_text)
|
||||
file.name = "%s-%s.%s" % (name, rev, format)
|
||||
return file
|
||||
return file, author
|
||||
|
||||
class SubmitTests(TestCase):
|
||||
def setUp(self):
|
||||
|
@ -99,7 +110,7 @@ class SubmitTests(TestCase):
|
|||
settings.SUBMIT_YANG_INVAL_MODEL_DIR = self.saved_yang_inval_model_dir
|
||||
|
||||
|
||||
def do_submission(self, name, rev, group=None, formats=["txt",]):
|
||||
def do_submission(self, name, rev, group=None, formats=["txt",], author=None):
|
||||
# break early in case of missing configuration
|
||||
self.assertTrue(os.path.exists(settings.IDSUBMIT_IDNITS_BINARY))
|
||||
|
||||
|
@ -113,8 +124,10 @@ class SubmitTests(TestCase):
|
|||
|
||||
# submit
|
||||
files = {}
|
||||
if author is None:
|
||||
author = PersonFactory()
|
||||
for format in formats:
|
||||
files[format] = submission_file(name, rev, group, format, "test_submission.%s" % format)
|
||||
files[format], __ = submission_file(name, rev, group, format, "test_submission.%s" % format, author=author)
|
||||
|
||||
r = self.client.post(url, files)
|
||||
if r.status_code != 302:
|
||||
|
@ -128,15 +141,17 @@ class SubmitTests(TestCase):
|
|||
self.assertTrue(os.path.exists(os.path.join(self.staging_dir, u"%s-%s.%s" % (name, rev, format))))
|
||||
self.assertEqual(Submission.objects.filter(name=name).count(), 1)
|
||||
submission = Submission.objects.get(name=name)
|
||||
self.assertTrue(all([ c.passed!=False for c in submission.checks.all() ]))
|
||||
if len(submission.authors) != 1:
|
||||
debug.show('submission')
|
||||
debug.pprint('submission.__dict__')
|
||||
self.assertEqual(len(submission.authors), 1)
|
||||
author = submission.authors[0]
|
||||
self.assertEqual(author["name"], "Author Name")
|
||||
self.assertEqual(author["email"], "author@example.com")
|
||||
self.assertEqual(author["affiliation"], "Test Centre Inc.")
|
||||
self.assertEqual(author["country"], "UK")
|
||||
a = submission.authors[0]
|
||||
self.assertEqual(a["name"], author.ascii)
|
||||
self.assertEqual(a["email"], author.email().address.lower())
|
||||
self.assertEqual(a["affiliation"], "Test Centre Inc.")
|
||||
self.assertEqual(a["country"], "UK")
|
||||
|
||||
return status_url
|
||||
return status_url, author
|
||||
|
||||
def supply_extra_metadata(self, name, status_url, submitter_name, submitter_email, replaces):
|
||||
# check the page
|
||||
|
@ -204,12 +219,12 @@ class SubmitTests(TestCase):
|
|||
rev = "00"
|
||||
group = "mars"
|
||||
|
||||
status_url = self.do_submission(name, rev, group, formats)
|
||||
status_url, author = self.do_submission(name, rev, group, formats)
|
||||
|
||||
# supply submitter info, then draft should be in and ready for approval
|
||||
mailbox_before = len(outbox)
|
||||
replaced_alias = draft.docalias_set.first()
|
||||
r = self.supply_extra_metadata(name, status_url, "Author Name", "author@example.com",
|
||||
r = self.supply_extra_metadata(name, status_url, author.ascii, author.email().address.lower(),
|
||||
replaces=str(replaced_alias.pk) + "," + str(sug_replaced_alias.pk))
|
||||
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
@ -245,7 +260,7 @@ class SubmitTests(TestCase):
|
|||
new_revision = draft.latest_event(type="new_revision")
|
||||
self.assertEqual(draft.group.acronym, "mars")
|
||||
self.assertEqual(new_revision.type, "new_revision")
|
||||
self.assertEqual(new_revision.by.name, "Author Name")
|
||||
self.assertEqual(new_revision.by.name, author.name)
|
||||
self.assertTrue(draft.latest_event(type="added_suggested_replaces"))
|
||||
self.assertTrue(not os.path.exists(os.path.join(self.staging_dir, u"%s-%s.txt" % (name, rev))))
|
||||
self.assertTrue(os.path.exists(os.path.join(self.repository_dir, u"%s-%s.txt" % (name, rev))))
|
||||
|
@ -255,8 +270,7 @@ class SubmitTests(TestCase):
|
|||
self.assertEqual(draft.get_state("draft-stream-%s" % draft.stream_id).slug, "wg-doc")
|
||||
authors = draft.documentauthor_set.all()
|
||||
self.assertEqual(len(authors), 1)
|
||||
self.assertEqual(authors[0].person.plain_name(), "Author Name")
|
||||
self.assertEqual(authors[0].email.address, "author@example.com")
|
||||
self.assertEqual(authors[0].person, author)
|
||||
self.assertEqual(set(draft.formal_languages.all()), set(FormalLanguageName.objects.filter(slug="json")))
|
||||
self.assertEqual(draft.relations_that_doc("replaces").count(), 1)
|
||||
self.assertTrue(draft.relations_that_doc("replaces").first().target, replaced_alias)
|
||||
|
@ -264,7 +278,7 @@ class SubmitTests(TestCase):
|
|||
self.assertTrue(draft.relations_that_doc("possibly-replaces").first().target, sug_replaced_alias)
|
||||
self.assertEqual(len(outbox), mailbox_before + 4)
|
||||
self.assertTrue((u"I-D Action: %s" % name) in outbox[-3]["Subject"])
|
||||
self.assertTrue("Author Name" in unicode(outbox[-3]))
|
||||
self.assertTrue(author.ascii in unicode(outbox[-3]))
|
||||
self.assertTrue("New Version Notification" in outbox[-2]["Subject"])
|
||||
self.assertTrue(name in unicode(outbox[-2]))
|
||||
self.assertTrue("mars" in unicode(outbox[-2]))
|
||||
|
@ -302,17 +316,12 @@ class SubmitTests(TestCase):
|
|||
if not stream_type=='ietf':
|
||||
draft.stream_id=stream_type
|
||||
draft.save_with_history([DocEvent.objects.create(doc=draft, rev=draft.rev, type="added_comment", by=Person.objects.get(user__username="secretary"), desc="Test")])
|
||||
if not change_authors:
|
||||
draft.documentauthor_set.all().delete()
|
||||
author_person, author_email = ensure_person_email_info_exists(u'Author Name',u'author@example.com')
|
||||
draft.documentauthor_set.create(person=author_person, email=author_email)
|
||||
else:
|
||||
prev_author = draft.documentauthor_set.all()[0]
|
||||
if change_authors:
|
||||
# Make it such that one of the previous authors has an invalid email address
|
||||
bogus_person, bogus_email = ensure_person_email_info_exists(u'Bogus Person',None)
|
||||
DocumentAuthor.objects.create(document=draft, person=bogus_person, email=bogus_email, order=draft.documentauthor_set.latest('order').order+1)
|
||||
|
||||
prev_author = draft.documentauthor_set.all()[0]
|
||||
|
||||
# pretend IANA reviewed it
|
||||
draft.set_state(State.objects.get(used=True, type="draft-iana-review", slug="not-ok"))
|
||||
|
||||
|
@ -342,7 +351,7 @@ class SubmitTests(TestCase):
|
|||
with open(os.path.join(self.repository_dir, "%s-%s.txt" % (name, old_rev)), 'w') as f:
|
||||
f.write("a" * 2000)
|
||||
|
||||
status_url = self.do_submission(name, rev, group, formats)
|
||||
status_url, author = self.do_submission(name, rev, group, formats, author=prev_author.person)
|
||||
|
||||
# supply submitter info, then previous authors get a confirmation email
|
||||
mailbox_before = len(outbox)
|
||||
|
@ -440,12 +449,11 @@ class SubmitTests(TestCase):
|
|||
self.assertEqual(draft.get_state_slug("draft-iana-review"), "changed")
|
||||
authors = draft.documentauthor_set.all()
|
||||
self.assertEqual(len(authors), 1)
|
||||
self.assertEqual(authors[0].person.plain_name(), "Author Name")
|
||||
self.assertEqual(authors[0].email.address, "author@example.com")
|
||||
self.assertIn(author, [ a.person for a in authors ])
|
||||
self.assertEqual(len(outbox), mailbox_before + 3)
|
||||
self.assertTrue((u"I-D Action: %s" % name) in outbox[-3]["Subject"])
|
||||
self.assertTrue((u"I-D Action: %s" % name) in draft.message_set.order_by("-time")[0].subject)
|
||||
self.assertTrue("Author Name" in unicode(outbox[-3]))
|
||||
self.assertTrue(author.ascii in unicode(outbox[-3]))
|
||||
self.assertTrue("i-d-announce@" in outbox[-3]['To'])
|
||||
self.assertTrue("New Version Notification" in outbox[-2]["Subject"])
|
||||
self.assertTrue(name in unicode(outbox[-2]))
|
||||
|
@ -466,7 +474,7 @@ class SubmitTests(TestCase):
|
|||
self.submit_existing(["txt", "xml"])
|
||||
|
||||
def test_submit_existing_txt_preserve_authors(self):
|
||||
self.submit_existing(["txt"],change_authors=False)
|
||||
self.submit_existing(["txt"], change_authors=False)
|
||||
|
||||
def test_submit_existing_rg(self):
|
||||
self.submit_existing(["txt"],group_type='rg')
|
||||
|
@ -488,7 +496,7 @@ class SubmitTests(TestCase):
|
|||
rev = "00"
|
||||
group = None
|
||||
|
||||
status_url = self.do_submission(name, rev, group, formats)
|
||||
status_url, author = self.do_submission(name, rev, group, formats)
|
||||
|
||||
# supply submitter info, then draft should be be ready for email auth
|
||||
mailbox_before = len(outbox)
|
||||
|
@ -505,7 +513,7 @@ class SubmitTests(TestCase):
|
|||
self.assertTrue("Confirm submission" in confirm_email["Subject"])
|
||||
self.assertTrue(name in confirm_email["Subject"])
|
||||
# both submitter and author get email
|
||||
self.assertTrue("author@example.com" in confirm_email["To"])
|
||||
self.assertTrue(author.email().address.lower() in confirm_email["To"])
|
||||
self.assertTrue("submitter@example.com" in confirm_email["To"])
|
||||
self.assertFalse("chairs have been copied" in unicode(confirm_email))
|
||||
|
||||
|
@ -543,7 +551,7 @@ class SubmitTests(TestCase):
|
|||
replaces_count = draft.relateddocument_set.filter(relationship_id='replaces').count()
|
||||
name = draft.name
|
||||
rev = '%02d'%(int(draft.rev)+1)
|
||||
status_url = self.do_submission(name,rev)
|
||||
status_url, author = self.do_submission(name,rev)
|
||||
mailbox_before = len(outbox)
|
||||
replaced_alias = draft.docalias_set.first()
|
||||
r = self.supply_extra_metadata(name, status_url, "Submitter Name", "author@example.com", replaces=str(replaced_alias.pk))
|
||||
|
@ -611,7 +619,7 @@ class SubmitTests(TestCase):
|
|||
name = "draft-ietf-mars-testing-tests"
|
||||
rev = "00"
|
||||
|
||||
status_url = self.do_submission(name, rev)
|
||||
status_url, author = self.do_submission(name, rev)
|
||||
|
||||
# check we got cancel button
|
||||
r = self.client.get(status_url)
|
||||
|
@ -633,7 +641,7 @@ class SubmitTests(TestCase):
|
|||
name = "draft-ietf-mars-testing-tests"
|
||||
rev = "00"
|
||||
|
||||
status_url = self.do_submission(name, rev)
|
||||
status_url, author = self.do_submission(name, rev)
|
||||
|
||||
# check we have edit button
|
||||
r = self.client.get(status_url)
|
||||
|
@ -897,7 +905,7 @@ class SubmitTests(TestCase):
|
|||
# submit
|
||||
files = {}
|
||||
for format in formats:
|
||||
files[format] = submission_file(name, rev, group, "bad", "test_submission.bad")
|
||||
files[format], author = submission_file(name, rev, group, "bad", "test_submission.bad")
|
||||
|
||||
r = self.client.post(url, files)
|
||||
|
||||
|
@ -950,7 +958,9 @@ class SubmitTests(TestCase):
|
|||
#author = PersonFactory(name=u"Jörgen Nilsson".encode('latin1'))
|
||||
user = UserFactory(first_name=u"Jörgen", last_name=u"Nilsson")
|
||||
author = PersonFactory(user=user)
|
||||
files = {"txt": submission_file(name, rev, group, "txt", "test_submission.nonascii", author=author) }
|
||||
|
||||
file, __ = submission_file(name, rev, group, "txt", "test_submission.nonascii", author=author, ascii=False)
|
||||
files = {"txt": file }
|
||||
|
||||
r = self.client.post(url, files)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
@ -1270,7 +1280,7 @@ ZSBvZiBsaW5lcyAtIGJ1dCBpdCBjb3VsZCBiZSBhIGRyYWZ0Cg==
|
|||
|
||||
if r.status_code != 302:
|
||||
q = PyQuery(r.content)
|
||||
print q
|
||||
print(q)
|
||||
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
|
@ -1396,7 +1406,7 @@ Thank you
|
|||
|
||||
if r.status_code != 302:
|
||||
q = PyQuery(r.content)
|
||||
print q
|
||||
print(q)
|
||||
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
|
@ -1475,7 +1485,7 @@ Subject: test
|
|||
# submit
|
||||
files = {}
|
||||
for format in formats:
|
||||
files[format] = submission_file(name, rev, group, format, "test_submission.%s" % format)
|
||||
files[format], author = submission_file(name, rev, group, format, "test_submission.%s" % format)
|
||||
|
||||
r = self.client.post(url, files)
|
||||
if r.status_code != 302:
|
||||
|
@ -1519,3 +1529,99 @@ Subject: test
|
|||
self.assertEqual(submission.submitter, email.utils.formataddr((submitter_name, submitter_email)))
|
||||
|
||||
return r
|
||||
|
||||
class ApiSubmitTests(TestCase):
|
||||
def setUp(self):
|
||||
# break early in case of missing configuration
|
||||
self.assertTrue(os.path.exists(settings.IDSUBMIT_IDNITS_BINARY))
|
||||
|
||||
self.saved_idsubmit_staging_path = settings.IDSUBMIT_STAGING_PATH
|
||||
self.staging_dir = self.tempdir('submit-staging')
|
||||
settings.IDSUBMIT_STAGING_PATH = self.staging_dir
|
||||
|
||||
self.saved_internet_draft_path = settings.INTERNET_DRAFT_PATH
|
||||
self.saved_idsubmit_repository_path = settings.IDSUBMIT_REPOSITORY_PATH
|
||||
self.repository_dir = self.tempdir('submit-repository')
|
||||
settings.INTERNET_DRAFT_PATH = settings.IDSUBMIT_REPOSITORY_PATH = self.repository_dir
|
||||
|
||||
self.saved_archive_dir = settings.INTERNET_DRAFT_ARCHIVE_DIR
|
||||
self.archive_dir = self.tempdir('submit-archive')
|
||||
settings.INTERNET_DRAFT_ARCHIVE_DIR = self.archive_dir
|
||||
|
||||
MeetingFactory(type_id='ietf', date=datetime.date.today()+datetime.timedelta(days=60))
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.staging_dir)
|
||||
shutil.rmtree(self.repository_dir)
|
||||
shutil.rmtree(self.archive_dir)
|
||||
settings.IDSUBMIT_STAGING_PATH = self.saved_idsubmit_staging_path
|
||||
settings.INTERNET_DRAFT_PATH = self.saved_internet_draft_path
|
||||
settings.IDSUBMIT_REPOSITORY_PATH = self.saved_idsubmit_repository_path
|
||||
settings.INTERNET_DRAFT_ARCHIVE_DIR = self.saved_archive_dir
|
||||
|
||||
def post_submission(self, rev, author=None, name=None, group=None, email=None, title=None, year=None):
|
||||
|
||||
url = urlreverse('ietf.submit.views.api_submit')
|
||||
|
||||
if author is None:
|
||||
author = PersonFactory()
|
||||
if name is None:
|
||||
slug = re.sub('[^a-z0-9-]+', '', author.ascii_parts()[3].lower())
|
||||
name = 'draft-%s-foo' % slug
|
||||
if email is None:
|
||||
email = author.user.username
|
||||
|
||||
# submit
|
||||
data = {}
|
||||
data['xml'], author = submission_file(name, rev, group, 'xml', "test_submission.xml", author=author, email=email, title=title, year=year)
|
||||
data['user'] = email
|
||||
|
||||
r = self.client.post(url, data)
|
||||
|
||||
return r, author, name
|
||||
|
||||
def test_api_submit_bad_method(self):
|
||||
url = urlreverse('ietf.submit.views.api_submit')
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 405)
|
||||
|
||||
def test_api_submit_ok(self):
|
||||
r, author, name = self.post_submission('00')
|
||||
expected = "Upload of %s OK, confirmation requests sent to:\n %s" % (name, author.formatted_email())
|
||||
self.assertContains(r, expected, status_code=200)
|
||||
|
||||
def test_api_submit_no_user(self):
|
||||
email='nonexistant.user@example.org'
|
||||
r, author, name = self.post_submission('00', email=email)
|
||||
expected = "No such user: %s" % email
|
||||
self.assertContains(r, expected, status_code=404)
|
||||
|
||||
def test_api_submit_no_person(self):
|
||||
user = UserFactory()
|
||||
email = user.username
|
||||
r, author, name = self.post_submission('00', email=email)
|
||||
expected = "No person with username %s" % email
|
||||
self.assertContains(r, expected, status_code=404)
|
||||
|
||||
def test_api_submit_wrong_revision(self):
|
||||
r, author, name = self.post_submission('01')
|
||||
expected = "Invalid revision (revision 00 is expected)"
|
||||
self.assertContains(r, expected, status_code=400)
|
||||
|
||||
def test_api_submit_pending_submission(self):
|
||||
r, author, name = self.post_submission('00')
|
||||
expected = "Upload of"
|
||||
self.assertContains(r, expected, status_code=200)
|
||||
r, author, name = self.post_submission('00', author=author, name=name)
|
||||
expected = "A submission with same name and revision is currently being processed"
|
||||
self.assertContains(r, expected, status_code=400)
|
||||
|
||||
def test_api_submit_no_title(self):
|
||||
r, author, name = self.post_submission('00', title="")
|
||||
expected = "Could not extract a valid title from the upload"
|
||||
self.assertContains(r, expected, status_code=400)
|
||||
|
||||
def test_api_submit_failed_idnits(self):
|
||||
r, author, name = self.post_submission('00', year="1900")
|
||||
expected = "Document date must be within 3 days of submission date"
|
||||
self.assertContains(r, expected, status_code=400)
|
||||
|
|
|
@ -3,9 +3,12 @@
|
|||
import os
|
||||
import datetime
|
||||
import six # pyflakes:ignore
|
||||
import xml2rfc
|
||||
from unidecode import unidecode
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.validators import validate_email, ValidationError
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
|
@ -18,12 +21,15 @@ from ietf.doc.utils import set_replaces_for_document
|
|||
from ietf.doc.mails import send_review_possibly_replaces_request
|
||||
from ietf.group.models import Group
|
||||
from ietf.ietfauth.utils import has_role
|
||||
from ietf.name.models import StreamName
|
||||
from ietf.name.models import StreamName, FormalLanguageName
|
||||
from ietf.person.models import Person, Email
|
||||
from ietf.community.utils import update_name_contains_indexes_with_new_doc
|
||||
from ietf.submit.mail import announce_to_lists, announce_new_version, announce_to_authors
|
||||
from ietf.submit.models import Submission, SubmissionEvent, Preapproval, DraftSubmissionStateName
|
||||
from ietf.submit.mail import ( announce_to_lists, announce_new_version, announce_to_authors,
|
||||
send_approval_request_to_group, send_submission_confirmation )
|
||||
from ietf.submit.models import Submission, SubmissionEvent, Preapproval, DraftSubmissionStateName, SubmissionCheck
|
||||
from ietf.utils import log
|
||||
from ietf.utils.accesstoken import generate_random_key
|
||||
from ietf.utils.draft import Draft
|
||||
from ietf.utils.mail import is_valid_email
|
||||
|
||||
|
||||
|
@ -461,16 +467,16 @@ def cancel_submission(submission):
|
|||
remove_submission_files(submission)
|
||||
|
||||
def rename_submission_files(submission, prev_rev, new_rev):
|
||||
from ietf.submit.forms import SubmissionUploadForm
|
||||
for ext in SubmissionUploadForm.base_fields.keys():
|
||||
from ietf.submit.forms import SubmissionManualUploadForm
|
||||
for ext in SubmissionManualUploadForm.base_fields.keys():
|
||||
source = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.%s' % (submission.name, prev_rev, ext))
|
||||
dest = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.%s' % (submission.name, new_rev, ext))
|
||||
if os.path.exists(source):
|
||||
os.rename(source, dest)
|
||||
|
||||
def move_files_to_repository(submission):
|
||||
from ietf.submit.forms import SubmissionUploadForm
|
||||
for ext in SubmissionUploadForm.base_fields.keys():
|
||||
from ietf.submit.forms import SubmissionManualUploadForm
|
||||
for ext in SubmissionManualUploadForm.base_fields.keys():
|
||||
source = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.%s' % (submission.name, submission.rev, ext))
|
||||
dest = os.path.join(settings.IDSUBMIT_REPOSITORY_PATH, '%s-%s.%s' % (submission.name, submission.rev, ext))
|
||||
if os.path.exists(source):
|
||||
|
@ -533,3 +539,184 @@ def expire_submission(submission, by):
|
|||
submission.save()
|
||||
|
||||
SubmissionEvent.objects.create(submission=submission, by=by, desc="Cancelled expired submission")
|
||||
|
||||
def get_draft_meta(form):
|
||||
authors = []
|
||||
file_name = {}
|
||||
abstract = None
|
||||
file_size = None
|
||||
for ext in form.fields.keys():
|
||||
if not ext in form.formats:
|
||||
continue
|
||||
f = form.cleaned_data[ext]
|
||||
if not f:
|
||||
continue
|
||||
|
||||
name = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.%s' % (form.filename, form.revision, ext))
|
||||
file_name[ext] = name
|
||||
with open(name, 'wb+') as destination:
|
||||
for chunk in f.chunks():
|
||||
destination.write(chunk)
|
||||
|
||||
if form.cleaned_data['xml']:
|
||||
if not ('txt' in form.cleaned_data and form.cleaned_data['txt']):
|
||||
file_name['txt'] = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.txt' % (form.filename, form.revision))
|
||||
try:
|
||||
pagedwriter = xml2rfc.PaginatedTextRfcWriter(form.xmltree, quiet=True)
|
||||
pagedwriter.write(file_name['txt'])
|
||||
except Exception as e:
|
||||
raise ValidationError("Error from xml2rfc: %s" % e)
|
||||
file_size = os.stat(file_name['txt']).st_size
|
||||
# Some meta-information, such as the page-count, can only
|
||||
# be retrieved from the generated text file. Provide a
|
||||
# parsed draft object to get at that kind of information.
|
||||
with open(file_name['txt']) as txt_file:
|
||||
form.parsed_draft = Draft(txt_file.read().decode('utf8'), txt_file.name)
|
||||
|
||||
else:
|
||||
file_size = form.cleaned_data['txt'].size
|
||||
|
||||
if form.authors:
|
||||
authors = form.authors
|
||||
else:
|
||||
# If we don't have an xml file, try to extract the
|
||||
# relevant information from the text file
|
||||
for author in form.parsed_draft.get_author_list():
|
||||
full_name, first_name, middle_initial, last_name, name_suffix, email, country, company = author
|
||||
|
||||
name = full_name.replace("\n", "").replace("\r", "").replace("<", "").replace(">", "").strip()
|
||||
|
||||
if email:
|
||||
try:
|
||||
validate_email(email)
|
||||
except ValidationError:
|
||||
email = ""
|
||||
|
||||
def turn_into_unicode(s):
|
||||
if s is None:
|
||||
return u""
|
||||
|
||||
if isinstance(s, unicode):
|
||||
return s
|
||||
else:
|
||||
try:
|
||||
return s.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
try:
|
||||
return s.decode("latin-1")
|
||||
except UnicodeDecodeError:
|
||||
return ""
|
||||
|
||||
name = turn_into_unicode(name)
|
||||
email = turn_into_unicode(email)
|
||||
company = turn_into_unicode(company)
|
||||
|
||||
authors.append({
|
||||
"name": name,
|
||||
"email": email,
|
||||
"affiliation": company,
|
||||
"country": country
|
||||
})
|
||||
|
||||
if form.abstract:
|
||||
abstract = form.abstract
|
||||
else:
|
||||
abstract = form.parsed_draft.get_abstract()
|
||||
|
||||
return authors, abstract, file_name, file_size
|
||||
|
||||
|
||||
def get_submission(form):
|
||||
submissions = Submission.objects.filter(name=form.filename,
|
||||
rev=form.revision,
|
||||
state_id = "waiting-for-draft").distinct()
|
||||
if not submissions:
|
||||
submission = Submission(name=form.filename, rev=form.revision, group=form.group)
|
||||
elif len(submissions) == 1:
|
||||
submission = submissions.first()
|
||||
else:
|
||||
raise Exception("Multiple submissions found waiting for upload")
|
||||
return submission
|
||||
|
||||
|
||||
def fill_in_submission(form, submission, authors, abstract, file_size):
|
||||
# See if there is a Submission in state waiting-for-draft
|
||||
# for this revision.
|
||||
# If so - we're going to update it otherwise we create a new object
|
||||
|
||||
submission.state = DraftSubmissionStateName.objects.get(slug="uploaded")
|
||||
submission.remote_ip = form.remote_ip
|
||||
submission.title = form.title
|
||||
submission.abstract = abstract
|
||||
submission.pages = form.parsed_draft.get_pagecount()
|
||||
submission.words = form.parsed_draft.get_wordcount()
|
||||
submission.authors = authors
|
||||
submission.first_two_pages = ''.join(form.parsed_draft.pages[:2])
|
||||
submission.file_size = file_size
|
||||
submission.file_types = ','.join(form.file_types)
|
||||
submission.submission_date = datetime.date.today()
|
||||
submission.document_date = form.parsed_draft.get_creation_date()
|
||||
submission.replaces = ""
|
||||
|
||||
submission.save()
|
||||
|
||||
submission.formal_languages = FormalLanguageName.objects.filter(slug__in=form.parsed_draft.get_formal_languages())
|
||||
|
||||
def apply_checkers(submission, file_name):
|
||||
# run submission checkers
|
||||
def apply_check(submission, checker, method, fn):
|
||||
func = getattr(checker, method)
|
||||
passed, message, errors, warnings, items = func(fn)
|
||||
check = SubmissionCheck(submission=submission, checker=checker.name, passed=passed,
|
||||
message=message, errors=errors, warnings=warnings, items=items,
|
||||
symbol=checker.symbol)
|
||||
check.save()
|
||||
|
||||
for checker_path in settings.IDSUBMIT_CHECKER_CLASSES:
|
||||
checker_class = import_string(checker_path)
|
||||
checker = checker_class()
|
||||
# ordered list of methods to try
|
||||
for method in ("check_fragment_xml", "check_file_xml", "check_fragment_txt", "check_file_txt", ):
|
||||
ext = method[-3:]
|
||||
if hasattr(checker, method) and ext in file_name:
|
||||
apply_check(submission, checker, method, file_name[ext])
|
||||
break
|
||||
|
||||
def send_confirmation_emails(request, submission, requires_group_approval, requires_prev_authors_approval):
|
||||
docevent_from_submission(request, submission, desc="Uploaded new revision")
|
||||
|
||||
if requires_group_approval:
|
||||
submission.state = DraftSubmissionStateName.objects.get(slug="grp-appr")
|
||||
submission.save()
|
||||
|
||||
sent_to = send_approval_request_to_group(request, submission)
|
||||
|
||||
desc = "sent approval email to group chairs: %s" % u", ".join(sent_to)
|
||||
docDesc = u"Request for posting approval emailed to group chairs: %s" % u", ".join(sent_to)
|
||||
|
||||
else:
|
||||
group_authors_changed = False
|
||||
doc = submission.existing_document()
|
||||
if doc and doc.group:
|
||||
old_authors = [ author.person for author in doc.documentauthor_set.all() ]
|
||||
new_authors = [ get_person_from_name_email(author["name"], author.get("email")) for author in submission.authors ]
|
||||
group_authors_changed = set(old_authors)!=set(new_authors)
|
||||
|
||||
submission.auth_key = generate_random_key()
|
||||
if requires_prev_authors_approval:
|
||||
submission.state = DraftSubmissionStateName.objects.get(slug="aut-appr")
|
||||
else:
|
||||
submission.state = DraftSubmissionStateName.objects.get(slug="auth")
|
||||
submission.save()
|
||||
|
||||
sent_to = send_submission_confirmation(request, submission, chair_notice=group_authors_changed)
|
||||
|
||||
if submission.state_id == "aut-appr":
|
||||
desc = u"sent confirmation email to previous authors: %s" % u", ".join(sent_to)
|
||||
docDesc = "Request for posting confirmation emailed to previous authors: %s" % u", ".join(sent_to)
|
||||
else:
|
||||
desc = u"sent confirmation email to submitter and authors: %s" % u", ".join(sent_to)
|
||||
docDesc = "Request for posting confirmation emailed to submitter and authors: %s" % u", ".join(sent_to)
|
||||
return sent_to, desc, docDesc
|
||||
|
||||
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
# Copyright The IETF Trust 2007, All Rights Reserved
|
||||
import base64
|
||||
import datetime
|
||||
import os
|
||||
import xml2rfc
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.models import User
|
||||
from django.urls import reverse as urlreverse
|
||||
from django.core.validators import validate_email, ValidationError
|
||||
from django.core.validators import ValidationError
|
||||
from django.http import HttpResponseRedirect, Http404, HttpResponseForbidden, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils.module_loading import import_string
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
|
@ -20,20 +19,17 @@ from ietf.group.models import Group
|
|||
from ietf.ietfauth.utils import has_role, role_required
|
||||
from ietf.mailtrigger.utils import gather_address_lists
|
||||
from ietf.message.models import Message, MessageAttachment
|
||||
from ietf.name.models import FormalLanguageName
|
||||
from ietf.submit.forms import ( SubmissionUploadForm, AuthorForm, SubmitterForm, EditSubmissionForm,
|
||||
PreapprovalForm, ReplacesForm, SubmissionEmailForm, MessageModelForm )
|
||||
from ietf.submit.mail import ( send_full_url, send_approval_request_to_group,
|
||||
send_submission_confirmation, send_manual_post_request, add_submission_email, get_reply_to )
|
||||
from ietf.submit.models import (Submission, SubmissionCheck, Preapproval,
|
||||
from ietf.submit.forms import ( SubmissionManualUploadForm, SubmissionAutoUploadForm, AuthorForm,
|
||||
SubmitterForm, EditSubmissionForm, PreapprovalForm, ReplacesForm, SubmissionEmailForm, MessageModelForm )
|
||||
from ietf.submit.mail import ( send_full_url, send_manual_post_request, add_submission_email, get_reply_to )
|
||||
from ietf.submit.models import (Submission, Preapproval,
|
||||
DraftSubmissionStateName, SubmissionEmailEvent )
|
||||
from ietf.submit.utils import ( approvable_submissions_for_user, preapprovals_for_user,
|
||||
recently_approved_by_user, validate_submission, create_submission_event,
|
||||
docevent_from_submission, post_submission, cancel_submission, rename_submission_files,
|
||||
get_person_from_name_email )
|
||||
recently_approved_by_user, validate_submission, create_submission_event, docevent_from_submission,
|
||||
post_submission, cancel_submission, rename_submission_files, remove_submission_files, get_draft_meta,
|
||||
get_submission, fill_in_submission, apply_checkers, send_confirmation_emails )
|
||||
from ietf.stats.utils import clean_country_name
|
||||
from ietf.utils.accesstoken import generate_random_key, generate_access_token
|
||||
from ietf.utils.draft import Draft
|
||||
from ietf.utils.accesstoken import generate_access_token
|
||||
from ietf.utils.log import log
|
||||
from ietf.utils.mail import send_mail_message
|
||||
|
||||
|
@ -41,144 +37,20 @@ from ietf.utils.mail import send_mail_message
|
|||
def upload_submission(request):
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
form = SubmissionUploadForm(request, data=request.POST, files=request.FILES)
|
||||
form = SubmissionManualUploadForm(request, data=request.POST, files=request.FILES)
|
||||
if form.is_valid():
|
||||
authors = []
|
||||
file_name = {}
|
||||
abstract = None
|
||||
file_size = None
|
||||
for ext in form.fields.keys():
|
||||
f = form.cleaned_data[ext]
|
||||
if not f:
|
||||
continue
|
||||
authors, abstract, file_name, file_size = get_draft_meta(form)
|
||||
|
||||
name = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.%s' % (form.filename, form.revision, ext))
|
||||
file_name[ext] = name
|
||||
with open(name, 'wb+') as destination:
|
||||
for chunk in f.chunks():
|
||||
destination.write(chunk)
|
||||
|
||||
if form.cleaned_data['xml']:
|
||||
if not form.cleaned_data['txt']:
|
||||
file_name['txt'] = os.path.join(settings.IDSUBMIT_STAGING_PATH, '%s-%s.txt' % (form.filename, form.revision))
|
||||
submission = get_submission(form)
|
||||
try:
|
||||
pagedwriter = xml2rfc.PaginatedTextRfcWriter(form.xmltree, quiet=True)
|
||||
pagedwriter.write(file_name['txt'])
|
||||
except Exception as e:
|
||||
raise ValidationError("Error from xml2rfc: %s" % e)
|
||||
file_size = os.stat(file_name['txt']).st_size
|
||||
# Some meta-information, such as the page-count, can only
|
||||
# be retrieved from the generated text file. Provide a
|
||||
# parsed draft object to get at that kind of information.
|
||||
with open(file_name['txt']) as txt_file:
|
||||
form.parsed_draft = Draft(txt_file.read().decode('utf8'), txt_file.name)
|
||||
|
||||
else:
|
||||
file_size = form.cleaned_data['txt'].size
|
||||
|
||||
if form.authors:
|
||||
authors = form.authors
|
||||
else:
|
||||
# If we don't have an xml file, try to extract the
|
||||
# relevant information from the text file
|
||||
for author in form.parsed_draft.get_author_list():
|
||||
full_name, first_name, middle_initial, last_name, name_suffix, email, country, company = author
|
||||
|
||||
name = full_name.replace("\n", "").replace("\r", "").replace("<", "").replace(">", "").strip()
|
||||
|
||||
if email:
|
||||
try:
|
||||
validate_email(email)
|
||||
except ValidationError:
|
||||
email = ""
|
||||
|
||||
def turn_into_unicode(s):
|
||||
if s is None:
|
||||
return u""
|
||||
|
||||
if isinstance(s, unicode):
|
||||
return s
|
||||
else:
|
||||
try:
|
||||
return s.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
try:
|
||||
return s.decode("latin-1")
|
||||
except UnicodeDecodeError:
|
||||
return ""
|
||||
|
||||
name = turn_into_unicode(name)
|
||||
email = turn_into_unicode(email)
|
||||
company = turn_into_unicode(company)
|
||||
|
||||
authors.append({
|
||||
"name": name,
|
||||
"email": email,
|
||||
"affiliation": company,
|
||||
"country": country
|
||||
})
|
||||
|
||||
if form.abstract:
|
||||
abstract = form.abstract
|
||||
else:
|
||||
abstract = form.parsed_draft.get_abstract()
|
||||
|
||||
# See if there is a Submission in state waiting-for-draft
|
||||
# for this revision.
|
||||
# If so - we're going to update it otherwise we create a new object
|
||||
|
||||
submissions = Submission.objects.filter(name=form.filename,
|
||||
rev=form.revision,
|
||||
state_id = "waiting-for-draft").distinct()
|
||||
|
||||
if not submissions:
|
||||
submission = Submission(name=form.filename, rev=form.revision, group=form.group)
|
||||
elif len(submissions) == 1:
|
||||
submission = submissions[0]
|
||||
else:
|
||||
raise Exception("Multiple submissions found waiting for upload")
|
||||
|
||||
try:
|
||||
submission.state = DraftSubmissionStateName.objects.get(slug="uploaded")
|
||||
submission.remote_ip = form.remote_ip
|
||||
submission.title = form.title
|
||||
submission.abstract = abstract
|
||||
submission.pages = form.parsed_draft.get_pagecount()
|
||||
submission.words = form.parsed_draft.get_wordcount()
|
||||
submission.authors = authors
|
||||
submission.first_two_pages = ''.join(form.parsed_draft.pages[:2])
|
||||
submission.file_size = file_size
|
||||
submission.file_types = ','.join(form.file_types)
|
||||
submission.submission_date = datetime.date.today()
|
||||
submission.document_date = form.parsed_draft.get_creation_date()
|
||||
submission.replaces = ""
|
||||
|
||||
submission.save()
|
||||
|
||||
submission.formal_languages = FormalLanguageName.objects.filter(slug__in=form.parsed_draft.get_formal_languages())
|
||||
|
||||
fill_in_submission(form, submission, authors, abstract, file_size)
|
||||
except Exception as e:
|
||||
if submission:
|
||||
submission.delete()
|
||||
log("Exception: %s\n" % e)
|
||||
raise
|
||||
|
||||
# run submission checkers
|
||||
def apply_check(submission, checker, method, fn):
|
||||
func = getattr(checker, method)
|
||||
passed, message, errors, warnings, items = func(fn)
|
||||
check = SubmissionCheck(submission=submission, checker=checker.name, passed=passed,
|
||||
message=message, errors=errors, warnings=warnings, items=items,
|
||||
symbol=checker.symbol)
|
||||
check.save()
|
||||
|
||||
for checker_path in settings.IDSUBMIT_CHECKER_CLASSES:
|
||||
checker_class = import_string(checker_path)
|
||||
checker = checker_class()
|
||||
# ordered list of methods to try
|
||||
for method in ("check_fragment_xml", "check_file_xml", "check_fragment_txt", "check_file_txt", ):
|
||||
ext = method[-3:]
|
||||
if hasattr(checker, method) and ext in file_name:
|
||||
apply_check(submission, checker, method, file_name[ext])
|
||||
break
|
||||
apply_checkers(submission, file_name)
|
||||
|
||||
create_submission_event(request, submission, desc="Uploaded submission")
|
||||
# Don't add an "Uploaded new revision doevent yet, in case of cancellation
|
||||
|
@ -186,22 +58,92 @@ def upload_submission(request):
|
|||
return redirect("ietf.submit.views.submission_status", submission_id=submission.pk, access_token=submission.access_token())
|
||||
except IOError as e:
|
||||
if "read error" in str(e): # The server got an IOError when trying to read POST data
|
||||
form = SubmissionUploadForm(request=request)
|
||||
form = SubmissionManualUploadForm(request=request)
|
||||
form._errors = {}
|
||||
form._errors["__all__"] = form.error_class(["There was a failure receiving the complete form data -- please try again."])
|
||||
else:
|
||||
raise
|
||||
except ValidationError as e:
|
||||
form = SubmissionUploadForm(request=request)
|
||||
form = SubmissionManualUploadForm(request=request)
|
||||
form._errors = {}
|
||||
form._errors["__all__"] = form.error_class(["There was a failure converting the xml file to text -- please verify that your xml file is valid. (%s)" % e.message])
|
||||
else:
|
||||
form = SubmissionUploadForm(request=request)
|
||||
form = SubmissionManualUploadForm(request=request)
|
||||
|
||||
return render(request, 'submit/upload_submission.html',
|
||||
{'selected': 'index',
|
||||
'form': form})
|
||||
|
||||
@csrf_exempt
|
||||
def api_submit(request):
|
||||
"Automated submission entrypoint"
|
||||
submission = None
|
||||
def err(code, text):
|
||||
return HttpResponse(text, status=code, reason=text, content_type='text/plain')
|
||||
if request.method == 'POST':
|
||||
e = None
|
||||
try:
|
||||
form = SubmissionAutoUploadForm(request, data=request.POST, files=request.FILES)
|
||||
if form.is_valid():
|
||||
username = form.cleaned_data['user']
|
||||
user = User.objects.filter(username=username)
|
||||
if user.count() == 0:
|
||||
return err(404, "No such user: %s" % username)
|
||||
if user.count() > 1:
|
||||
return err(500, "Multiple matching accounts for %s" % username)
|
||||
user = user.first()
|
||||
if not hasattr(user, 'person'):
|
||||
return err(404, "No person with username %s" % username)
|
||||
|
||||
authors, abstract, file_name, file_size = get_draft_meta(form)
|
||||
|
||||
submission = get_submission(form)
|
||||
fill_in_submission(form, submission, authors, abstract, file_size)
|
||||
apply_checkers(submission, file_name)
|
||||
|
||||
create_submission_event(request, submission, desc="Uploaded submission")
|
||||
|
||||
errors = validate_submission(submission)
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
||||
errors = [ c.message for c in submission.checks.all() if not c.passed ]
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
||||
if not user.username in [ a['email'] for a in authors ]:
|
||||
raise ValidationError('Submitter %s is not one of the document authors' % user.username)
|
||||
|
||||
submission.submitter = user.person.formatted_email()
|
||||
docevent_from_submission(request, submission, desc="Uploaded new revision")
|
||||
|
||||
requires_group_approval = (submission.rev == '00' and submission.group and submission.group.type_id in ("wg", "rg", "ietf", "irtf", "iab", "iana", "rfcedtyp") and not Preapproval.objects.filter(name=submission.name).exists())
|
||||
requires_prev_authors_approval = Document.objects.filter(name=submission.name)
|
||||
|
||||
sent_to, desc, docDesc = send_confirmation_emails(request, submission, requires_group_approval, requires_prev_authors_approval)
|
||||
msg = u"Set submitter to \"%s\" and %s" % (submission.submitter, desc)
|
||||
create_submission_event(request, submission, msg)
|
||||
docevent_from_submission(request, submission, docDesc, who="(System)")
|
||||
|
||||
return HttpResponse(
|
||||
"Upload of %s OK, confirmation requests sent to:\n %s" % (submission.name, ',\n '.join(sent_to)),
|
||||
content_type="text/plain")
|
||||
else:
|
||||
raise ValidationError(form.errors)
|
||||
except IOError as e:
|
||||
return err(500, "IO Error: %s" % str(e))
|
||||
except ValidationError as e:
|
||||
return err(400, "Validation Error: %s" % str(e))
|
||||
except Exception as e:
|
||||
raise
|
||||
return err(500, "Exception: %s" % str(e))
|
||||
finally:
|
||||
if e and submission:
|
||||
remove_submission_files(submission)
|
||||
submission.delete()
|
||||
else:
|
||||
return err(405, "Method not allowed")
|
||||
|
||||
def note_well(request):
|
||||
return render(request, 'submit/note_well.html', {'selected': 'notewell'})
|
||||
|
||||
|
@ -252,6 +194,8 @@ def submission_status(request, submission_id, access_token=None):
|
|||
not key_matched
|
||||
and not is_secretariat
|
||||
and not submission.state_id in ("cancel", "posted") )
|
||||
|
||||
# Begin common code chunk
|
||||
addrs = gather_address_lists('sub_confirmation_requested',submission=submission)
|
||||
confirmation_list = addrs.to
|
||||
confirmation_list.extend(addrs.cc)
|
||||
|
@ -260,15 +204,10 @@ def submission_status(request, submission_id, access_token=None):
|
|||
|
||||
requires_prev_authors_approval = Document.objects.filter(name=submission.name)
|
||||
|
||||
group_authors_changed = False
|
||||
doc = submission.existing_document()
|
||||
if doc and doc.group:
|
||||
old_authors = [ author.person for author in doc.documentauthor_set.all() ]
|
||||
new_authors = [ get_person_from_name_email(author["name"], author.get("email")) for author in submission.authors ]
|
||||
group_authors_changed = set(old_authors)!=set(new_authors)
|
||||
|
||||
message = None
|
||||
|
||||
|
||||
|
||||
if submission.state_id == "cancel":
|
||||
message = ('error', 'This submission has been cancelled, modification is no longer possible.')
|
||||
elif submission.state_id == "auth":
|
||||
|
@ -309,34 +248,7 @@ def submission_status(request, submission_id, access_token=None):
|
|||
post_submission(request, submission, desc)
|
||||
create_submission_event(request, submission, desc)
|
||||
else:
|
||||
docevent_from_submission(request, submission, desc="Uploaded new revision")
|
||||
|
||||
if requires_group_approval:
|
||||
submission.state = DraftSubmissionStateName.objects.get(slug="grp-appr")
|
||||
submission.save()
|
||||
|
||||
sent_to = send_approval_request_to_group(request, submission)
|
||||
|
||||
desc = "sent approval email to group chairs: %s" % u", ".join(sent_to)
|
||||
docDesc = u"Request for posting approval emailed to group chairs: %s" % u", ".join(sent_to)
|
||||
|
||||
else:
|
||||
submission.auth_key = generate_random_key()
|
||||
if requires_prev_authors_approval:
|
||||
submission.state = DraftSubmissionStateName.objects.get(slug="aut-appr")
|
||||
else:
|
||||
submission.state = DraftSubmissionStateName.objects.get(slug="auth")
|
||||
submission.save()
|
||||
|
||||
sent_to = send_submission_confirmation(request, submission, chair_notice=group_authors_changed)
|
||||
|
||||
if submission.state_id == "aut-appr":
|
||||
desc = u"sent confirmation email to previous authors: %s" % u", ".join(sent_to)
|
||||
docDesc = "Request for posting confirmation emailed to previous authors: %s" % u", ".join(sent_to)
|
||||
else:
|
||||
desc = u"sent confirmation email to submitter and authors: %s" % u", ".join(sent_to)
|
||||
docDesc = "Request for posting confirmation emailed to submitter and authors: %s" % u", ".join(sent_to)
|
||||
|
||||
sent_to, desc, docDesc = send_confirmation_emails(request, submission, requires_group_approval, requires_prev_authors_approval)
|
||||
msg = u"Set submitter to \"%s\", replaces to %s and %s" % (
|
||||
submission.submitter,
|
||||
", ".join(prettify_std_name(r.name) for r in replaces) if replaces else "(none)",
|
||||
|
|
18
ietf/urls.py
18
ietf/urls.py
|
@ -12,18 +12,15 @@ from django.views.defaults import server_error
|
|||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf import api
|
||||
from ietf.doc import views_search
|
||||
from ietf.group.urls import group_urls, grouptype_urls, stream_urls
|
||||
from ietf.help import views as help_views
|
||||
from ietf.ipr.sitemaps import IPRMap
|
||||
from ietf.liaisons.sitemaps import LiaisonMap
|
||||
from ietf.meeting import views as meeting_views
|
||||
from ietf.utils.urls import url
|
||||
|
||||
|
||||
admin.autodiscover()
|
||||
api.autodiscover()
|
||||
|
||||
# sometimes, this code gets called more than once, which is an
|
||||
# that seems impossible to work around.
|
||||
|
@ -43,6 +40,7 @@ urlpatterns = [
|
|||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^admin/docs/', include('django.contrib.admindocs.urls')),
|
||||
url(r'^ann/', include('ietf.nomcom.redirect_ann_urls')),
|
||||
url(r'^api/', include('ietf.api.urls')),
|
||||
url(r'^community/', include('ietf.community.urls')),
|
||||
url(r'^accounts/settings/', include('ietf.cookies.urls')),
|
||||
url(r'^doc/', include('ietf.doc.urls')),
|
||||
|
@ -77,20 +75,6 @@ urlpatterns = [
|
|||
url(r'^googlea30ad1dacffb5e5b.html', TemplateView.as_view(template_name='googlea30ad1dacffb5e5b.html')),
|
||||
]
|
||||
|
||||
# Endpoints for Tastypie's REST API
|
||||
urlpatterns += [
|
||||
url(r'^api/v1/?$', api.top_level),
|
||||
]
|
||||
for n,a in api._api_list:
|
||||
urlpatterns += [
|
||||
url(r'^api/v1/', include(a.urls)),
|
||||
]
|
||||
|
||||
# Custom API endpoints
|
||||
urlpatterns += [
|
||||
url(r'^api/notify/meeting/import_recordings/(?P<number>[a-z0-9-]+)/?$', meeting_views.api_import_recordings),
|
||||
]
|
||||
|
||||
# This is needed to serve files during testing
|
||||
if settings.SERVER_MODE in ('development', 'test'):
|
||||
save_debug = settings.DEBUG
|
||||
|
|
Loading…
Reference in a new issue