# -*- 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'
'), HTML(u'
%s
' % _(u'Options')), HTML(u'
'), ]) 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'
'), # 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 []