#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2008-2012 Étienne Loks # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # See the file COPYING for details. """ Forms """ from django import forms from django.conf import settings from django.contrib.gis.db import models from django.core.exceptions import ImproperlyConfigured from django.forms.formsets import formset_factory from django.utils.translation import ugettext as _ from django.contrib.auth.models import User, Permission, ContentType from django.contrib.admin.widgets import AdminDateWidget from django.core.mail import EmailMessage, BadHeaderError from chimere.models import Marker, Route, PropertyModel, Property, Area,\ News, Category, SubCategory, RouteFile, MultimediaFile, MultimediaType, \ PictureFile from chimere.widgets import AreaField, PointField, TextareaWidget, \ DatePickerWidget from datetime import timedelta, datetime, tzinfo ZERO = timedelta(0) class UTC(tzinfo): """UTC time zone""" def utcoffset(self, dt): return ZERO def tzname(self, dt): return settings.TIME_ZONE def dst(self, dt): return ZERO def notifyStaff(subject, body, sender=None): if not settings.EMAIL_HOST: return if settings.PROJECT_NAME: subject = u'[%s] %s' % (settings.PROJECT_NAME, subject) user_list = [u.email for u in User.objects.filter(is_staff=True).exclude(email="").order_by('id')] headers = {} if sender: headers['Reply-To'] = sender email = EmailMessage(subject, body, user_list[0], user_list, headers=headers) try: email.send() except BadHeaderError: return False return True def notifySubmission(geo_object): category = u" - ".join([unicode(cat) for cat in geo_object.categories.all()]) subject = u'%s %s' % (_(u"New submission for"), category) message = _(u'The new item "%s" has been submited in the category: ') % \ geo_object.name + category message += "\n\n" + _(u"To valid, precise or unvalid this item: ") message += settings.BASE_URL + 'admin' message += u"\n\n--\nChimère" return notifyStaff(subject, message) class ContactForm(forms.Form): """ Main form for categories """ email = forms.EmailField(label=_("Email (optional)"), required=False) content = forms.CharField(label=_("Object"), widget=forms.Textarea) class NewsAdminForm(forms.ModelForm): """ Main form for news """ content = forms.CharField(widget=TextareaWidget) class Meta: model = News class CategoryAdminForm(forms.ModelForm): """ Main form for categories """ description = forms.CharField(widget=TextareaWidget, required=False) class Meta: model = Category class MarkerAdminFormBase(forms.ModelForm): """ Main form for marker """ class Meta: model = Marker def __init__(self, *args, **keys): """ Custom initialization method in order to manage properties """ if 'instance' in keys and keys['instance']: instance = keys['instance'] property_dct = {} for pm in PropertyModel.objects.filter(available=True): property = instance.getProperty(pm) if property: property_dct[pm.getNamedId()] = property.value if 'initial' in keys: keys['initial'].update(property_dct) else: keys['initial'] = property_dct super(MarkerAdminFormBase, self).__init__(*args, **keys) if settings.CHIMERE_DAYS_BEFORE_EVENT: self.fields['start_date'].widget = DatePickerWidget() self.fields['end_date'].widget = DatePickerWidget() def clean(self): ''' Verify that a start date is provided when an end date is set ''' if not settings.CHIMERE_DAYS_BEFORE_EVENT: return self.cleaned_data if self.cleaned_data['end_date'] and \ not self.cleaned_data['start_date']: msg = _(u"End date has been set with no start date") self._errors["end_date"] = self.error_class([msg]) del self.cleaned_data['end_date'] return self.cleaned_data def save(self, *args, **keys): """ Custom save method in order to manage associated properties """ print self.cleaned_data new_marker = super(MarkerAdminFormBase, self).save(*args, **keys) if 'status' not in self.cleaned_data: new_marker.status = 'S' if new_marker.status == 'A': tz = UTC() new_marker.available_date = datetime.replace(datetime.utcnow(), tzinfo=tz) new_marker.save() # save properties properties = dict([(k.split('_')[-1], self.cleaned_data[k]) \ for k in self.cleaned_data.keys() if k.startswith('property_')]) new_marker.saveProperties(properties) return new_marker # As we have dynamic fields, it's cleaner to make the class dynamic too fields = {} # declare properties for prop in PropertyModel.objects.filter(available=True): key = "property_%d_%d" % (prop.order, prop.id) fields[key] = forms.CharField(label=prop.name, widget=PropertyModel.TYPE_WIDGET[prop.type], required=False) MarkerAdminForm = type("MarkerAdminForm", (MarkerAdminFormBase,), fields) class MarkerForm(MarkerAdminForm): """ Form for the edit page """ ref_pk = forms.IntegerField(label=u" ", widget=forms.HiddenInput(), required=False) class Meta: model = Marker exclude = ('status',) class RouteAdminForm(forms.ModelForm): """ Main form for route """ class Meta: model = Route def __init__(self, *args, **keys): """ Custom initialization method in order to manage properties """ if 'instance' in keys and keys['instance']: instance = keys['instance'] property_dct = {} for pm in PropertyModel.objects.filter(available=True): property = instance.getProperty(pm) if property: property_dct[pm.getNamedId()] = property.value if 'initial' in keys: keys['initial'].update(property_dct) else: keys['initial'] = property_dct super(RouteAdminForm, self).__init__(*args, **keys) if settings.CHIMERE_DAYS_BEFORE_EVENT: self.fields['start_date'].widget = DatePickerWidget() self.fields['end_date'].widget = DatePickerWidget() def save(self, *args, **keys): """ Custom save method in order to manage associated properties """ new_route = super(RouteAdminForm, self).save(*args, **keys) if 'status' not in self.cleaned_data: new_route.status = 'S' new_route.save() return new_route class RouteForm(RouteAdminForm): """ Form for the edit page """ picture = forms.ImageField(label=_("Image"), required=False) point = forms.CharField(label=" ", required=False, widget=forms.HiddenInput) associated_file_id = forms.CharField(label=" ", required=False, widget=forms.HiddenInput) class Meta: model = Route exclude = ('status',) def __init__(self, *args, **kwargs): if kwargs.get('instance'): try: kwargs['initial'] = { 'point':Marker.objects.get(route=kwargs['instance']).point} except: pass super(RouteForm, self).__init__(*args, **kwargs) def save(self, *args, **keys): """ Custom save method in order to manage associated marker and file """ new_route = super(RouteForm, self).save(*args, **keys) # associate a route file if 'associated_file_id' in self.cleaned_data and \ self.cleaned_data['associated_file_id']: file_pk = int(self.cleaned_data['associated_file_id']) new_route.associated_file = RouteFile.objects.get(pk=file_pk) new_route.save() marker_fields = [f.attname for f in Marker._meta.fields] marker_dct = dict([(k, self.cleaned_data[k]) for k in self.cleaned_data if k in marker_fields]) marker_dct['status'] = new_route.status categories = [] new_marker, created = Marker.objects.get_or_create(route=new_route, defaults=marker_dct) if not created: for att in marker_dct.keys(): setattr(new_marker, att, marker_dct[att]) new_marker.save() new_marker.categories.clear() for category in self.cleaned_data['categories']: new_marker.categories.add(category) new_marker.save() # save properties properties = dict([(k.split('_')[-1], self.cleaned_data[k]) \ for k in self.cleaned_data.keys() if k.startswith('property_')]) new_marker.saveProperties(properties) return new_route class BaseFileForm(forms.ModelForm): id = forms.IntegerField(label=u"", widget=forms.HiddenInput(), required=False) def __init__(self, *args, **kwargs): if not hasattr(self, '_related_name') or not self._related_name: raise ImproperlyConfigured super(BaseFileForm, self).__init__(*args, **kwargs) def save(self, associated_marker): if not hasattr(self, 'cleaned_data') or not self.cleaned_data: return instance = None if self.cleaned_data.get('id'): try: # marker must be equal to none because the link has just be # removed - this condition is necessary to prevent modification # of already validated items by a forged request instance = self._meta.model.objects.get( pk=self.cleaned_data['id'], marker=None) except: pass self.cleaned_data.pop('id') if self.cleaned_data.get('DELETE'): if instance: instance.delete() return self.cleaned_data.pop('DELETE') if instance: for k in self.cleaned_data: setattr(instance, k, self.cleaned_data[k]) instance.save() else: instance = self._meta.model.objects.create(**self.cleaned_data) getattr(associated_marker, self._related_name).add(instance) class MultimediaFileForm(BaseFileForm): """ Form for a multimedia file """ _related_name = 'multimedia_files' class Meta: model = MultimediaFile exclude = ('order',) def __init__(self, *args, **kwargs): super(MultimediaFileForm, self).__init__(*args, **kwargs) self.fields['multimedia_type'].widget.choices = \ MultimediaType.get_tuples() MultimediaFileFormSet = formset_factory(MultimediaFileForm, can_delete=True) class PictureFileForm(BaseFileForm): """ Form for a picture file """ _related_name = 'pictures' class Meta: model = PictureFile exclude = ('order', 'height', 'width') PictureFileFormSet = formset_factory(PictureFileForm, can_delete=True) class FileForm(forms.Form): raw_file = forms.FileField(label=_(u"File")) def clean_raw_file(self): data = self.cleaned_data['raw_file'] if '.' not in data.name or \ data.name.split('.')[-1].lower() not in ('kml', 'gpx'): raise forms.ValidationError(_(u"Bad file format: this must be a "\ u"GPX or KML file")) return data class FullFileForm(FileForm): name = forms.CharField(label=_(u"Name"), max_length=150) def __init__(self, *args, **kwargs): super(FullFileForm, self).__init__(*args, **kwargs) self.fields.keyOrder = ['name', 'raw_file'] class AreaAdminForm(forms.ModelForm): """ Admin page to create an area """ area = AreaField(label=_("Area"), fields=(PointField(), PointField())) class Meta: model = Area def __init__(self, *args, **keys): """ Custom initialization method in order to manage area """ if 'instance' in keys and keys['instance']: instance = keys['instance'] dct = {'area':(instance.upper_left_corner, instance.lower_right_corner)} if 'initial' in keys: keys['initial'].update(dct) else: keys['initial'] = dct super(AreaAdminForm, self).__init__(*args, **keys) def save(self, *args, **keys): """ Custom save method in order to manage area """ new_area = super(AreaAdminForm, self).save(*args, **keys) area = self.cleaned_data['area'] new_area.upper_left_corner = 'POINT(%s %s)' % (area[0][0], area[0][1]) new_area.lower_right_corner = 'POINT(%s %s)' % (area[1][0], area[1][1]) content_type = ContentType.objects.get(app_label="chimere", model="area") if new_area.urn: mnemo = 'change_area_' + new_area.urn perm = Permission.objects.filter(codename=mnemo) if not perm: perm = Permission(name='Can change ' + new_area.name, content_type_id=content_type.id, codename=mnemo) perm.save() else: if 'urn' in self.initial: mnemo = 'change_area_' + self.initial['urn'] perm = Permission.objects.filter(codename=mnemo) if perm: perm[0].delete() return new_area class AreaForm(AreaAdminForm): """ Form for the edit page """ class Meta: model = Area