datatracker/ietf/secr/drafts/forms.py
Ole Laursen b2ff10b0f2 Add support for extracting the country line from the author addresses
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
2017-02-15 18:43:57 +00:00

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.')