source: main/branches/3D/openPLM/plmapp/forms.py @ 595

Revision 595, 21.6 KB checked in by pcosquer, 9 years ago (diff)

3D branch: merge changes from trunk rev 594

Line 
1############################################################################
2# openPLM - open source PLM
3# Copyright 2010 Philippe Joulaud, Pierre Cosquer
4#
5# This file is part of openPLM.
6#
7#    openPLM is free software: you can redistribute it and/or modify
8#    it under the terms of the GNU General Public License as published by
9#    the Free Software Foundation, either version 3 of the License, or
10#    (at your option) any later version.
11#
12#    openPLM is distributed in the hope that it will be useful,
13#    but WITHOUT ANY WARRANTY; without even the implied warranty of
14#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15#    GNU General Public License for more details.
16#
17#    You should have received a copy of the GNU General Public License
18#    along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
19#
20# Contact :
21#    Philippe Joulaud : ninoo.fr@gmail.com
22#    Pierre Cosquer : pierre.cosquer@insa-rennes.fr
23################################################################################
24
25import re
26
27from django import forms
28from django.conf import settings
29from django.forms.formsets import formset_factory, BaseFormSet
30from django.forms.models import modelform_factory, modelformset_factory
31from django.contrib.auth.models import User, Group
32from django.forms import ValidationError
33from django.utils.translation import ugettext_lazy as _
34from django.contrib.sites.models import Site
35from django.utils.functional import memoize
36
37import openPLM.plmapp.models as m
38from openPLM.plmapp.controllers import rx_bad_ref, DocumentController
39from openPLM.plmapp.controllers.user import UserController
40from openPLM.plmapp.controllers.group import GroupController
41from openPLM.plmapp.widgets import JQueryAutoComplete
42from openPLM.plmapp.encoding import ENCODINGS
43
44class PLMObjectForm(forms.Form):
45    u"""
46    A formulaire that identifies a :class:`PLMObject`.
47    """
48
49    type = forms.CharField()
50    reference = forms.CharField()
51    revision = forms.CharField()
52
53
54def _clean_reference(self):
55    data = self.cleaned_data["reference"]
56    if rx_bad_ref.search(data):
57        raise ValidationError(_("Bad reference: '#', '?', '/' and '..' are not allowed"))
58    return re.sub("\s+", " ", data.strip(" "))
59
60def _clean_revision(self):
61    data = self.cleaned_data["revision"]
62    if rx_bad_ref.search(data):
63        raise ValidationError(_("Bad revision: '#', '?', '/' and '..' are not allowed"))
64    return re.sub("\s+", " ", data.strip(" "))
65
66INVALID_GROUP = _("Bad group, check that the group exists and that you belong"
67        " to this group.")
68
69def get_creation_form(user, cls=m.PLMObject, data=None, empty_allowed=False):
70    u"""
71    Returns a creation form suitable to creates an object
72    of type *cls*.
73
74    The returned form can be used, if it is valid, with the function
75    :meth:`~plmapp.controllers.PLMObjectController.create_from_form`
76    to create a :class:`~plmapp.models.PLMObject` and his associated
77    :class:`~plmapp.controllers.PLMObjectController`.
78
79    If *initial* is provided, it will be used to fill the form.
80    """
81    Form = get_creation_form.cache.get(cls)
82    if Form is None:
83        fields = cls.get_creation_fields()
84        Form = modelform_factory(cls, fields=fields, exclude=('type', 'state'))
85        if issubclass(cls, m.PLMObject):
86            Form.clean_reference = _clean_reference
87            Form.clean_revision = _clean_revision
88            def _clean(self):
89                cleaned_data = self.cleaned_data
90                ref = cleaned_data.get("reference", "")
91                rev = cleaned_data.get("revision", "")
92                if cls.objects.filter(type=cls.__name__, revision=rev, reference=ref):
93                    raise ValidationError(_("An object with the same type, reference and revision already exists"))
94                return cleaned_data
95            Form.clean = _clean
96        get_creation_form.cache[cls] = Form
97    form = Form(data=data, empty_permitted=empty_allowed) if data else Form()
98    if issubclass(cls, m.PLMObject):
99        # display only valid groups
100        groups = user.groups.all().values_list("id", flat=True)
101        field = form.fields["group"]
102        field.queryset = m.GroupInfo.objects.filter(id__in=groups)
103        field.error_messages["invalid_choice"] = INVALID_GROUP
104    return form
105get_creation_form.cache = {}
106       
107def get_modification_form(cls=m.PLMObject, data=None, instance=None):
108    Form = get_modification_form.cache.get(cls)
109    if Form is None:
110        fields = cls.get_modification_fields()
111        Form = modelform_factory(cls, fields=fields)
112        get_modification_form.cache[cls] = Form
113    if data:
114        return Form(data)
115    elif instance:
116        return Form(instance=instance)
117    else:
118        return Form()
119get_modification_form.cache = {}
120
121def integerfield_clean(value):
122    if value:
123        value = value.replace(" ", "")
124        value_validated = re.search(r'^([><]?)(\-?\d*)$',value)
125        if value_validated:
126            return value_validated.groups()
127        else:
128            raise ValidationError("Number or \"< Number\" or \"> Number\"")
129    return None
130
131class TypeForm(forms.Form):
132    LIST = m.get_all_users_and_plmobjects_with_level()
133    type = forms.TypedChoiceField(choices=LIST)
134
135class TypeFormWithoutUser(forms.Form):
136    LIST_WO_USER = m.get_all_plmobjects_with_level()
137    type = forms.TypedChoiceField(choices=LIST_WO_USER,
138            label=_("Select a type"))
139
140class TypeSearchForm(TypeForm):
141    pass
142
143class FakeItems(object):
144    def __init__(self, values):
145        self.values = values
146    def items(self):
147        return self.values
148
149def get_search_form(cls=m.PLMObject, data=None):
150    Form = get_search_form.cache.get(cls)
151    if Form is None:
152        if issubclass(cls, (m.PLMObject, m.GroupInfo)):
153            fields = set(cls.get_creation_fields())
154            fields.update(set(cls.get_modification_fields()))
155            fields.difference_update(("type", "lifecycle", "group"))
156        else:
157            fields = set(["username", "first_name", "last_name"])
158        fields_dict = {}
159        for field in fields:
160            model_field = cls._meta.get_field(field)
161            form_field = model_field.formfield()
162            form_field.help_text = ""
163            if isinstance(form_field.widget, forms.Textarea):
164                form_field.widget = forms.TextInput(attrs={'title':"You can use * charactere(s) to enlarge your research.", 'value':"*"})
165            if isinstance(form_field.widget, forms.TextInput):
166                source = '/ajax/complete/%s/%s/' % (cls.__name__, field)
167                form_field.widget = JQueryAutoComplete(source,
168                    attrs={'title':"You can use * charactere(s) to enlarge your research.", 'value':"*"})
169            if isinstance(form_field, forms.fields.IntegerField) and isinstance(form_field.widget, forms.TextInput):
170                form_field.widget = forms.TextInput(attrs={'title':"Please enter a whole number. You can use < or > to enlarge your research."})
171            form_field.required = False
172            fields_dict[field] = form_field
173            if isinstance(form_field, forms.fields.IntegerField):
174                form_field.clean = integerfield_clean
175
176        def search(self, query_set=None):
177            if self.is_valid():
178                query = {}
179                for field in self.changed_data:
180                    model_field = cls._meta.get_field(field)
181                    form_field = model_field.formfield()
182                    value =  self.cleaned_data[field]
183                    if value is None or (isinstance(value, basestring) and value.isspace()):
184                        continue
185                    if isinstance(form_field, forms.fields.CharField)\
186                                    and isinstance(form_field.widget, (forms.TextInput, forms.Textarea)):
187                        value_list = re.split(r"\s*\*\s*", value)
188                        if len(value_list)==1:
189                            query["%s__iexact"%field]=value_list[0]
190                        else :
191                            if value_list[0]:
192                                query["%s__istartswith"%field]=value_list[0]
193                            if value_list[-1]:
194                                query["%s__iendswith"%field]=value_list[-1]
195                            for value_item in value_list[1:-1]:
196                                if value_item:
197                                    query["%s__icontains"%field]=value_item
198                    elif isinstance(form_field, forms.fields.IntegerField)\
199                                    and isinstance(form_field.widget, (forms.TextInput, forms.Textarea)):
200                        sign, value_str = self.cleaned_data[field]
201                        cr = "%s__%s" %(field, {"" : "exact", ">" : "gt", "<" : "lt"}[sign])
202                        query[cr]= int(value_str)
203                    else:
204                        query[field] = self.cleaned_data[field]
205                    query_set = query_set.filter(**query)
206                if query_set is not None:
207                    return query_set
208                else:
209                    return []
210        fields_list = fields_dict.items()
211        for ref, field in fields_list:
212            if ref=='reference':
213                fields_list.remove((ref, field))
214                fields_list.insert(0, (ref, field))
215                break
216        ordered_fields_list = FakeItems(fields_list)
217        Form = type("Search%sForm" % cls.__name__,
218                    (forms.BaseForm,),
219                    {"base_fields" : ordered_fields_list, "search" : search})
220        get_search_form.cache[cls] = Form
221    if data is not None:
222        return Form(data=data, empty_permitted=True)
223    else:
224        return Form(empty_permitted=True)
225get_search_form.cache = {}   
226
227from haystack.query import EmptySearchQuerySet
228from openPLM.plmapp.search import SmartSearchQuerySet
229
230class SimpleSearchForm(forms.Form):
231    q = forms.CharField(label=_("Query"), required=False)
232
233    def search(self, *models):
234        if self.is_valid():
235            query = self.cleaned_data["q"].strip()
236            sqs = SmartSearchQuerySet().highlight().models(*models)
237            if not query or query == "*":
238                return sqs
239            results = sqs.auto_query(query)
240            return results
241        else:
242            return EmptySearchQuerySet()
243       
244
245class AddChildForm(PLMObjectForm):
246    quantity = forms.FloatField()
247    order = forms.IntegerField()
248
249
250class DisplayChildrenForm(forms.Form):
251    LEVELS = (("all", "All levels",),
252              ("first", "First level",),
253              ("last", "Last level"),)
254    level = forms.ChoiceField(choices=LEVELS, widget=forms.RadioSelect())
255    date = forms.SplitDateTimeField(required=False)
256
257class ModifyChildForm(forms.ModelForm):
258    delete = forms.BooleanField(required=False, initial=False)
259    parent = forms.ModelChoiceField(queryset=m.Part.objects.all(),
260                                   widget=forms.HiddenInput())
261    child = forms.ModelChoiceField(queryset=m.Part.objects.all(),
262                                   widget=forms.HiddenInput())
263    quantity = forms.FloatField(widget=forms.TextInput(attrs={'size':'4'}))
264    order = forms.IntegerField(widget=forms.TextInput(attrs={'size':'2'}))
265    class Meta:
266        model = m.ParentChildLink
267        fields = ["order", "quantity", "child", "parent"]
268
269ChildrenFormset = modelformset_factory(m.ParentChildLink,
270                                       form=ModifyChildForm, extra=0)
271def get_children_formset(controller, data=None):
272    if data is None:
273        queryset = m.ParentChildLink.objects.filter(parent=controller,
274                                                    end_time__exact=None)
275        formset = ChildrenFormset(queryset=queryset)
276    else:
277        formset = ChildrenFormset(data=data)
278    return formset
279
280class AddRevisionForm(forms.Form):
281    revision = forms.CharField()
282    clean_revision = _clean_revision
283   
284class AddRelPartForm(PLMObjectForm):
285    pass
286   
287class ModifyRelPartForm(forms.ModelForm):
288    delete = forms.BooleanField(required=False, initial=False)
289    document = forms.ModelChoiceField(queryset=m.Document.objects.all(),
290                                   widget=forms.HiddenInput())
291    part = forms.ModelChoiceField(queryset=m.Part.objects.all(),
292                                   widget=forms.HiddenInput())
293    class Meta:
294        model = m.DocumentPartLink
295        fields = ["document", "part"]
296       
297RelPartFormset = modelformset_factory(m.DocumentPartLink,
298                                      form=ModifyRelPartForm, extra=0)
299def get_rel_part_formset(controller, data=None):
300    if data is None:
301        queryset = controller.get_attached_parts()
302        formset = RelPartFormset(queryset=queryset)
303    else:
304        formset = RelPartFormset(data=data)
305    return formset
306
307class AddFileForm(forms.Form):
308    filename = forms.FileField()
309   
310class ModifyFileForm(forms.ModelForm):
311    delete = forms.BooleanField(required=False, initial=False)
312    document = forms.ModelChoiceField(queryset=m.Document.objects.all(),
313                                   widget=forms.HiddenInput())
314    class Meta:
315        model = m.DocumentFile
316        fields = ["document"]
317       
318FileFormset = modelformset_factory(m.DocumentFile, form=ModifyFileForm, extra=0)
319def get_file_formset(controller, data=None):
320    if data is None:
321        queryset = controller.files
322        formset = FileFormset(queryset=queryset)
323    else:
324        formset = FileFormset(data=data)
325    return formset
326
327class AddDocCadForm(PLMObjectForm):
328    pass
329   
330class ModifyDocCadForm(forms.ModelForm):
331    delete = forms.BooleanField(required=False, initial=False)
332    part = forms.ModelChoiceField(queryset=m.Part.objects.all(),
333                                   widget=forms.HiddenInput())
334    document = forms.ModelChoiceField(queryset=m.Document.objects.all(),
335                                   widget=forms.HiddenInput())
336    class Meta:
337        model = m.DocumentPartLink
338        fields = ["part", "document"]
339       
340DocCadFormset = modelformset_factory(m.DocumentPartLink,
341                                     form=ModifyDocCadForm, extra=0)
342def get_doc_cad_formset(controller, data=None):
343    if data is None:
344        queryset = controller.get_attached_documents()
345        formset = DocCadFormset(queryset=queryset)
346    else:
347        formset = DocCadFormset(data=data)
348    return formset
349
350
351class NavigateFilterForm(forms.Form):
352    only_search_results = forms.BooleanField(initial=False,
353                required=False, label=_("only search results"))
354    prog = forms.ChoiceField(choices=(("dot", _("Hierarchical")),
355                                      ("neato", _("Radial 1")),
356                                      ("twopi", _("Radial 2")),
357                                      ),
358                             required=False, initial="dot",
359                             label=_("layout"))
360    doc_parts = forms.CharField(initial="", required="",
361                                widget=forms.HiddenInput())
362    update = forms.BooleanField(initial=False, required=False,
363           widget=forms.HiddenInput() )
364
365class PartNavigateFilterForm(NavigateFilterForm):
366    child = forms.BooleanField(initial=True, required=False, label=_("child"))
367    parents = forms.BooleanField(initial=True, required=False, label=_("parents"))
368    doc = forms.BooleanField(initial=True, required=False, label=_("doc"))
369    cad = forms.BooleanField(required=False, label=_("cad"))
370    owner = forms.BooleanField(required=False, label=_("owner"))
371    signer = forms.BooleanField(required=False, label=_("signer"))
372    notified = forms.BooleanField(required=False, label=_("notified"))
373
374class DocNavigateFilterForm(NavigateFilterForm):
375    part = forms.BooleanField(initial=True, required=False, label=_("part"))
376    owner = forms.BooleanField(required=False, label=_("owner"))
377    signer = forms.BooleanField(required=False, label=_("signer"))
378    notified = forms.BooleanField(required=False, label=_("notified"))
379
380class UserNavigateFilterForm(NavigateFilterForm):
381    owned = forms.BooleanField(initial=True, required=False, label=_("owned"))
382    to_sign = forms.BooleanField(required=False, label=_("to sign"))
383    request_notification_from = forms.BooleanField(required=False, label=_("request notification from"))
384
385class GroupNavigateFilterForm(NavigateFilterForm):
386    owner = forms.BooleanField(required=False, label=_("owner"))
387    user = forms.BooleanField(required=False, label=_("user"))
388    part = forms.BooleanField(initial=True, required=False, label=_("part"))
389    doc = forms.BooleanField(initial=True, required=False, label=_("doc"))
390
391def get_navigate_form(obj):
392    if isinstance(obj, UserController):
393        cls = UserNavigateFilterForm
394    elif isinstance(obj, DocumentController):
395        cls = DocNavigateFilterForm
396    elif isinstance(obj, GroupController):
397        cls = GroupNavigateFilterForm
398    else:
399        cls = PartNavigateFilterForm
400    return cls
401
402
403class OpenPLMUserChangeForm(forms.ModelForm):
404    #username = forms.RegexField(widget=forms.HiddenInput())
405    class Meta:
406        model = User
407        exclude = ('username','is_staff', 'is_active', 'is_superuser', 'last_login', 'date_joined', 'groups', 'user_permissions', 'password')
408
409class SelectUserForm(forms.Form):
410    type = forms.CharField(label=_("Type"), initial="User")
411    username = forms.CharField(label=_("Username"))
412   
413   
414class ModifyUserForm(forms.Form):
415    delete = forms.BooleanField(required=False, initial=False)
416    user = forms.ModelChoiceField(queryset=User.objects.all(),
417                                   widget=forms.HiddenInput())
418    group = forms.ModelChoiceField(queryset=Group.objects.all(),
419                                   widget=forms.HiddenInput())
420   
421    @property
422    def user_data(self):
423        return self.initial["user"]
424
425UserFormset = formset_factory(ModifyUserForm, extra=0)
426def get_user_formset(controller, data=None):
427    if data is None:
428        queryset = controller.user_set.exclude(id=controller.owner.id)
429        initial = [dict(group=controller.object, user=user)
430                for user in queryset]
431        formset = UserFormset(initial=initial)
432    else:
433        formset = UserFormset(data)
434    return formset
435
436
437class SponsorForm(forms.ModelForm):
438    sponsor = forms.ModelChoiceField(queryset=User.objects.all(),
439            required=True, widget=forms.HiddenInput())
440    warned = forms.BooleanField(initial=False, required=False,
441                                widget=forms.HiddenInput())
442
443    class Meta:
444        model = User
445        fields = ('username', 'first_name', 'last_name', 'email', 'groups')
446
447    def __init__(self, *args, **kwargs):
448        sponsor = kwargs.pop("sponsor", None)
449        super(SponsorForm, self).__init__(*args, **kwargs)
450        if "sponsor" in self.data:
451            sponsor = int(self.data["sponsor"])
452        if sponsor is not None:
453            qset = m.GroupInfo.objects.filter(owner__id=sponsor)
454            self.fields["groups"].queryset = qset
455        self.fields["groups"].help_text = _("The new user will belong to the selected groups")
456        for key, field in self.fields.iteritems():
457            if key != "warned":
458                field.required = True
459
460    def clean_email(self):
461        email = self.cleaned_data["email"]
462        if email and bool(User.objects.filter(email=email)):
463            raise forms.ValidationError(_(u'Email address must be unique.'))
464        try:
465            # checks *email*
466            if settings.RESTRICT_EMAIL_TO_DOMAINS:
467                # i don't know if a domain can contains a '@'
468                domain = email.rsplit("@", 1)[1]
469                if domain not in Site.objects.values_list("domain", flat=True):
470                    raise forms.ValidationError(_(u"Email's domain not valid"))
471        except AttributeError:
472            # restriction disabled if the setting is not set
473            pass
474        return email
475 
476    def clean(self):
477        super(SponsorForm, self).clean()
478        if not self.cleaned_data.get("warned", False):
479            first_name = self.cleaned_data["first_name"]
480            last_name = self.cleaned_data["last_name"]
481            homonyms = User.objects.filter(first_name=first_name, last_name=last_name)
482            if homonyms:
483                self.data = self.data.copy()
484                self.data["warned"] = "on"
485                error = _(u"Warning! There are homonyms: %s!") % \
486                    u", ".join(u.username for u in homonyms)
487                raise forms.ValidationError(error)
488        return self.cleaned_data
489
490_inv_qset = m.Invitation.objects.filter(state=m.Invitation.PENDING)
491class InvitationForm(forms.Form):
492    invitation = forms.ModelChoiceField(queryset=_inv_qset,
493            required=True, widget=forms.HiddenInput())
494
495class CSVForm(forms.Form):
496    file = forms.FileField()
497    encoding = forms.TypedChoiceField(initial="utf_8", choices=ENCODINGS)
498
499
500def get_headers_formset(Importer):
501    class CSVHeaderForm(forms.Form):
502        HEADERS = Importer.get_headers()
503        header = forms.TypedChoiceField(choices=zip(HEADERS, HEADERS),
504                required=False)
505
506    class BaseHeadersFormset(BaseFormSet):
507
508        def clean(self):
509            if any(self.errors):
510                return
511            headers = []
512            for form in self.forms:
513                header = form.cleaned_data['header']
514                if header == u'None':
515                    header = None
516                if header and header in headers:
517                    raise forms.ValidationError(_("Columns must have distinct headers."))
518                headers.append(header)
519            for field in Importer.REQUIRED_HEADERS:
520                if field not in headers:
521                    raise forms.ValidationError(Importer.get_missing_headers_msg())
522            self.headers = headers
523
524    return formset_factory(CSVHeaderForm, extra=0, formset=BaseHeadersFormset)
525
526get_headers_formset = memoize(get_headers_formset, {}, 1)
527
Note: See TracBrowser for help on using the repository browser.