to the draft parser (incorporating patch from trunk), store the extracted country instead of trying to turn it into an ISO country code, add country and continent name models and add initial data for those, add helper function for cleaning the countries, add author country and continent charts, move the affiliation models to stats/models.py, fix a bunch of bugs. - Legacy-Id: 12846
374 lines
16 KiB
Python
374 lines
16 KiB
Python
import datetime
|
|
import re
|
|
import os
|
|
|
|
from django import forms
|
|
|
|
from ietf.doc.models import Document, DocAlias, State
|
|
from ietf.name.models import IntendedStdLevelName, DocRelationshipName
|
|
from ietf.group.models import Group
|
|
from ietf.person.models import Person, Email
|
|
from ietf.person.fields import SearchableEmailField
|
|
from ietf.secr.groups.forms import get_person
|
|
|
|
|
|
# ---------------------------------------------
|
|
# Select Choices
|
|
# ---------------------------------------------
|
|
WITHDRAW_CHOICES = (('ietf','Withdraw by IETF'),('author','Withdraw by Author'))
|
|
|
|
# ---------------------------------------------
|
|
# Custom Fields
|
|
# ---------------------------------------------
|
|
class DocumentField(forms.FileField):
|
|
'''A validating document upload field'''
|
|
|
|
def __init__(self, unique=False, *args, **kwargs):
|
|
self.extension = kwargs.pop('extension')
|
|
self.filename = kwargs.pop('filename')
|
|
self.rev = kwargs.pop('rev')
|
|
super(DocumentField, self).__init__(*args, **kwargs)
|
|
|
|
def clean(self, data, initial=None):
|
|
file = super(DocumentField, self).clean(data,initial)
|
|
if file:
|
|
# validate general file format
|
|
m = re.search(r'.*-\d{2}\.(txt|pdf|ps|xml)', file.name)
|
|
if not m:
|
|
raise forms.ValidationError('File name must be in the form base-NN.[txt|pdf|ps|xml]')
|
|
|
|
# ensure file extension is correct
|
|
base,ext = os.path.splitext(file.name)
|
|
if ext != self.extension:
|
|
raise forms.ValidationError('Incorrect file extension: %s' % ext)
|
|
|
|
# if this isn't a brand new submission we need to do some extra validations
|
|
if self.filename:
|
|
# validate filename
|
|
if base[:-3] != self.filename:
|
|
raise forms.ValidationError, "Filename: %s doesn't match Draft filename." % base[:-3]
|
|
# validate revision
|
|
next_revision = str(int(self.rev)+1).zfill(2)
|
|
if base[-2:] != next_revision:
|
|
raise forms.ValidationError, "Expected revision # %s" % (next_revision)
|
|
|
|
return file
|
|
|
|
class GroupModelChoiceField(forms.ModelChoiceField):
|
|
'''
|
|
Custom ModelChoiceField sets queryset to include all active workgroups and the
|
|
individual submission group, none. Displays group acronyms as choices. Call it without the
|
|
queryset argument, for example:
|
|
|
|
group = GroupModelChoiceField(required=True)
|
|
'''
|
|
def __init__(self, *args, **kwargs):
|
|
kwargs['queryset'] = Group.objects.filter(type__in=('wg','individ'),state__in=('bof','proposed','active')).order_by('acronym')
|
|
super(GroupModelChoiceField, self).__init__(*args, **kwargs)
|
|
|
|
def label_from_instance(self, obj):
|
|
return obj.acronym
|
|
|
|
class AliasModelChoiceField(forms.ModelChoiceField):
|
|
'''
|
|
Custom ModelChoiceField, just uses Alias name in the select choices as opposed to the
|
|
more confusing alias -> doc format used by DocAlias.__unicode__
|
|
'''
|
|
def label_from_instance(self, obj):
|
|
return obj.name
|
|
|
|
# ---------------------------------------------
|
|
# Forms
|
|
# ---------------------------------------------
|
|
class AddModelForm(forms.ModelForm):
|
|
start_date = forms.DateField()
|
|
group = GroupModelChoiceField(required=True,help_text='Use group "none" for Individual Submissions.')
|
|
|
|
class Meta:
|
|
model = Document
|
|
fields = ('title','group','stream','start_date','pages','abstract','internal_comments')
|
|
|
|
# use this method to set attrs which keeps other meta info from model.
|
|
def __init__(self, *args, **kwargs):
|
|
super(AddModelForm, self).__init__(*args, **kwargs)
|
|
self.fields['title'].label='Document Name'
|
|
self.fields['title'].widget=forms.Textarea()
|
|
self.fields['start_date'].initial=datetime.date.today
|
|
self.fields['pages'].label='Number of Pages'
|
|
self.fields['internal_comments'].label='Comments'
|
|
|
|
class AuthorForm(forms.Form):
|
|
'''
|
|
The generic javascript for populating the email list based on the name selected expects to
|
|
see an id_email field
|
|
'''
|
|
person = forms.CharField(max_length=50,widget=forms.TextInput(attrs={'class':'name-autocomplete'}),help_text="To see a list of people type the first name, or last name, or both.")
|
|
email = forms.CharField(widget=forms.Select(),help_text="Select an email.")
|
|
affiliation = forms.CharField(max_length=100, required=False, help_text="Affiliation")
|
|
country = forms.CharField(max_length=255, required=False, help_text="Country")
|
|
|
|
# check for id within parenthesis to ensure name was selected from the list
|
|
def clean_person(self):
|
|
person = self.cleaned_data.get('person', '')
|
|
m = re.search(r'(\d+)', person)
|
|
if person and not m:
|
|
raise forms.ValidationError("You must select an entry from the list!")
|
|
|
|
# return person object
|
|
return get_person(person)
|
|
|
|
# check that email exists and return the Email object
|
|
def clean_email(self):
|
|
email = self.cleaned_data['email']
|
|
try:
|
|
obj = Email.objects.get(address=email)
|
|
except Email.ObjectDoesNoExist:
|
|
raise forms.ValidationError("Email address not found!")
|
|
|
|
# return email object
|
|
return obj
|
|
|
|
class EditModelForm(forms.ModelForm):
|
|
#expiration_date = forms.DateField(required=False)
|
|
state = forms.ModelChoiceField(queryset=State.objects.filter(type='draft'),empty_label=None)
|
|
iesg_state = forms.ModelChoiceField(queryset=State.objects.filter(type='draft-iesg'),required=False)
|
|
group = GroupModelChoiceField(required=True)
|
|
review_by_rfc_editor = forms.BooleanField(required=False)
|
|
shepherd = SearchableEmailField(required=False, only_users=True)
|
|
|
|
class Meta:
|
|
model = Document
|
|
fields = ('title','group','ad','shepherd','notify','stream','review_by_rfc_editor','name','rev','pages','intended_std_level','std_level','abstract','internal_comments')
|
|
|
|
# use this method to set attrs which keeps other meta info from model.
|
|
def __init__(self, *args, **kwargs):
|
|
super(EditModelForm, self).__init__(*args, **kwargs)
|
|
self.fields['ad'].queryset = Person.objects.filter(role__name='ad').distinct()
|
|
self.fields['title'].label='Document Name'
|
|
self.fields['title'].widget=forms.Textarea()
|
|
self.fields['rev'].widget.attrs['size'] = 2
|
|
self.fields['abstract'].widget.attrs['cols'] = 72
|
|
self.initial['state'] = self.instance.get_state().pk
|
|
if self.instance.get_state('draft-iesg'):
|
|
self.initial['iesg_state'] = self.instance.get_state('draft-iesg').pk
|
|
|
|
# setup special fields
|
|
if self.instance:
|
|
# setup replaced
|
|
self.fields['review_by_rfc_editor'].initial = bool(self.instance.tags.filter(slug='rfc-rev'))
|
|
|
|
def save(self, commit=False):
|
|
m = super(EditModelForm, self).save(commit=False)
|
|
state = self.cleaned_data['state']
|
|
iesg_state = self.cleaned_data['iesg_state']
|
|
|
|
if 'state' in self.changed_data:
|
|
m.set_state(state)
|
|
|
|
# note we're not sending notices here, is this desired
|
|
if 'iesg_state' in self.changed_data:
|
|
if iesg_state == None:
|
|
m.unset_state('draft-iesg')
|
|
else:
|
|
m.set_state(iesg_state)
|
|
|
|
if 'review_by_rfc_editor' in self.changed_data:
|
|
if self.cleaned_data.get('review_by_rfc_editor',''):
|
|
m.tags.add('rfc-rev')
|
|
else:
|
|
m.tags.remove('rfc-rev')
|
|
|
|
# handle replaced by
|
|
|
|
return m
|
|
|
|
# field must contain filename of existing draft
|
|
def clean_replaced_by(self):
|
|
name = self.cleaned_data.get('replaced_by', '')
|
|
if name and not Document.objects.filter(name=name):
|
|
raise forms.ValidationError("ERROR: Draft does not exist")
|
|
return name
|
|
|
|
def clean(self):
|
|
super(EditModelForm, self).clean()
|
|
cleaned_data = self.cleaned_data
|
|
"""
|
|
expiration_date = cleaned_data.get('expiration_date','')
|
|
status = cleaned_data.get('status','')
|
|
replaced = cleaned_data.get('replaced',False)
|
|
replaced_by = cleaned_data.get('replaced_by','')
|
|
replaced_status_object = IDStatus.objects.get(status_id=5)
|
|
expired_status_object = IDStatus.objects.get(status_id=2)
|
|
# this condition seems to be valid
|
|
#if expiration_date and status != expired_status_object:
|
|
# raise forms.ValidationError('Expiration Date set but status is %s' % (status))
|
|
if status == expired_status_object and not expiration_date:
|
|
raise forms.ValidationError('Status is Expired but Expirated Date is not set')
|
|
if replaced and status != replaced_status_object:
|
|
raise forms.ValidationError('You have checked Replaced but status is %s' % (status))
|
|
if replaced and not replaced_by:
|
|
raise forms.ValidationError('You have checked Replaced but Replaced By field is empty')
|
|
"""
|
|
return cleaned_data
|
|
|
|
class EmailForm(forms.Form):
|
|
# max_lengths come from db limits, cc is not limited
|
|
to = forms.CharField(max_length=255)
|
|
cc = forms.CharField(required=False)
|
|
subject = forms.CharField(max_length=255)
|
|
body = forms.CharField(widget=forms.Textarea())
|
|
|
|
class ExtendForm(forms.Form):
|
|
expiration_date = forms.DateField()
|
|
|
|
class ReplaceForm(forms.Form):
|
|
replaced = AliasModelChoiceField(DocAlias.objects.none(),empty_label=None,help_text='This document may have more than one alias. Be sure to select the correct alias to replace.')
|
|
replaced_by = forms.CharField(max_length=100,help_text='Enter the filename of the Draft which replaces this one.')
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.draft = kwargs.pop('draft')
|
|
super(ReplaceForm, self).__init__(*args, **kwargs)
|
|
self.fields['replaced'].queryset = DocAlias.objects.filter(document=self.draft)
|
|
|
|
# field must contain filename of existing draft
|
|
def clean_replaced_by(self):
|
|
name = self.cleaned_data.get('replaced_by', '')
|
|
try:
|
|
doc = Document.objects.get(name=name)
|
|
except Document.DoesNotExist:
|
|
raise forms.ValidationError("ERROR: Draft does not exist: %s" % name)
|
|
if name == self.draft.name:
|
|
raise forms.ValidationError("ERROR: A draft can't replace itself")
|
|
return doc
|
|
|
|
class BaseRevisionModelForm(forms.ModelForm):
|
|
class Meta:
|
|
model = Document
|
|
fields = ('title','pages','abstract')
|
|
|
|
class RevisionModelForm(forms.ModelForm):
|
|
class Meta:
|
|
model = Document
|
|
fields = ('title','pages','abstract')
|
|
|
|
# use this method to set attrs which keeps other meta info from model.
|
|
def __init__(self, *args, **kwargs):
|
|
super(RevisionModelForm, self).__init__(*args, **kwargs)
|
|
self.fields['title'].label='Document Name'
|
|
self.fields['title'].widget=forms.Textarea()
|
|
self.fields['pages'].label='Number of Pages'
|
|
|
|
class RfcModelForm(forms.ModelForm):
|
|
rfc_number = forms.IntegerField()
|
|
rfc_published_date = forms.DateField(initial=datetime.datetime.now)
|
|
group = GroupModelChoiceField(required=True)
|
|
|
|
class Meta:
|
|
model = Document
|
|
fields = ('title','group','pages','std_level','internal_comments')
|
|
|
|
# use this method to set attrs which keeps other meta info from model.
|
|
def __init__(self, *args, **kwargs):
|
|
super(RfcModelForm, self).__init__(*args, **kwargs)
|
|
self.fields['title'].widget = forms.Textarea()
|
|
self.fields['std_level'].required = True
|
|
|
|
def save(self, force_insert=False, force_update=False, commit=False):
|
|
obj = super(RfcModelForm, self).save(commit=False)
|
|
|
|
# create DocAlias
|
|
DocAlias.objects.create(document=self.instance,name="rfc%d" % self.cleaned_data['rfc_number'])
|
|
|
|
return obj
|
|
|
|
def clean_rfc_number(self):
|
|
rfc_number = self.cleaned_data['rfc_number']
|
|
if DocAlias.objects.filter(name='rfc' + str(rfc_number)):
|
|
raise forms.ValidationError("RFC %d already exists" % rfc_number)
|
|
return rfc_number
|
|
|
|
class RfcObsoletesForm(forms.Form):
|
|
relation = forms.ModelChoiceField(queryset=DocRelationshipName.objects.filter(slug__in=('updates','obs')),required=False)
|
|
rfc = forms.IntegerField(required=False)
|
|
|
|
# ensure that RFC exists
|
|
def clean_rfc(self):
|
|
rfc = self.cleaned_data.get('rfc','')
|
|
if rfc:
|
|
if not Document.objects.filter(docalias__name="rfc%s" % rfc):
|
|
raise forms.ValidationError("RFC does not exist")
|
|
return rfc
|
|
|
|
def clean(self):
|
|
super(RfcObsoletesForm, self).clean()
|
|
cleaned_data = self.cleaned_data
|
|
relation = cleaned_data.get('relation','')
|
|
rfc = cleaned_data.get('rfc','')
|
|
if (relation and not rfc) or (rfc and not relation):
|
|
raise forms.ValidationError('You must select a relation and enter RFC #')
|
|
return cleaned_data
|
|
|
|
class SearchForm(forms.Form):
|
|
intended_std_level = forms.ModelChoiceField(queryset=IntendedStdLevelName.objects,label="Intended Status",required=False)
|
|
document_title = forms.CharField(max_length=80,label='Document Title',required=False)
|
|
group = forms.CharField(max_length=12,required=False)
|
|
filename = forms.CharField(max_length=80,required=False)
|
|
state = forms.ModelChoiceField(queryset=State.objects.filter(type='draft'),required=False)
|
|
revision_date_start = forms.DateField(label='Revision Date (start)',required=False)
|
|
revision_date_end = forms.DateField(label='Revision Date (end)',required=False)
|
|
|
|
class UploadForm(forms.Form):
|
|
txt = DocumentField(label=u'.txt format', required=True,extension='.txt',filename=None,rev=None)
|
|
xml = DocumentField(label=u'.xml format', required=False,extension='.xml',filename=None,rev=None)
|
|
pdf = DocumentField(label=u'.pdf format', required=False,extension='.pdf',filename=None,rev=None)
|
|
ps = DocumentField(label=u'.ps format', required=False,extension='.ps',filename=None,rev=None)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
if 'draft' in kwargs:
|
|
self.draft = kwargs.pop('draft')
|
|
else:
|
|
self.draft = None
|
|
super(UploadForm, self).__init__(*args, **kwargs)
|
|
if self.draft:
|
|
for field in self.fields.itervalues():
|
|
field.filename = self.draft.name
|
|
field.rev = self.draft.rev
|
|
|
|
|
|
def clean(self):
|
|
# Checks that all files have the same base
|
|
if any(self.errors):
|
|
# Don't bother validating unless each field is valid on its own
|
|
return
|
|
txt = self.cleaned_data['txt']
|
|
xml = self.cleaned_data['xml']
|
|
pdf = self.cleaned_data['pdf']
|
|
ps = self.cleaned_data['ps']
|
|
|
|
# we only need to do these validations for new drafts
|
|
if not self.draft:
|
|
names = []
|
|
for file in (txt,xml,pdf,ps):
|
|
if file:
|
|
base = os.path.splitext(file.name)[0]
|
|
if base not in names:
|
|
names.append(base)
|
|
|
|
if len(names) > 1:
|
|
raise forms.ValidationError, "All files must have the same base name"
|
|
|
|
# ensure that the basename is unique
|
|
base = os.path.splitext(txt.name)[0]
|
|
if Document.objects.filter(name=base[:-3]):
|
|
raise forms.ValidationError, "This document filename already exists: %s" % base[:-3]
|
|
|
|
# ensure that rev is 00
|
|
if base[-2:] != '00':
|
|
raise forms.ValidationError, "New Drafts must start with 00 revision number."
|
|
|
|
return self.cleaned_data
|
|
|
|
class WithdrawForm(forms.Form):
|
|
type = forms.CharField(widget=forms.Select(choices=WITHDRAW_CHOICES),help_text='Select which type of withdraw to perform.')
|
|
|