Source code for annotations.forms

from django.contrib.auth.forms import UserChangeForm
from annotations.models import *
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from django import forms
from django.forms import widgets, BaseFormSet
from crispy_forms.helper import FormHelper
from django.db.models import Count
from django.db.utils import ProgrammingError

from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField

import autocomplete_light

import networkx as nx


[docs]class RegistrationForm(forms.Form): """ Gives user form of signup and validates it. """ full_name = forms.CharField(required=True, max_length=30, label=_("Full name")) username = forms.RegexField(regex=r'^\w+$', widget=forms.TextInput(attrs=dict(required=True, max_length=30)), label=_("Username"), error_messages={ 'invalid': _("This value must contain only letters, " "numbers and underscores.") }) email = forms.EmailField(widget=forms.TextInput(attrs=dict(required=True, max_length=30)), label=_("Email address")) password1 = forms.CharField(widget=forms.PasswordInput(attrs=dict(required=True, max_length=30, render_value=False)), label=_("Password")) password2 = forms.CharField(widget=forms.PasswordInput(attrs=dict(required=True, max_length=30, render_value=False)), label=_("Password (again)")) affiliation = forms.CharField(required=True, max_length=30, label=_("Affliation")) location = forms.CharField(required=True, max_length=30, label=_("Location")) link = forms.URLField(required=True, max_length=500, label=_("Link"))
[docs] def clean_username(self): """ Validates username. """ try: user = VogonUser.objects.get(username__iexact=self.cleaned_data['username']) except VogonUser.DoesNotExist: return self.cleaned_data['username'] raise forms.ValidationError(_("The username already exists. Please try another one."))
[docs] def clean(self): """ Validates the values inserted in Password and Confirm Password field are same. """ if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data: if self.cleaned_data['password1'] != self.cleaned_data['password2']: raise forms.ValidationError(_("The two password fields did not match."))
[docs]def validatefiletype(file): """ Validates type of uploaded file. Parameters ---------- file : file The file that is uploaded. Raises ------ ValidationError Raises this exception if uploaded file is neither plain text nor PDF """ # TODO: add PDF back in once we have a better system in place for processing # large uploads. if file.content_type != 'text/plain': # file.content_type != 'application/pdf' and raise ValidationError('Please choose a plain text file')
[docs]class UploadFileForm(forms.Form): title = forms.CharField(max_length=255, required=True, help_text='The title of the original resource.') uri = forms.CharField(label='URI', max_length=255, required=True, help_text='"URI" stands for Uniform Resource Identifier. This can be a DOI, a permalink to a digital archive (e.g. JSTOR), or any other unique identifier.') ispublic = forms.BooleanField(label='Make this text public', required=False, help_text='By checking this box you affirm that you have the right to make this file publicly available.') filetoupload = forms.FileField(label='Choose a plain text file:', required=True, validators=[validatefiletype], help_text="Soon you'll be able to upload images, PDFs, and HTML documents!") datecreated = forms.DateField(label='Date created:', required=False, widget=forms.TextInput(attrs={'class':'datepicker'}), help_text='The date that the original resource was published.') project = forms.ModelChoiceField(queryset=TextCollection.objects.all(), required=False, label='Add to project', help_text='You can add this text to a project that you own.')
[docs]class UserCreationForm(forms.ModelForm): password1 = forms.CharField(label='Password', widget=forms.PasswordInput) password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
[docs] class Meta: model = VogonUser fields = ('full_name', 'email', 'affiliation', 'location', 'link', 'imagefile')
[docs] def clean_password2(self): # Check that the two password entries match password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") if password1 and password2 and password1 != password2: raise forms.ValidationError("Passwords don't match") return password2
[docs] def save(self, commit=True): # Save the provided password in hashed format user = super(UserCreationForm, self).save(commit=False) user.set_password(self.cleaned_data["password1"]) if commit: user.save() return user
[docs]class UserChangeForm(forms.ModelForm):
[docs] class Meta: model = VogonUser fields = ('full_name', 'email', 'affiliation', 'location', 'imagefile', 'link')
[docs] def clean_password(self): # Regardless of what the user provides, return the initial value. # This is done here, rather than on the field, because the # field does not have access to the initial value return self.initial.get("password")
[docs]class AutocompleteWidget(widgets.TextInput): def _format_value(self, value): if self.is_localized: return formats.localize_input(value) return value
[docs] def render(self, name, value, attrs=None): if value is None: value = '' final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) if value != '': # Only add the 'value' attribute if a value is non-empty. final_attrs['value'] = widgets.force_text(self._format_value(value)) classes = 'autocomplete' if 'class' in final_attrs: classes += ' ' + final_attrs['class'] return widgets.format_html('<input class="' + classes + '"{} />', widgets.flatatt(final_attrs))
[docs]class ConceptField(forms.CharField): queryset = Concept.objects.all()
[docs] def label_from_instance(self, obj): """ The ``_concept`` field should be populated with the :class:`.Concept`\s id. """ return obj.id
[docs] def to_python(self, value): if value in self.empty_values: return None try: key = 'pk' value = self.queryset.get(**{key: value}) except (ValueError, TypeError, self.queryset.model.DoesNotExist): raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice') return value
[docs]class TemplateChoiceField(forms.ChoiceField):
[docs] def label_from_instance(self, obj): """ The ``_concept`` field should be populated with the :class:`.Concept`\s id. """ return obj.id
[docs]class ChoiceIntegerField(forms.IntegerField): """ An :class:`.IntegerField` that plays well with the :class:`.Select` widget. """
[docs] def to_python(self, value): """ Validates that int() can be called on the input. If not, return -1 (default value for ..._relationtemplate_internal_id fields). """ try: value = value.split(':')[-1] int(value) return super(ChoiceIntegerField, self).to_python(value) except ValueError: return -1
[docs]class RelationTemplateForm(forms.ModelForm): name = forms.CharField(widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'What is this relation called?' })) description = forms.CharField(widget=forms.Textarea(attrs={ 'class': 'form-control', 'rows': 2, 'placeholder': 'Please describe this relation.', })) expression = forms.CharField(widget=forms.Textarea(attrs={ 'class': 'form-control', 'rows': 2, 'placeholder': 'Enter an expression pattern for this relation.' }))
[docs] class Meta: model = RelationTemplate exclude = []
[docs]class UberCheckboxInput(forms.CheckboxInput):
[docs] def value_from_datadict(self, data, files, name): """ For some stupid reason, some checked checkboxes are getting passed as '', and :class:`forms.CheckboxInput` stupidly calls these values False. So, we fix that. If the field is present in the POST data, then it is True. Grrrr. """ if name not in data: # A missing value means False because HTML form submission does not # send results for unselected checkboxes. return False return True
[docs]class RelationTemplatePartForm(forms.ModelForm): """ TODO: make sure that there are no self-loops in inter-part references. """ source_concept = ConceptField(widget=widgets.HiddenInput(), required=False) source_node_type = forms.ChoiceField(choices=[('', 'Select a node type')] + list(RelationTemplatePart.NODE_CHOICES), required=True, widget=widgets.Select(attrs={'class': 'form-control node_type_field', 'part': 'source'})) source_concept_text = forms.CharField(widget=AutocompleteWidget(attrs={'target': 'source_concept'}), required=False) source_prompt_text = forms.BooleanField(required=False, initial=True, widget=UberCheckboxInput()) predicate_concept = ConceptField(widget=widgets.HiddenInput(), required=False) predicate_node_type = forms.ChoiceField(choices=[('', 'Select a node type')] + list(RelationTemplatePart.PRED_CHOICES), required=True, widget=widgets.Select(attrs={'class': 'form-control node_type_field', 'part': 'predicate'})) predicate_concept_text = forms.CharField(widget=AutocompleteWidget(attrs={'target': 'predicate_concept'}), required=False) predicate_prompt_text = forms.BooleanField(required=False, initial=True, widget=UberCheckboxInput()) object_concept = ConceptField(widget=widgets.HiddenInput(), required=False) object_node_type = forms.ChoiceField(choices=[('', 'Select a node type')] + list(RelationTemplatePart.NODE_CHOICES), required=True, widget=widgets.Select(attrs={'class': 'form-control node_type_field', 'part': 'object'})) object_concept_text= forms.CharField(widget=AutocompleteWidget(attrs={'target': 'object_concept'}), required=False) object_prompt_text = forms.BooleanField(required=False, initial=True, widget=UberCheckboxInput()) source_relationtemplate_internal_id = ChoiceIntegerField(required=False) object_relationtemplate_internal_id = ChoiceIntegerField(required=False) internal_id = forms.IntegerField(widget=widgets.HiddenInput())
[docs] class Media: js = ('annotations/js/autocomplete.js',) css = { 'all': ['annotations/css/autocomplete.css'] }
[docs] class Meta: model = RelationTemplatePart exclude = [ 'source_relationtemplate', 'object_relationtemplate', 'part_of', ] autocomplete_fields = ( # TODO: do we need this? 'source_concept', 'predicate_concept', 'object_concept', )
def __init__(self, *args, **kwargs): super(RelationTemplatePartForm, self).__init__(*args, **kwargs) # We need a bit of set-up for angular data bindings to work properly # on the form. # Angular can't handle hyphens, so we use underscores. print self.prefix self.safe_prefix = self.prefix.replace('-', '_') self.ident = self.prefix.split('-')[-1] self.fields['internal_id'].initial = self.ident for fname, field in self.fields.iteritems(): if 'prompt_text' in fname: continue if 'class' not in field.widget.attrs: field.widget.attrs.update({'class': 'form-control'}) if 'target' in field.widget.attrs: field.widget.attrs['target'] = 'id_{0}-'.format(self.prefix) + field.widget.attrs['target']
[docs] def clean(self, *args, **kwargs): super(RelationTemplatePartForm, self).clean(*args, **kwargs) for field in ['source', 'object']: selected_node_type = self.cleaned_data.get('%s_node_type' % field) # If the user has selected the "Concept type" field, then they must # also provide a specific concept type. if selected_node_type == 'TP': if not self.cleaned_data.get('%s_type' % field, None): self.add_error('%s_type' % field, 'Must select a concept type')
[docs]class RelationTemplatePartFormSet(BaseFormSet): """ Ensure that the structure of the links among relation template parts is coherent: it must be acyclic, and the parts must all be connected. """
[docs] def clean(self): if any(self.errors): return formGraph = nx.DiGraph() # Dependency graph among parts. for form in self.forms: formGraph.add_node(form.cleaned_data['internal_id']) for field in ['source', 'object']: fieldname = '%s_relationtemplate_internal_id' % field target = form.cleaned_data[fieldname] if target > -1: formGraph.add_edge(form.cleaned_data['internal_id'], target) if not nx.is_directed_acyclic_graph(formGraph): for form in self.forms: form.add_error(None, 'Circular reference among relation parts.') if not nx.is_connected(formGraph.to_undirected()): for form in self.forms: form.add_error(None, 'At least one relation part is disconnected from the rest of the relation.')
# print self.cleaned_data
[docs]class ProjectForm(forms.ModelForm): """ Gives the participants list for every collection. """
[docs] class Meta: model = TextCollection exclude = ['ownedBy', 'texts', 'participants']
# def __init__(self, *args, **kwargs): # super(ProjectForm, self).__init__(*args, **kwargs) # self.fields["participants"].widget = forms.CheckboxSelectMultiple() # self.fields["participants"].queryset = VogonUser.objects.order_by('username')
[docs]class MySplitDateTimeWidget(forms.widgets.SplitDateTimeWidget): """ Allow addition of separate CSS classes for the Date and Time widgets. """ def __init__(self, attrs=None, date_format=None, time_format=None): date_class = attrs.get('date_class', '') date_placeholder = attrs.get('date_placeholder', '') if date_class: del attrs['date_class'] if date_placeholder: del attrs['date_placeholder'] time_class = attrs.get('time_class', '') time_placeholder = attrs.get('time_placeholder', '') if time_class: del attrs['time_class'] if time_placeholder: del attrs['time_placeholder'] widgets = (forms.widgets.DateInput(attrs={'class' : date_class, 'placeholder': date_placeholder}, format=date_format), forms.widgets.TimeInput(attrs={'class' : time_class, 'placeholder': time_placeholder}, format=time_format)) super(forms.widgets.SplitDateTimeWidget, self).__init__(widgets, attrs)
# The database annotations in this Form cause call kinds of problems when # testing. TODO: we should find a more elegant solution than just eating # these exceptions. try:
[docs] class RelationSetFilterForm(forms.Form): text = forms.MultipleChoiceField(choices=Text.objects.annotate(num_appellations=Count('appellation')).filter(num_appellations__gte=1).values_list('id', 'title'), required=False, widget=forms.widgets.SelectMultiple(attrs={ 'class': 'form-control ymultiselect' })) project = forms.MultipleChoiceField(choices=TextCollection.objects.all().values_list('id', 'name'), required=False, widget=forms.widgets.SelectMultiple(attrs={'class': 'form-control ymultiselect'})) text_published_from = forms.DateField(required=False, widget=forms.widgets.DateInput(attrs={ 'class': 'datepicker form-control', 'placeholder': 'YYYY-MM-DD' })) text_published_through = forms.DateField(required=False, widget=forms.widgets.DateInput(attrs={ 'class': 'datepicker form-control', 'placeholder': 'YYYY-MM-DD' })) user = forms.MultipleChoiceField(choices=VogonUser.objects.annotate(num_appellations=Count('appellation')).filter(num_appellations__gte=1).values_list('id', 'username'), required=False, widget=forms.widgets.SelectMultiple(attrs={'class': 'form-control ymultiselect'})) created_from = forms.SplitDateTimeField(required=False, widget=MySplitDateTimeWidget(attrs={ 'date_class': 'datepicker form-control', 'placeholder': 'YYYY-MM-DD', 'time_class': 'form-control', 'time_placeholder': 'HH:MM:SS' })) created_through = forms.SplitDateTimeField(required=False, widget=MySplitDateTimeWidget(attrs={ 'date_class': 'datepicker form-control', 'placeholder': 'YYYY-MM-DD', 'time_class': 'form-control', 'time_placeholder': 'HH:MM:SS' })) node_types = forms.MultipleChoiceField(choices=Type.objects.all().values_list('id', 'label'), required=False, widget=forms.widgets.SelectMultiple(attrs={ 'class': 'form-control ymultiselect' })) exclusive = forms.BooleanField(required=False)
except ProgrammingError: pass
[docs]class RepositorySearchForm(forms.Form): query = forms.CharField(max_length=255, widget=widgets.TextInput(attrs={'class': 'form-control'}))