This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
polynum/polynum/request/forms.py

527 lines
20 KiB
Python

# -*- coding: utf-8 -*-
import json
import os.path
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from django.template.loader import render_to_string
from django.template.defaultfilters import filesizeformat
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Div, HTML, Field, Fieldset
from ..base.models import (Request, Profile, ProfileOption,
ProfileOptionChoice, DeliveryPlace, DocumentUsage, DocumentLicence)
from ..base.models.request import minimum_delivery_timedelta
from ajax_select.fields import AutoCompleteField
import utils
from ..utils import ellipsize
import app_settings
from ..base import app_settings as base_app_settings
import widgets
def make_description(model):
qs = model.objects.all()
if not any([o.description for o in qs]):
return {}
return { 'rel': 'popover',
'data-content': render_to_string('field_popover_description.html',
{'objects': qs}),
'data-title': _('Aide') }
mousewheel_popover = {
'rel': 'popover',
'data-title': _('Aide'),
'data-content': _('Vous pouvez augmentez ce nombre avec votre molette de souris.')
}
class BaseModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(BaseModelForm, self).__init__(*args, **kwargs)
for field in getattr(self.Meta, 'required', ()):
self.fields[field].required = True
self.helper = FormHelper()
self.helper.form_tag = False
def full_clean(self):
oldvalues = {}
for k, v in self.fields.iteritems():
oldvalues[k] = v.required
v.required = False
result = super(BaseModelForm, self).full_clean()
for k, v in oldvalues.iteritems():
self.fields[k].required = v
return result
def pprint_data_default_hook(self, field, data):
required = getattr(self.Meta, 'required', ())
value = getattr(self.instance, field, '') or ''
missing = not bool(value) and field in required
return (self.fields[field].label, u'%s' % (getattr(self.instance, field, None) or ''), missing)
def pprint_data(self, data):
ret = []
for field in self._meta.fields:
hook_name = 'pprint_data_%s' % field
hook = getattr(self, hook_name, None)
if hook is None:
ret.append(self.pprint_data_default_hook(field, data))
else:
ret.append(hook(data))
return ret
class DocumentUploadForm(BaseModelForm):
class Meta:
model = Request
fields = ('uploadfile', )
widgets = {
'uploadfile': widgets.ClearableFileInputNoURL,
}
@classmethod
def display_name(self):
return _(u'Envoi du document')
def __init__(self, *args, **kwargs):
super(DocumentUploadForm, self).__init__(*args, **kwargs)
self.fields['uploadfile'].label = _(u'Document')
self.fields['uploadfile'].help_text = _(u'Taille maximum autorisée: %s.') % \
filesizeformat(app_settings.MAX_DOCUMENT_SIZE)
# crispy
self.helper.layout = Layout(
Field('uploadfile'),
)
def clean(self):
cleaned_data = super(DocumentUploadForm, self).clean()
uploaded_file = cleaned_data.get('uploadfile')
if uploaded_file is not None and uploaded_file is not False:
if uploaded_file.size > app_settings.MAX_DOCUMENT_SIZE:
raise ValidationError(_('Fichier trop gros; la taille maximum autorisée est de %s.'
% filesizeformat(app_settings.MAX_DOCUMENT_SIZE)))
try:
utils.fill_document_attributes_from_pdf_file(self.instance, uploaded_file)
except ValueError, e:
raise ValidationError(_(e.args[0]))
return cleaned_data
def save(self, *args, **kwargs):
return super(DocumentUploadForm, self).save(*args, **kwargs)
def pprint_data_uploadfile(self, data):
'''
get the "cleaned" datas from the form, return a list of tuples
[ (u'description', u'value'), ... ]
'''
if self.instance.uploadfile:
name = os.path.basename(str(self.instance.uploadfile))
else:
name = _(u'sous forme papier')
return (_(u'Document'), name, False)
class DocumentDetailsForm(BaseModelForm):
class Meta:
model = Request
fields = ('name', 'nb_pages', 'usage', )
required = fields
@classmethod
def display_name(self):
return _(u'Détails sur le document')
def pprint_data_nb_pages(self, data):
t = self.pprint_data_default_hook('nb_pages', data)
return t[:2] + (self.instance.nb_pages <= 0,)
def clean_nb_pages(self):
if self.cleaned_data['nb_pages'] is None:
raise ValidationError('Le nombre de pages doit être un nombre')
return self.cleaned_data['nb_pages']
def save(self):
if self.cleaned_data['usage'] and self.cleaned_data['usage'].no_diffusion:
self.instance.licence = DocumentLicence(id=1)
return super(DocumentDetailsForm, self).save()
def __init__(self, *args, **kwargs):
super(DocumentDetailsForm, self).__init__(*args, **kwargs)
self.helper.layout = Layout(
Field('name', css_class='span12'),
Field('nb_pages', css_class='input-mini wheelable-input', **mousewheel_popover),
Field('usage', css_class='span6',
**make_description(DocumentUsage)),
)
class ReproOriginForm(BaseModelForm):
sponsor = AutoCompleteField('ldap-users', help_text=None, label=_(u'Commanditaire'), required=False)
class Meta:
model = Request
fields = ('sponsor', 'entity')
widgets = { 'entity': widgets.MillerColumns}
required = ('entity', )
class Media:
css = {
'all': ('css/ajax_select.css',),
}
js = ('js/ajax_select.js',)
@classmethod
def display_name(self):
return _(u'Affectation de la demande')
def __init__(self, *args, **kwargs):
self.frequent_entities = kwargs.pop('frequent_entities', [])
super(ReproOriginForm, self).__init__(*args, **kwargs)
self.fields['entity'].help_text = _(u'Sélectionnez le destinataire dans les listes, selon le budget à imputer.')
# crispy
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Field('sponsor', css_class='span12'),
Field('entity', template='field_entity.html',
**{'data-frequent-values': json.dumps(self.frequent_entities)}))
def save(self, *args, **kwargs):
if self.instance.entity:
codes = self.instance.entity.accounting_codes()
if len(codes) == 1:
self.instance.financial_code = codes[0].code
super(ReproOriginForm, self).save(*args, **kwargs)
class ReproDetailsForm(BaseModelForm):
class Meta:
model = Request
fields = ('copies', 'base_profile', 'details',)
required = ('copies', 'base_profile')
widgets = {
'base_profile': forms.RadioSelect,
'details': forms.Textarea,
}
@classmethod
def display_name(self):
return _(u"Choix d'impression")
def __init__(self, *args, **kwargs):
super(ReproDetailsForm, self).__init__(*args, **kwargs)
l = ((u'%s' % p.id, p) for p in Profile.visibles.all())
self.fields['base_profile'].choices = l
# crispy
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Field('copies', css_class='input-mini wheelable-input',
**mousewheel_popover),
Field('base_profile', template = 'radioprofile.html'),
)
self.helper.layout.fields.extend([
HTML(u'<div class="accordion-group">'),
HTML(u'<div class="accordion-heading"><a class="accordion-toggle" data-toggle="collapse" href="#profile-details">%s</a></div>' % _(u'Options')),
HTML(u'<div id="profile-details" class="accordion-body collapse">'),
])
self.optionfields = []
# build "options" fields
for option in ProfileOption.visibles.all():
fieldname = 'profile_option_%s' % option.id
self.optionfields.append(fieldname)
choices = [(u'%s' % c.id, c.name) \
for c in option.profileoptionchoice_set.filter(visible=True).all()]
if option.list_type == 'L': # Liste (choix unique)
self.fields[fieldname] = forms.ChoiceField(
label = u'%s' % option.name,
choices = choices,
required = False,
help_text = option.description,
)
elif option.list_type == 'R': # Radio (choix unique)
self.fields[fieldname] = forms.ChoiceField(
label = u'%s' % option.name,
widget = forms.RadioSelect,
choices = choices,
required = False,
help_text = option.description,
)
elif option.list_type == 'C': # Cases à cocher (choix multiple)
self.fields[fieldname] = forms.MultipleChoiceField(
label = u'%s' % option.name,
widget = forms.CheckboxSelectMultiple,
choices = choices,
required = False,
help_text = option.description,
)
elif option.list_type == 'LM': # Liste (choix multiple)
self.fields[fieldname] = forms.MultipleChoiceField(
label = u'%s' % option.name,
choices = choices,
required = False,
help_text = option.description,
)
# hack to display fields side-by-side (should be done in a template)
for n in xrange((len(self.optionfields)+1)/2):
left_field = Field(self.optionfields[n*2])
try:
right_field = Field(self.optionfields[n*2+1])
except:
right_field = HTML('')
self.helper.layout.fields.append(Div(
Div(left_field, css_class='span6'),
Div(right_field, css_class='span6'),
css_class="row",
))
self.helper.layout.fields.extend([
Field('details', css_class='span12',
placeholder=_(u"Indiquez par exemple un autre format "
u"(A3, A5, …), une orientation du papier (paysage, "
u"portrait, plusieurs pages par feuille, …), une "
u"finition particulière …")),
HTML(u'</div></div>'), # profile_details
])
def save(self, *args, **kwargs):
self.instance.choices.clear()
chosen = set()
for option in ProfileOption.visibles.all():
fieldname = 'profile_option_%s' % option.id
cleaned_data = self.cleaned_data
value = cleaned_data.get(fieldname, None)
if hasattr(value, 'append'):
for v in value:
chosen.add(v)
elif value is not None:
chosen.add(value)
chosen = filter(None, chosen)
result = super(ReproDetailsForm, self).save(*args, **kwargs)
self.instance.choices = ProfileOptionChoice.visibles.filter(id__in=chosen)
return result
def pprint_data_base_profile(self, data):
profile = getattr(self.instance, 'base_profile', None)
if not profile:
base_profile = data.get('base_profile')
if base_profile:
profile = Profile.visibles.get(id=base_profile)
value = u'%s' % profile if profile else ''
return (_(u'Profil de base'), value, not bool(value))
def pprint_data_copies(self, data):
t = self.pprint_data_default_hook('copies', data)
return t[:2] + (self.instance.copies < 1,)
def pprint_data_choices(self, data):
ret = []
profile = getattr(self.instance, 'base_profile', None)
if profile:
ids = set(profile.choices.values_list('id', flat=True))
def choice_ok(choice):
return choice.id not in ids or \
choice.option.list_type in ('C', 'LM')
if self.instance.id:
for choice in self.instance.choices.all():
if choice_ok(choice):
ret.append((choice.option.name, choice.name, False))
else:
option_choices = filter(None, [ self.data.get(field) for field in self.optionfields ])
for choice in ProfileOptionChoice.visibles.filter(id__in=option_choices).select_related():
if choice_ok(choice):
ret.append((choice.option.name, choice.name, False))
return ret
def pprint_data_details(self, data):
t = self.pprint_data_default_hook('details', data)
return t[0], ellipsize(t[1], 80), t[2]
def pprint_data(self, data):
ret = super(ReproDetailsForm, self).pprint_data(data)
ret.extend(self.pprint_data_choices(data))
return ret
def clean_copies(self):
if self.cleaned_data['copies'] is None:
raise ValidationError('Le nombre de copies doit être un nombre')
return self.cleaned_data['copies']
class DeliveryForm(BaseModelForm):
class Meta:
model = Request
fields = ('delivery_place', 'delivery_date', 'contact_email',
'contact_telephone1', 'contact_telephone2', 'contact_bureau')
required = ('delivery_place', 'delivery_date')
@classmethod
def display_name(self):
return _(u"Options de livraison")
def check_delivery_date(self, delivery_date):
start = self.instance.history_set.start_date()
not_before = start + minimum_delivery_timedelta()
return not (delivery_date < not_before.date())
def pprint_data_delivery_date(self, data):
t = self.pprint_data_default_hook('delivery_date', data)
return t[:2] + (not self.check_delivery_date(self.instance.delivery_date),)
def clean_delivery_date(self):
delivery_date = self.cleaned_data['delivery_date']
if not delivery_date or not self.check_delivery_date(delivery_date):
msg = u"La date de livraison souhaitée doit être au minimum"
days = base_app_settings.MINIMUM_DELIVERY_DELAY
msg += " %s jour(s) plus tard que la date de la demande." % days
raise ValidationError(msg)
return delivery_date
def __init__(self, *args, **kwargs):
super(DeliveryForm, self).__init__(*args, **kwargs)
self.fields['delivery_place'].empty_label = None
# crispy
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Field('delivery_place', css_class='span6',
**make_description(DeliveryPlace)),
Field('delivery_date', css_class='span2 datepicker'),
Fieldset(u'Contact',
HTML('{% load editor %}{% editablecontent "_request_edit_delivery_contact_header" %}{% endeditablecontent %}'),
Field(
'contact_email',
'contact_telephone1',
'contact_telephone2',
'contact_bureau')))
class CopyrigtsForm(BaseModelForm):
class Meta:
model = Request
fields = ('copyright', 'licence')
required = ('licence',)
widgets = {
}
@classmethod
def display_name(self):
return _(u"Options de diffusion")
def clean(self):
cleaned_data = super(CopyrigtsForm, self).clean()
copyright = cleaned_data.get('copyright')
licence = cleaned_data.get('licence')
if copyright is True and licence is not None \
and licence.only_free_documents:
raise ValidationError(_(u"Ce mode de diffusion n'est pas"
u" compatible avec un document contenant un ou des "
u"extrait(s) d'oeuvres(s) soumise(s) au droit "
u"d'auteur"))
return cleaned_data
def __init__(self, *args, **kwargs):
super(CopyrigtsForm, self).__init__(*args, **kwargs)
if 'licence' in self.fields:
self.fields['licence'].label = _(u'Diffusion de votre fichier')
if len(self.fields['licence'].choices) < 2:
f = self.fields['licence']
f.empty_label = None
f.initial = list(self.fields['licence'].choices)[0][0]
if 'copyright' in self.fields:
self.fields['copyright'].label = _(u'Droits de copie')
if not self.no_diffusion():
self.fields['copyright'].required = True
# crispy
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Field('copyright', css_class="span8"),
HTML('{% load editor %}{% editablecontent "_request_edit_copyright_copyright" %}{% endeditablecontent %}'),
Field('licence', css_class="span8", **make_description(DocumentLicence)),
HTML('{% load editor %}{% editablecontent "_request_edit_copyright_licence" %}{% endeditablecontent %}'),
)
def no_diffusion(self):
return self.instance is not None and self.instance.is_from_remote_request or \
(self.instance.usage and self.instance.usage.no_diffusion) \
or self.instance.is_paper()
def pprint_data_copyright(self, data):
if self.no_diffusion():
missing = False
else:
missing = self.instance.copyright is None
value = ''
if self.instance.copyright is not None:
value = _(u'Oui') if self.instance.copyright else _('Non')
return (self.fields['copyright'].label, value, missing)
class FinancialForm(BaseModelForm):
class Meta:
model = Request
fields = ('financial_code', 'financial_comment')
required = ('financial_code',)
def __init__(self, *args, **kwargs):
self.frequent_financial_codes = kwargs.pop(
'frequent_financial_codes', [])
super(FinancialForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Field('financial_code', **{
'data-frequent-values': json.dumps(self.frequent_financial_codes)
}),
Field('financial_comment', css_class="span12"))
@classmethod
def display_name(self):
return _(u"Données financières")
class CostForm(BaseModelForm):
class Meta:
model = Request
fields = ('cost',)
required = fields
def __init__(self, *args, **kwargs):
super(CostForm, self).__init__(*args, **kwargs)
self.fields['cost'].localize = True
self.fields['cost'].widget.is_localized = True
if self.instance and self.instance.id:
help_text = _(u'Coût estimé: %s\xa0') % self.instance.estimated_cost()
self.fields['cost'].help_text = help_text
@classmethod
def display_name(self):
return _(u"Coût réel")
class ValidationForm(forms.Form):
@classmethod
def display_name(self):
return _(u"Validation")
def __init__(self, *args, **kwargs):
super(ValidationForm, self).__init__(*args, **kwargs)
# crispy
self.helper = FormHelper()
self.helper.form_tag = False
def pprint_data(self, data):
return []