467 lines
22 KiB
Python
467 lines
22 KiB
Python
import sha
|
|
import random
|
|
import os
|
|
import subprocess
|
|
import datetime
|
|
|
|
from django import forms
|
|
from django.forms.fields import email_re
|
|
from django.conf import settings
|
|
from django.contrib.sites.models import Site
|
|
from django.template.loader import render_to_string
|
|
from django.utils.html import mark_safe
|
|
|
|
from ietf.idtracker.models import InternetDraft, IETFWG
|
|
from ietf.proceedings.models import Meeting
|
|
from ietf.submit.models import IdSubmissionDetail, TempIdAuthors
|
|
from ietf.submit.utils import MANUAL_POST_REQUESTED, NONE_WG, UPLOADED, WAITING_AUTHENTICATION
|
|
from ietf.submit.parsers.pdf_parser import PDFParser
|
|
from ietf.submit.parsers.plain_parser import PlainParser
|
|
from ietf.submit.parsers.ps_parser import PSParser
|
|
from ietf.submit.parsers.xml_parser import XMLParser
|
|
from ietf.utils.mail import send_mail
|
|
from ietf.utils.draft import Draft
|
|
|
|
|
|
CUTOFF_HOUR = 17
|
|
|
|
|
|
class UploadForm(forms.Form):
|
|
|
|
txt = forms.FileField(label=u'.txt format', required=True)
|
|
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)
|
|
|
|
fieldsets = [('Upload a draft', ('txt', 'xml', 'pdf', 'ps'))]
|
|
|
|
class Media:
|
|
css = {'all': ("/css/liaisons.css", )}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.request=kwargs.pop('request', None)
|
|
self.remote_ip=self.request.META.get('REMOTE_ADDR', None)
|
|
super(UploadForm, self).__init__(*args, **kwargs)
|
|
self.in_first_cut_off = False
|
|
self.idnits_message = None
|
|
self.shutdown = False
|
|
self.draft = None
|
|
self.filesize = None
|
|
self.group = None
|
|
self.file_type = []
|
|
self.read_dates()
|
|
|
|
def read_dates(self):
|
|
now = datetime.datetime.utcnow()
|
|
first_cut_off = Meeting.get_first_cut_off()
|
|
second_cut_off = Meeting.get_second_cut_off()
|
|
ietf_monday = Meeting.get_ietf_monday()
|
|
|
|
if now.date() >= first_cut_off and now.date() < second_cut_off: # We are in the first_cut_off
|
|
if now.date() == first_cut_off and now.hour < CUTOFF_HOUR:
|
|
self.cutoff_warning = 'The pre-meeting cutoff date for new documents (i.e., version -00 Internet-Drafts) is %s at 5 PM (PT). You will not be able to submit a new document after this time until %s, at midnight' % (first_cut_off, ietf_monday)
|
|
else: # No 00 version allowed
|
|
self.cutoff_warning = 'The pre-meeting cutoff date for new documents (i.e., version -00 Internet-Drafts) was %s at 5 PM (PT). You will not be able to submit a new document until %s, at midnight.<br>You can still submit a version -01 or higher Internet-Draft until 5 PM (PT), %s' % (first_cut_off, ietf_monday, second_cut_off)
|
|
self.in_first_cut_off = True
|
|
elif now.date() >= second_cut_off and now.date() < ietf_monday:
|
|
if now.date() == second_cut_off and now.hour < CUTOFF_HOUR: # We are in the first_cut_off yet
|
|
self.cutoff_warning = 'The pre-meeting cutoff date for new documents (i.e., version -00 Internet-Drafts) was %s at 5 PM (PT). You will not be able to submit a new document until %s, at midnight.<br>The I-D submission tool will be shut down at 5 PM (PT) today, and reopened at midnight (PT), %s' % (first_cut_off, ietf_monday, ietf_monday)
|
|
self.in_first_cut_off = True
|
|
else: # Completely shut down of the tool
|
|
self.cutoff_warning = 'The cut off time for the I-D submission was 5 PM (PT), %s.<br>The I-D submission tool will be reopened at midnight, %s' % (second_cut_off, ietf_monday)
|
|
self.shutdown = True
|
|
|
|
def __unicode__(self):
|
|
return self.as_div()
|
|
|
|
def as_div(self):
|
|
return render_to_string('submit/submitform.html', {'form': self})
|
|
|
|
def get_fieldsets(self):
|
|
if not self.fieldsets:
|
|
yield dict(name=None, fields=self)
|
|
else:
|
|
for fieldset, fields in self.fieldsets:
|
|
fieldset_dict = dict(name=fieldset, fields=[])
|
|
for field_name in fields:
|
|
if field_name in self.fields.keyOrder:
|
|
fieldset_dict['fields'].append(self[field_name])
|
|
if not fieldset_dict['fields']:
|
|
# if there is no fields in this fieldset, we continue to next fieldset
|
|
continue
|
|
yield fieldset_dict
|
|
|
|
def clean_txt(self):
|
|
txt_file = self.cleaned_data['txt']
|
|
if not txt_file:
|
|
return txt_file
|
|
parsed_info = PlainParser(txt_file).critical_parse()
|
|
if parsed_info.errors:
|
|
raise forms.ValidationError(parsed_info.errors)
|
|
self.filesize=txt_file.size
|
|
return txt_file
|
|
|
|
def clean_pdf(self):
|
|
pdf_file = self.cleaned_data['pdf']
|
|
if not pdf_file:
|
|
return pdf_file
|
|
parsed_info = PDFParser(pdf_file).critical_parse()
|
|
if parsed_info.errors:
|
|
raise forms.ValidationError(parsed_info.errors)
|
|
return pdf_file
|
|
|
|
def clean_ps(self):
|
|
ps_file = self.cleaned_data['ps']
|
|
if not ps_file:
|
|
return ps_file
|
|
parsed_info = PSParser(ps_file).critical_parse()
|
|
if parsed_info.errors:
|
|
raise forms.ValidationError(parsed_info.errors)
|
|
return ps_file
|
|
|
|
def clean_xml(self):
|
|
xml_file = self.cleaned_data['xml']
|
|
if not xml_file:
|
|
return xml_file
|
|
parsed_info = XMLParser(xml_file).critical_parse()
|
|
if parsed_info.errors:
|
|
raise forms.ValidationError(parsed_info.errors)
|
|
return xml_file
|
|
|
|
def clean(self):
|
|
if self.shutdown:
|
|
raise forms.ValidationError('The tool is shut down')
|
|
self.check_paths()
|
|
if self.cleaned_data.get('txt', None):
|
|
self.get_draft()
|
|
self.group=self.get_working_group()
|
|
self.check_previous_submission()
|
|
if self.draft.revision == '00' and self.in_first_cut_off:
|
|
raise forms.ValidationError(mark_safe(self.cutoff_warning))
|
|
self.check_tresholds()
|
|
return super(UploadForm, self).clean()
|
|
|
|
def check_tresholds(self):
|
|
filename = self.draft.filename
|
|
revision = self.draft.revision
|
|
remote_ip = self.remote_ip
|
|
today = datetime.date.today()
|
|
|
|
# Same draft by name
|
|
same_name = IdSubmissionDetail.objects.filter(filename=filename, revision=revision, submission_date=today)
|
|
if same_name.count() > settings.MAX_SAME_DRAFT_NAME:
|
|
raise forms.ValidationError('A same I-D cannot be submitted more than %s times a day' % settings.MAX_SAME_DRAFT_NAME)
|
|
if sum([i.filesize for i in same_name]) > (settings.MAX_SAME_DRAFT_NAME_SIZE * 1048576):
|
|
raise forms.ValidationError('A same I-D submission cannot exceed more than %s MByte a day' % settings.MAX_SAME_DRAFT_NAME_SIZE)
|
|
|
|
# Total from same ip
|
|
same_ip = IdSubmissionDetail.objects.filter(remote_ip=remote_ip, submission_date=today)
|
|
if same_ip.count() > settings.MAX_SAME_SUBMITTER:
|
|
raise forms.ValidationError('The same submitter cannot submit more than %s I-Ds a day' % settings.MAX_SAME_SUBMITTER)
|
|
if sum([i.filesize for i in same_ip]) > (settings.MAX_SAME_SUBMITTER_SIZE * 1048576):
|
|
raise forms.ValidationError('The same submitter cannot exceed more than %s MByte a day' % settings.MAX_SAME_SUBMITTER_SIZE)
|
|
|
|
# Total in same group
|
|
if self.group:
|
|
same_group = IdSubmissionDetail.objects.filter(group_acronym=self.group, submission_date=today)
|
|
if same_group.count() > settings.MAX_SAME_WG_DRAFT:
|
|
raise forms.ValidationError('A same working group I-Ds cannot be submitted more than %s times a day' % settings.MAX_SAME_WG_DRAFT)
|
|
if sum([i.filesize for i in same_group]) > (settings.MAX_SAME_WG_DRAFT_SIZE * 1048576):
|
|
raise forms.ValidationError('Total size of same working group I-Ds cannot exceed %s MByte a day' % settings.MAX_SAME_WG_DRAFT_SIZE)
|
|
|
|
|
|
# Total drafts for today
|
|
total_today = IdSubmissionDetail.objects.filter(submission_date=today)
|
|
if total_today.count() > settings.MAX_DAILY_SUBMISSION:
|
|
raise forms.ValidationError('The total number of today\'s submission has reached the maximum number of submission per day')
|
|
if sum([i.filesize for i in total_today]) > (settings.MAX_DAILY_SUBMISSION_SIZE * 1048576):
|
|
raise forms.ValidationError('The total size of today\'s submission has reached the maximum size of submission per day')
|
|
|
|
def check_paths(self):
|
|
self.staging_path = getattr(settings, 'STAGING_PATH', None)
|
|
self.idnits = getattr(settings, 'IDNITS_PATH', None)
|
|
if not self.staging_path:
|
|
raise forms.ValidationError('STAGING_PATH not defined on settings.py')
|
|
if not os.path.exists(self.staging_path):
|
|
raise forms.ValidationError('STAGING_PATH defined on settings.py does not exist')
|
|
if not self.idnits:
|
|
raise forms.ValidationError('IDNITS_PATH not defined on settings.py')
|
|
if not os.path.exists(self.idnits):
|
|
raise forms.ValidationError('IDNITS_PATH defined on settings.py does not exist')
|
|
|
|
def check_previous_submission(self):
|
|
filename = self.draft.filename
|
|
revision = self.draft.revision
|
|
existing = IdSubmissionDetail.objects.filter(filename=filename, revision=revision,
|
|
status__pk__gte=0, status__pk__lt=100)
|
|
if existing:
|
|
raise forms.ValidationError(mark_safe('Duplicate Internet-Draft submission is currently in process. <a href="/submit/status/%s/">Check it here</a>' % existing[0].pk))
|
|
|
|
def get_draft(self):
|
|
if self.draft:
|
|
return self.draft
|
|
txt_file = self.cleaned_data['txt']
|
|
txt_file.seek(0)
|
|
self.draft = Draft(txt_file.read())
|
|
txt_file.seek(0)
|
|
return self.draft
|
|
|
|
def save(self):
|
|
for ext in ['txt', 'pdf', 'xml', 'ps']:
|
|
fd = self.cleaned_data[ext]
|
|
if not fd:
|
|
continue
|
|
self.file_type.append('.%s' % ext)
|
|
filename = os.path.join(self.staging_path, '%s-%s.%s' % (self.draft.filename, self.draft.revision, ext))
|
|
destination = open(filename, 'wb+')
|
|
for chunk in fd.chunks():
|
|
destination.write(chunk)
|
|
destination.close()
|
|
self.check_idnits()
|
|
return self.save_draft_info(self.draft)
|
|
|
|
def check_idnits(self):
|
|
filepath = os.path.join(self.staging_path, '%s-%s.txt' % (self.draft.filename, self.draft.revision))
|
|
p = subprocess.Popen([self.idnits, '--submitcheck', '--nitcount', filepath], stdout=subprocess.PIPE)
|
|
self.idnits_message = p.stdout.read()
|
|
|
|
def get_working_group(self):
|
|
filename = self.draft.filename
|
|
existing_draft = InternetDraft.objects.filter(filename=filename)
|
|
if existing_draft:
|
|
group = existing_draft[0].group and existing_draft[0].group.ietfwg or None
|
|
if group and group.pk != NONE_WG:
|
|
return group
|
|
else:
|
|
return None
|
|
else:
|
|
if filename.startswith('draft-ietf-'):
|
|
# Extra check for WG that contains dashes
|
|
for group in IETFWG.objects.filter(group_acronym__acronym__contains='-'):
|
|
if filename.startswith('draft-ietf-%s-' % group.group_acronym.acronym):
|
|
return group
|
|
group_acronym = filename.split('-')[2]
|
|
try:
|
|
return IETFWG.objects.get(group_acronym__acronym=group_acronym)
|
|
except IETFWG.DoesNotExist:
|
|
raise forms.ValidationError('There is no active group with acronym \'%s\', please rename your draft' % group_acronym)
|
|
else:
|
|
return None
|
|
|
|
def save_draft_info(self, draft):
|
|
document_id = 0
|
|
existing_draft = InternetDraft.objects.filter(filename=draft.filename)
|
|
if existing_draft:
|
|
document_id = existing_draft[0].id_document_tag
|
|
detail = IdSubmissionDetail.objects.create(
|
|
id_document_name=draft.get_title(),
|
|
filename=draft.filename,
|
|
revision=draft.revision,
|
|
txt_page_count=draft.get_pagecount(),
|
|
filesize=self.filesize,
|
|
creation_date=draft.get_creation_date(),
|
|
submission_date=datetime.date.today(),
|
|
idnits_message=self.idnits_message,
|
|
temp_id_document_tag=document_id,
|
|
group_acronym=self.group,
|
|
remote_ip=self.remote_ip,
|
|
first_two_pages=''.join(draft.pages[:2]),
|
|
status_id=UPLOADED,
|
|
abstract=draft.get_abstract(),
|
|
file_type=','.join(self.file_type),
|
|
)
|
|
order = 0
|
|
for author in draft.get_author_info():
|
|
full_name, first_name, middle_initial, last_name, name_suffix, email = author
|
|
order += 1
|
|
TempIdAuthors.objects.create(
|
|
id_document_tag=document_id,
|
|
first_name=first_name,
|
|
middle_initial=middle_initial,
|
|
last_name=last_name,
|
|
name_suffix=name_suffix,
|
|
email_address=email,
|
|
author_order=order,
|
|
submission=detail)
|
|
return detail
|
|
|
|
|
|
class AutoPostForm(forms.Form):
|
|
|
|
first_name = forms.CharField(label=u'Given name', required=True)
|
|
last_name = forms.CharField(label=u'Last name', required=True)
|
|
email = forms.EmailField(label=u'Email address', required=True)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.draft = kwargs.pop('draft', None)
|
|
self.validation = kwargs.pop('validation', None)
|
|
super(AutoPostForm, self).__init__(*args, **kwargs)
|
|
|
|
def get_author_buttons(self):
|
|
button_template = '<input type="button" onclick="jQuery(\'#id_first_name\').val(\'%(first_name)s\');jQuery(\'#id_last_name\').val(\'%(last_name)s\');jQuery(\'#id_email\').val(\'%(email)s\');" value="%(full_name)s" />'
|
|
buttons = []
|
|
for i in self.validation.authors:
|
|
full_name = '%s. %s' % (i.first_name[0], i.last_name)
|
|
buttons.append(button_template % {'first_name': i.first_name,
|
|
'last_name': i.last_name,
|
|
'email': i.email()[1] or '',
|
|
'full_name': full_name})
|
|
return ''.join(buttons)
|
|
|
|
def save(self, request):
|
|
self.save_submitter_info()
|
|
self.save_new_draft_info()
|
|
self.send_confirmation_mail(request)
|
|
|
|
def send_confirmation_mail(self, request):
|
|
subject = 'Confirmation for Auto-Post of I-D %s' % self.draft.filename
|
|
from_email = settings.IDST_FROM_EMAIL
|
|
to_email = self.cleaned_data['email']
|
|
send_mail(request, to_email, from_email, subject, 'submit/confirm_autopost.txt',
|
|
{'draft': self.draft, 'domain': Site.objects.get_current().domain })
|
|
|
|
def save_submitter_info(self):
|
|
return TempIdAuthors.objects.create(
|
|
id_document_tag=self.draft.temp_id_document_tag,
|
|
first_name=self.cleaned_data['first_name'],
|
|
last_name=self.cleaned_data['last_name'],
|
|
email_address=self.cleaned_data['email'],
|
|
author_order=0,
|
|
submission=self.draft)
|
|
|
|
def save_new_draft_info(self):
|
|
salt = sha.new(str(random.random())).hexdigest()[:5]
|
|
self.draft.auth_key = sha.new(salt+self.cleaned_data['email']).hexdigest()
|
|
self.draft.status_id = WAITING_AUTHENTICATION
|
|
self.draft.save()
|
|
|
|
|
|
class MetaDataForm(AutoPostForm):
|
|
|
|
title = forms.CharField(label=u'Title', required=True)
|
|
version = forms.CharField(label=u'Version', required=True)
|
|
creation_date = forms.DateField(label=u'Creation date', required=True)
|
|
pages = forms.IntegerField(label=u'Pages', required=True)
|
|
abstract = forms.CharField(label=u'Abstract', widget=forms.Textarea, required=True)
|
|
first_name = forms.CharField(label=u'Given name', required=True)
|
|
last_name = forms.CharField(label=u'Last name', required=True)
|
|
email = forms.EmailField(label=u'Email address', required=True)
|
|
comments = forms.CharField(label=u'Comments to the secretariat', widget=forms.Textarea, required=False)
|
|
fields = ['title', 'version', 'creation_date', 'pages', 'abstract', 'first_name', 'last_name', 'email', 'comments']
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(MetaDataForm, self).__init__(*args, **kwargs)
|
|
self.set_initials()
|
|
self.authors = self.get_initial_authors()
|
|
|
|
def get_initial_authors(self):
|
|
authors=[]
|
|
if self.is_bound:
|
|
for key, value in self.data.items():
|
|
if key.startswith('first_name_'):
|
|
author = {'errors': {}}
|
|
index = key.replace('first_name_', '')
|
|
first_name = value.strip()
|
|
if not first_name:
|
|
author['errors']['first_name'] = 'This field is required'
|
|
last_name = self.data.get('last_name_%s' % index, '').strip()
|
|
if not last_name:
|
|
author['errors']['last_name'] = 'This field is required'
|
|
email = self.data.get('email_%s' % index, '').strip()
|
|
if email and not email_re.search(email):
|
|
author['errors']['email'] = 'Enter a valid e-mail address'
|
|
if first_name or last_name or email:
|
|
author.update({'first_name': first_name,
|
|
'last_name': last_name,
|
|
'email': ('%s %s' % (first_name, last_name), email),
|
|
'index': index,
|
|
})
|
|
authors.append(author)
|
|
authors.sort(lambda x,y: cmp(int(x['index']), int(y['index'])))
|
|
return authors
|
|
|
|
def set_initials(self):
|
|
self.fields['pages'].initial=self.draft.txt_page_count
|
|
self.fields['creation_date'].initial=self.draft.creation_date
|
|
self.fields['version'].initial=self.draft.revision
|
|
self.fields['abstract'].initial=self.draft.abstract
|
|
self.fields['title'].initial=self.draft.id_document_name
|
|
|
|
def clean_creation_date(self):
|
|
creation_date = self.cleaned_data.get('creation_date', None)
|
|
if not creation_date:
|
|
return None
|
|
submit_date = self.draft.submission_date
|
|
if (creation_date + datetime.timedelta(days=3) < submit_date or
|
|
creation_date - datetime.timedelta(days=3) > submit_date):
|
|
raise forms.ValidationError('Creation Date must be within 3 days of submission date')
|
|
return creation_date
|
|
|
|
def clean_version(self):
|
|
version = self.cleaned_data.get('version', None)
|
|
if not version:
|
|
return None
|
|
if len(version) > 2:
|
|
raise forms.ValidationError('Version field is not in NN format')
|
|
try:
|
|
version_int = int(version)
|
|
except ValueError:
|
|
raise forms.ValidationError('Version field is not in NN format')
|
|
if version_int > 99 or version_int < 0:
|
|
raise forms.ValidationError('Version must be set between 00 and 99')
|
|
existing_revisions = [int(i.revision_display()) for i in InternetDraft.objects.filter(filename=self.draft.filename)]
|
|
expected = 0
|
|
if existing_revisions:
|
|
expected = max(existing_revisions) + 1
|
|
if version_int != expected:
|
|
raise forms.ValidationError('Invalid Version Number (Version %02d is expected)' % expected)
|
|
return version
|
|
|
|
def clean(self):
|
|
if bool([i for i in self.authors if i['errors']]):
|
|
raise forms.ValidationError('Please fix errors in author list')
|
|
return super(MetaDataForm, self).clean()
|
|
|
|
def get_authors(self):
|
|
if not self.is_bound:
|
|
return self.validation.get_authors()
|
|
else:
|
|
return self.authors
|
|
|
|
def move_docs(self, draft, revision):
|
|
old_revision = draft.revision
|
|
for ext in draft.file_type.split(','):
|
|
source = os.path.join(settings.STAGING_PATH, '%s-%s%s' % (draft.filename, old_revision, ext))
|
|
dest = os.path.join(settings.STAGING_PATH, '%s-%s%s' % (draft.filename, revision, ext))
|
|
os.rename(source, dest)
|
|
|
|
def save_new_draft_info(self):
|
|
draft = self.draft
|
|
draft.id_documen_name = self.cleaned_data['title']
|
|
if draft.revision != self.cleaned_data['version']:
|
|
self.move_docs(draft, self.cleaned_data['version'])
|
|
draft.revision = self.cleaned_data['version']
|
|
draft.creation_date = self.cleaned_data['creation_date']
|
|
draft.txt_page_count = self.cleaned_data['pages']
|
|
draft.abstract = self.cleaned_data['abstract']
|
|
draft.comment_to_sec = self.cleaned_data['comments']
|
|
draft.status_id = MANUAL_POST_REQUESTED
|
|
draft.save()
|
|
self.save_submitter_info()
|
|
|
|
def save(self, request):
|
|
self.save_new_draft_info()
|
|
self.send_mail_to_secretariat(request)
|
|
|
|
def send_mail_to_secretariat(self, request):
|
|
subject = 'Manual Post Requested for %s' % self.draft.filename
|
|
from_email = settings.IDST_FROM_EMAIL
|
|
to_email = settings.IDST_TO_EMAIL
|
|
cc = [self.cleaned_data['email']]
|
|
cc += [i['email'][1] for i in self.authors]
|
|
if self.draft.group_acronym:
|
|
cc += [i.person.email()[1] for i in self.draft.group_acronym.wgchair_set.all()]
|
|
cc = list(set(cc))
|
|
send_mail(request, to_email, from_email, subject, 'submit/manual_post_mail.txt',
|
|
{'form': self, 'draft': self.draft, 'domain': Site.objects.get_current().domain }, cc=cc)
|