527 lines
20 KiB
Python
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 []
|
|
|