diff options
-rw-r--r-- | chimere/models.py | 489 |
1 files changed, 285 insertions, 204 deletions
diff --git a/chimere/models.py b/chimere/models.py index a37a548..94da386 100644 --- a/chimere/models.py +++ b/chimere/models.py @@ -20,7 +20,11 @@ """ Models description """ -import os, datetime, pyexiv2, re, string, copy +import os +import datetime +import pyexiv2 +import re +import copy import simplejson as json from lxml import etree from PIL import Image @@ -29,12 +33,10 @@ from BeautifulSoup import BeautifulSoup from django import forms from django.conf import settings -from django.contrib import admin from django.contrib.auth.models import User, Permission, ContentType, Group from django.contrib.gis.db import models -from django.contrib.gis.gdal import SpatialReference from django.core.files import File -from django.core.exceptions import ValidationError, ObjectDoesNotExist +from django.core.exceptions import ObjectDoesNotExist from django.core.urlresolvers import reverse from django.db.models import Q from django.db.models.signals import post_save, pre_save, m2m_changed @@ -42,9 +44,7 @@ from django.template import defaultfilters from django.utils.translation import ugettext_lazy as _ from chimere.widgets import HiddenPointChooserWidget, PointField, RouteField, \ - SelectMultipleField, TextareaWidget, \ - DatePickerWidget -from chimere.managers import BaseGeoManager + SelectMultipleField, TextareaWidget, DatePickerWidget from chimere.utils import KMLManager, OSMManager, ShapefileManager, \ GeoRSSManager, CSVManager, HtmlXsltManager, XMLXsltManager, JsonManager @@ -60,13 +60,16 @@ class Page(models.Model): template_path = models.CharField(_(u"Template path"), max_length=150, blank=True, null=True) content = models.TextField(blank=True, null=True) - def __unicode__(self): - ordering = ["order"] - return self.title + class Meta: + ordering = ["order"] verbose_name = _(u"Page") verbose_name_plural = _(u"Page") + def __unicode__(self): + return self.title + + def page_post_save(sender, **kwargs): if not kwargs['instance']: return @@ -76,6 +79,7 @@ def page_post_save(sender, **kwargs): page.save() post_save.connect(page_post_save, sender=Page) + def shortify(text): if not text: return '' @@ -88,15 +92,16 @@ def shortify(text): if c == '>': break if c == '<': - short_desc = desc[:-(idx+1)] + short_desc = desc[:-(idx + 1)] break if not short_desc: for idx, c in enumerate(reversed(desc)): if c == ' ' or c == '\n': - short_desc = desc[:-(idx+1)] + short_desc = desc[:-(idx + 1)] break return BeautifulSoup(short_desc).prettify() + class News(models.Model): """News of the site """ @@ -109,33 +114,38 @@ class News(models.Model): url = models.URLField(_(u"Url"), max_length=200, blank=True, null=True) areas = SelectMultipleField('Area', verbose_name=_(u"Associated areas"), blank=True, null=True) - def __unicode__(self): - ordering = ["-date"] - return self.title + class Meta: + ordering = ["-date"] verbose_name = _(u"News") verbose_name_plural = _(u"News") + def __unicode__(self): + return self.title + @property def short_desc(self): return shortify(self.content) + class TinyUrl(models.Model): """Tinyfied version of permalink parameters """ parameters = models.CharField(_(u"Parameters"), max_length=500) - def __unicode__(self): - return self.parameters - class Meta: - verbose_name = _(u"TinyUrl") digits = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" base = len(digits) + class Meta: + verbose_name = _(u"TinyUrl") + + def __unicode__(self): + return self.parameters + @classmethod def getParametersByUrn(cls, urn): c_id = 0 for idx, char in enumerate(reversed(urn)): - c_id += cls.digits.index(char)*pow(cls.base, idx) + c_id += cls.digits.index(char) * pow(cls.base, idx) try: params = cls.objects.get(id=c_id).parameters except cls.DoesNotExist: @@ -159,27 +169,34 @@ class TinyUrl(models.Model): break return urn + class ColorTheme(models.Model): """Color theme """ name = models.CharField(_(u"Name"), max_length=150) - def __unicode__(self): - return self.name + class Meta: verbose_name = _(u"Color theme") + def __unicode__(self): + return self.name + + class Color(models.Model): """Color """ code = models.CharField(_(u"Code"), max_length=6) order = models.IntegerField(_(u"Order")) color_theme = models.ForeignKey(ColorTheme, verbose_name=_(u"Color theme")) - def __unicode__(self): - return self.code + class Meta: ordering = ["order"] verbose_name = _(u"Color") + def __unicode__(self): + return self.code + + class Category(models.Model): """Category of Point Of Interest (POI) """ @@ -187,12 +204,14 @@ class Category(models.Model): available = models.BooleanField(_(u"Available")) order = models.IntegerField(_(u"Order")) description = models.TextField(blank=True, null=True) - def __unicode__(self): - return self.name + class Meta: ordering = ["order"] verbose_name = _(u"Category") + def __unicode__(self): + return self.name + class Icon(models.Model): '''Icon @@ -230,8 +249,9 @@ class SubCategory(models.Model): dated = models.BooleanField(_(u"Is dated"), default=False) description = models.TextField(blank=True, null=True) icon = models.ForeignKey(Icon, verbose_name=_(u"Icon")) - hover_icon = models.ForeignKey(Icon, verbose_name=_(u"Hover icon"), - blank=True, null=True, related_name='subcat_hovered') + hover_icon = models.ForeignKey( + Icon, verbose_name=_(u"Hover icon"), blank=True, null=True, + related_name='subcat_hovered') color_theme = models.ForeignKey(ColorTheme, verbose_name=_(u"Color theme"), blank=True, null=True) as_layer = models.BooleanField(_(u"Displayed in the layer menu"), @@ -239,14 +259,16 @@ class SubCategory(models.Model): routing_warn = models.BooleanField(_(u"Routing warn"), default=False) order = models.IntegerField(_(u"Order"), default=1000) keywords = models.TextField(_(u"Keywords"), max_length=200, - blank=True, null=True) - def __unicode__(self): - return u"%s / %s" % (self.category.name, self.name) + blank=True, null=True) + class Meta: ordering = ["category", "order"] verbose_name = _(u"Sub-category") verbose_name_plural = _(u"Sub-categories") + def __unicode__(self): + return u"%s / %s" % (self.category.name, self.name) + @classmethod def getAvailable(cls, item_types=None, area_name=None, public=False): '''Get list of tuples with first the category and second the associated @@ -277,8 +299,8 @@ class SubCategory(models.Model): sub_category.category.selected = True sub_categories[sub_category.category].append(sub_category) - subcategories = [(cat, subcats) \ - for cat, subcats in sub_categories.items()] + subcategories = [(cat, subcats) + for cat, subcats in sub_categories.items()] get_cat_order = lambda cat_tuple: cat_tuple[0].order subcategories = sorted(subcategories, key=get_cat_order) return subcategories @@ -303,7 +325,7 @@ class SubCategory(models.Model): 'offset_y': self.icon.offset_y, 'popup_offset_x': self.icon.popup_offset_x, 'popup_offset_y': self.icon.popup_offset_y} - } + } if self.hover_icon: items['icon_hover'] = {'url': self.hover_icon.image.url} @@ -380,10 +402,10 @@ class Importer(models.Model): source = models.CharField(_(u"Web address"), max_length=200, blank=True, null=True, help_text=_(u"Don't forget the trailing slash")) - source_file = models.FileField(_(u"Source file"), - upload_to='import_files', blank=True, null=True) - source_file_alt = models.FileField(_(u"Alt source file"), - upload_to='import_files', blank=True, null=True) + source_file = models.FileField( + _(u"Source file"), upload_to='import_files', blank=True, null=True) + source_file_alt = models.FileField( + _(u"Alt source file"), upload_to='import_files', blank=True, null=True) default_name = models.CharField(_(u"Name by default"), max_length=200, blank=True, null=True) srid = models.IntegerField(_(u"SRID"), blank=True, null=True) @@ -398,19 +420,20 @@ class Importer(models.Model): blank=True, null=True) license = models.CharField(_(u"License"), max_length=1000, blank=True, null=True) - categories = SelectMultipleField(SubCategory, blank=True, null=True, - verbose_name=_(u"Associated subcategories")) + categories = SelectMultipleField( + SubCategory, blank=True, null=True, + verbose_name=_(u"Associated subcategories")) state = models.TextField(_(u"State"), blank=True, null=True) - associate_marker_to_way = models.BooleanField(_(u"Automatically associate "\ - u"a marker to a way"), default=False) + associate_marker_to_way = models.BooleanField( + _(u"Automatically associate a marker to a way"), default=False) automatic_update = models.BooleanField(_(u"Automatically updated"), default=False) default_status = models.CharField(_(u"Default status"), max_length=1, choices=STATUS, default='I') - default_localisation = PointField(_(u"Default localisation"), - srid=settings.CHIMERE_EPSG_DISPLAY_PROJECTION, - blank=True, null=True, - widget=HiddenPointChooserWidget) + default_localisation = PointField( + _(u"Default localisation"), + srid=settings.CHIMERE_EPSG_DISPLAY_PROJECTION, blank=True, null=True, + widget=HiddenPointChooserWidget) class Meta: verbose_name = _(u"Importer") @@ -456,47 +479,51 @@ class ImporterKeyCategories(models.Model): class Meta: verbose_name = _(u"Importer - Key categories") + class GeographicItem(models.Model): name = models.CharField(_(u"Name"), max_length=150) categories = SelectMultipleField(SubCategory) - submiter_session_key = models.CharField(_(u"Submitter session key"), - blank=True, null=True, max_length=40) + submiter_session_key = models.CharField( + _(u"Submitter session key"), blank=True, null=True, max_length=40) submiter_name = models.CharField(_(u"Submitter name or nickname"), blank=True, null=True, max_length=40) submiter_email = models.EmailField(_(u"Submitter email"), blank=True, null=True) - submiter_comment = models.TextField(_(u"Submitter comment"), max_length=200, - blank=True, null=True) + submiter_comment = models.TextField(_(u"Submitter comment"), + max_length=200, blank=True, null=True) status = models.CharField(_(u"Status"), max_length=1, choices=STATUS) - keywords = models.TextField(_(u"Keywords"), max_length=200, - blank=True, null=True) + keywords = models.TextField(_(u"Keywords"), max_length=200, blank=True, + null=True) import_key = models.CharField(_(u"Import key"), max_length=200, blank=True, null=True) import_version = models.IntegerField(_(u"Import version"), blank=True, null=True) import_source = models.CharField(_(u"Source"), max_length=200, blank=True, null=True) - modified_since_import = models.BooleanField(_(u"Modified since last import"), - default=True) + modified_since_import = models.BooleanField( + _(u"Modified since last import"), default=True) not_for_osm = models.BooleanField(_(u"Not to be exported to OSM"), default=False) origin = models.CharField(_(u"Origin"), max_length=1000, blank=True, null=True) license = models.CharField(_(u"License"), max_length=1000, blank=True, null=True) - start_date = models.DateField(_(u"Start date"), blank=True, null=True, - help_text=_(u"Not mandatory. Set it for dated item such as event. "\ + start_date = models.DateField( + _(u"Start date"), blank=True, null=True, + help_text=_(u"Not mandatory. Set it for dated item such as event. " u"Format YYYY-MM-DD")) - end_date = models.DateField(_(u"End date"), blank=True, null=True, - help_text=_(u"Not mandatory. Set it only if you have a multi-day "\ + end_date = models.DateField( + _(u"End date"), blank=True, null=True, + help_text=_(u"Not mandatory. Set it only if you have a multi-day " u"event. Format YYYY-MM-DD")) + class Meta: abstract = True def get_key(self, key): key_vals = self.import_key.split(';') for k_v in key_vals: - if k_v.startswith(key+':'): + if k_v.startswith(key + ':'): return k_v.split(':')[1] def set_key(self, key, value): @@ -523,8 +550,8 @@ class GeographicItem(models.Model): def has_modified(self): if (self.ref_item and self.ref_item != self) \ - or self.__class__.objects.filter(ref_item=self - ).exclude(pk=self.pk).count(): + or self.__class__.objects.filter( + ref_item=self).exclude(pk=self.pk).count(): return True return False @@ -536,6 +563,7 @@ class GeographicItem(models.Model): def all_properties(cls): return [pm for pm in PropertyModel.objects.all()] + def property_setter(cls, propertymodel): def setter(self, value): marker = self @@ -546,15 +574,17 @@ def property_setter(cls, propertymodel): marker.setProperty(propertymodel, value) return setter + class Marker(GeographicItem): '''Marker for a POI ''' - ref_item = models.ForeignKey("Marker", blank=True, null=True, - verbose_name=_(u"Reference marker"), related_name='submited_marker') + ref_item = models.ForeignKey( + "Marker", blank=True, null=True, verbose_name=_(u"Reference marker"), + related_name='submited_marker') point = PointField(_(u"Localisation"), srid=settings.CHIMERE_EPSG_DISPLAY_PROJECTION) available_date = models.DateTimeField(_(u"Available Date"), blank=True, - null=True) # used by feeds + null=True) # used by feeds route = models.ForeignKey(u"Route", blank=True, null=True, related_name='associated_marker') description = models.TextField(_(u"Description"), blank=True, null=True) @@ -578,7 +608,7 @@ class Marker(GeographicItem): setattr(self, attr_name, val) if not hasattr(self, attr_name + '_set'): setattr(self, attr_name + '_set', - property_setter(self.__class__, pm)) + property_setter(self.__class__, pm)) def get_init_multi(self): multis = [forms.model_to_dict(multi) @@ -683,14 +713,13 @@ class Marker(GeographicItem): if choice.count(): value = choice.all()[0].pk else: - choice = PropertyModelChoice.objects.create(value=value, - propertymodel=pm) + choice = PropertyModelChoice.objects.create( + value=value, propertymodel=pm) value = choice.pk # new property if not properties: - new_property = Property.objects.create(marker=self, - propertymodel=pm, - value=value) + new_property = Property.objects.create( + marker=self, propertymodel=pm, value=value) new_property.save() else: property = properties[0] @@ -758,11 +787,13 @@ class Marker(GeographicItem): return url PRE_ATTRS = { - 'Marker':('name', 'description', 'start_date', 'geometry', 'import_version', - 'modified_since_import'), - 'Route':('name', 'geometry', 'import_version', 'modified_since_import'), - 'Area':('urn', 'name'), - } + 'Marker': ('name', 'description', 'start_date', 'geometry', + 'import_version', 'modified_since_import'), + 'Route': ('name', 'geometry', 'import_version', 'modified_since_import'), + 'Area': ('urn', 'name'), +} + + def geometry_pre_save(cls, pre_save_geom_values): def geom_pre_save(sender, **kwargs): if not kwargs['instance'] or not kwargs['instance'].pk: @@ -778,12 +809,15 @@ def geometry_pre_save(cls, pre_save_geom_values): return geom_pre_save pre_save_marker_values = {} + + def marker_pre_save(sender, **kwargs): if not kwargs['instance']: return geometry_pre_save(Marker, pre_save_marker_values)(sender, **kwargs) pre_save.connect(marker_pre_save, sender=Marker) + def geometry_post_save(pre_save_geom_values): def geom_post_save(sender, **kwargs): if not kwargs['instance'] \ @@ -801,18 +835,21 @@ def geometry_post_save(pre_save_geom_values): return if instance.modified_since_import: return - if [key for key in pre if pre not in ('import_version', - 'modified_since_import') and - getattr(instance, key) != pre[key]]: + if [key for key in pre if pre not in ( + 'import_version', 'modified_since_import') and + getattr(instance, key) != pre[key]]: instance.modified_since_import = True instance.save() return geom_post_save + + def marker_post_save(sender, **kwargs): if not kwargs['instance'] or kwargs['created']: return geometry_post_save(pre_save_marker_values)(sender, **kwargs) post_save.connect(marker_post_save, sender=Marker) + class MultimediaType(models.Model): MEDIA_TYPES = (('A', _(u"Audio")), ('V', _(u"Video")), @@ -848,25 +885,27 @@ class MultimediaType(models.Model): return tuples IFRAME_LINKS = { - 'youtube':((re.compile(r'youtube.com\/watch\?[A-Za-z0-9_\-\=\&]*v=([A-Za-z0-9_-]*)[A-Za-z0-9_\-\=\&]*'), - re.compile(r'youtu.be\/([A-Za-z0-9_-]*)'), - re.compile(r'youtube.com\/embed\/([A-Za-z0-9_-]*)')), - "http://www.youtube.com/embed/%s"), - 'dailymotion':( + 'youtube': ((re.compile(r'youtube.com\/watch\?[A-Za-z0-9_\-\=\&]*v=' + r'([A-Za-z0-9_-]*)[A-Za-z0-9_\-\=\&]*'), + re.compile(r'youtu.be\/([A-Za-z0-9_-]*)'), + re.compile(r'youtube.com\/embed\/([A-Za-z0-9_-]*)')), + "http://www.youtube.com/embed/%s"), + 'dailymotion': ( (re.compile(r'dailymotion.com/video/([A-Za-z0-9]*)_[A-Za-z0-9_-]*'), re.compile(r'dailymotion.com/embed/video/([A-Za-z0-9]*)'), re.compile("http://www.dailymotion.com/embed/video/%s")), - 'http://www.dailymotion.com/embed/video/%s'), - 'vimeo':((re.compile(r'vimeo.com/video/([A-Za-z0-9]*)'), - re.compile(r'vimeo.com/([A-Za-z0-9]*)'),), + 'http://www.dailymotion.com/embed/video/%s'), + 'vimeo': ((re.compile(r'vimeo.com/video/([A-Za-z0-9]*)'), + re.compile(r'vimeo.com/([A-Za-z0-9]*)'),), "http://player.vimeo.com/video/%s") } + class MultimediaExtension(models.Model): name = models.CharField(_(u"Extension name"), max_length=6) - multimedia_type = models.ForeignKey(MultimediaType, - verbose_name=_(u"Associated multimedia type"), - related_name='extensions') + multimedia_type = models.ForeignKey( + MultimediaType, verbose_name=_(u"Associated multimedia type"), + related_name='extensions') class Meta: verbose_name = _(u"Multimedia extension") @@ -875,13 +914,15 @@ class MultimediaExtension(models.Model): def __unicode__(self): return self.name + class MultimediaFile(models.Model): name = models.CharField(_(u"Name"), max_length=150) url = models.URLField(_(u"Url"), max_length=200) order = models.IntegerField(_(u"Order"), default=1) multimedia_type = models.ForeignKey(MultimediaType, blank=True, null=True) - miniature = models.BooleanField(_(u"Display inside the description?"), - default=settings.CHIMERE_MINIATURE_BY_DEFAULT) + miniature = models.BooleanField( + _(u"Display inside the description?"), + default=settings.CHIMERE_MINIATURE_BY_DEFAULT) marker = models.ForeignKey(Marker, related_name='multimedia_files') class Meta: @@ -891,6 +932,7 @@ class MultimediaFile(models.Model): def __unicode__(self): return self.name or u"" + def multimediafile_post_save(sender, **kwargs): if not kwargs['instance'] or not kwargs['created']: return @@ -902,7 +944,7 @@ def multimediafile_post_save(sender, **kwargs): res, embeded_url = IFRAME_LINKS[mm_type] if [r for r in res if r.search(url)]: multimedia_type = MultimediaType.objects.get( - name__iexact=mm_type) + name__iexact=mm_type) multimediafile.multimedia_type = multimedia_type if not multimediafile.multimedia_type: ext = url.split(".")[-1] @@ -912,10 +954,12 @@ def multimediafile_post_save(sender, **kwargs): else: # default to an iframe multimediafile.multimedia_type = \ - MultimediaType.objects.filter(name__iexact='iframe').all()[0] + MultimediaType.objects.filter(name__iexact='iframe')\ + .all()[0] # manage iframe of video providers if multimediafile.multimedia_type.name.lower() in IFRAME_LINKS: - regexps, lnk = IFRAME_LINKS[multimediafile.multimedia_type.name.lower()] + regexps, lnk = IFRAME_LINKS[ + multimediafile.multimedia_type.name.lower()] key = None for regexp in regexps: key = regexp.findall(multimediafile.url) @@ -925,8 +969,9 @@ def multimediafile_post_save(sender, **kwargs): if key: multimediafile.url = lnk % key - mfs = MultimediaFile.objects.filter(marker=multimediafile.marker).exclude( - pk=multimediafile.pk).order_by('order') + mfs = MultimediaFile.objects.filter(marker=multimediafile.marker)\ + .exclude(pk=multimediafile.pk)\ + .order_by('order') for idx, mf in enumerate(mfs.all()): mf.order = idx + 1 mf.save() @@ -934,18 +979,19 @@ def multimediafile_post_save(sender, **kwargs): multimediafile.save() post_save.connect(multimediafile_post_save, sender=MultimediaFile) + class PictureFile(models.Model): name = models.CharField(_(u"Name"), max_length=150) picture = models.ImageField(_(u"Image"), upload_to='pictures', height_field='height', width_field='width') height = models.IntegerField(_(u"Height"), blank=True, null=True) width = models.IntegerField(_(u"Width"), blank=True, null=True) - miniature = models.BooleanField(_(u"Display inside the description?"), - default=settings.CHIMERE_MINIATURE_BY_DEFAULT) - thumbnailfile = models.ImageField(_(u"Thumbnail"), upload_to='pictures', - blank=True, null=True, - height_field='thumbnailfile_height', - width_field='thumbnailfile_width') + miniature = models.BooleanField( + _(u"Display inside the description?"), + default=settings.CHIMERE_MINIATURE_BY_DEFAULT) + thumbnailfile = models.ImageField( + _(u"Thumbnail"), upload_to='pictures', blank=True, null=True, + height_field='thumbnailfile_height', width_field='thumbnailfile_width') thumbnailfile_height = models.IntegerField(_(u"Thumbnail height"), blank=True, null=True) thumbnailfile_width = models.IntegerField(_(u"Thumbnail width"), @@ -960,6 +1006,7 @@ class PictureFile(models.Model): verbose_name = _(u"Picture file") verbose_name_plural = _(u"Picture files") + def scale_image(max_x, pair): x, y = pair new_y = (float(max_x) / x) * y @@ -974,6 +1021,7 @@ IMAGE_EXIF_ORIENTATION_MAP = { PYEXIV2_OLD_API = not hasattr(pyexiv2, 'ImageMetadata') + def picturefile_post_save(sender, **kwargs): if not kwargs['instance']: return @@ -986,12 +1034,12 @@ def picturefile_post_save(sender, **kwargs): metadata = pyexiv2.Image(filename) metadata.readMetadata() orientation = metadata['Exif.Image.Orientation'] \ - if 'Exif.Image.Orientation' in metadata.exifKeys() else None + if 'Exif.Image.Orientation' in metadata.exifKeys() else None else: metadata = pyexiv2.ImageMetadata(filename) metadata.read() orientation = metadata['Exif.Image.Orientation'].value \ - if 'Exif.Image.Orientation' in metadata else None + if 'Exif.Image.Orientation' in metadata else None if orientation and orientation in IMAGE_EXIF_ORIENTATION_MAP \ and orientation > 1: metadata['Exif.Image.Orientation'] = 1 @@ -1009,7 +1057,7 @@ def picturefile_post_save(sender, **kwargs): filehead, filetail = os.path.split(os.path.abspath(file.path)) basename, format = os.path.splitext(filetail) basename = defaultfilters.slugify(basename) - basename = re.sub(r'-','_', basename) + basename = re.sub(r'-', '_', basename) miniature = basename + '_thumb.jpg' filename = file.path miniature_filename = os.path.join(filehead, miniature) @@ -1021,12 +1069,10 @@ def picturefile_post_save(sender, **kwargs): image_x, image_y = image.size if settings.CHIMERE_THUMBS_SCALE_HEIGHT: image_y, image_x = scale_image( - settings.CHIMERE_THUMBS_SCALE_HEIGHT, - (image_y, image_x)) + settings.CHIMERE_THUMBS_SCALE_HEIGHT, (image_y, image_x)) elif settings.CHIMERE_THUMBS_SCALE_WIDTH: image_x, image_y = scale_image( - settings.CHIMERE_THUMBS_SCALE_WIDTH, - (image_x, image_y)) + settings.CHIMERE_THUMBS_SCALE_WIDTH, (image_x, image_y)) image.thumbnail([image_x, image_y], Image.ANTIALIAS) temp_image = open(miniature_filename, 'w') @@ -1043,8 +1089,8 @@ def picturefile_post_save(sender, **kwargs): if not kwargs['created']: return - pfs = PictureFile.objects.filter(marker=picturefile.marker).exclude( - pk=picturefile.pk).order_by('order') + pfs = PictureFile.objects.filter(marker=picturefile.marker)\ + .exclude(pk=picturefile.pk).order_by('order') for idx, pf in enumerate(pfs.all()): pf.order = idx + 1 pf.save() @@ -1052,12 +1098,13 @@ def picturefile_post_save(sender, **kwargs): picturefile.save() post_save.connect(picturefile_post_save, sender=PictureFile) + class RouteFile(models.Model): name = models.CharField(_(u"Name"), max_length=150) raw_file = models.FileField(_(u"Raw file (gpx or kml)"), upload_to='route_files') - simplified_file = models.FileField(_(u"Simplified file"), - upload_to='route_files', blank=True, null=True) + simplified_file = models.FileField( + _(u"Simplified file"), upload_to='route_files', blank=True, null=True) TYPE = (('K', _(u'KML')), ('G', _(u'GPX'))) file_type = models.CharField(max_length=1, choices=TYPE) @@ -1074,7 +1121,7 @@ class RouteFile(models.Model): return input_name = settings.MEDIA_ROOT + self.raw_file.name output_name = settings.MEDIA_ROOT + self.raw_file.name[:-4] + \ - "_simplified" + ".gpx" + "_simplified" + ".gpx" cli_args = [settings.GPSBABEL, '-i'] if self.file_type == 'K': cli_args.append('kml') @@ -1086,7 +1133,7 @@ class RouteFile(models.Model): p.wait() if p.returncode: print p.stderr.read() - #logger.error(p.stderr.read()) + # logger.error(p.stderr.read()) else: self.simplified_file = File(open(output_name)) self.save() @@ -1104,23 +1151,24 @@ class RouteFile(models.Model): if not pt.tag.endswith('trkpt'): continue pts.append((pt.get("lon"), pt.get("lat"))) - geojson_tpl = u'{"type":"Feature", "geometry":{ "type": "LineString", '\ - '"coordinates":[%s]}}' wkt_tpl = u'LINESTRING(%s)' - return wkt_tpl % u','.join([u'%s %s' % (pt[0], pt[1]) \ - for pt in pts]) + return wkt_tpl % u','.join([u'%s %s' % (pt[0], pt[1]) + for pt in pts]) + class Route(GeographicItem): '''Route on the map ''' - ref_item = models.ForeignKey("Route", blank=True, null=True, - verbose_name=_(u"Reference route"), related_name='submited_route') + ref_item = models.ForeignKey( + "Route", blank=True, null=True, verbose_name=_(u"Reference route"), + related_name='submited_route') route = RouteField(_(u"Route"), srid=settings.CHIMERE_EPSG_DISPLAY_PROJECTION) associated_file = models.ForeignKey(RouteFile, blank=True, null=True, verbose_name=_(u"Associated file")) - picture = models.ImageField(_(u"Image"), upload_to='upload', blank=True, - null=True, height_field='height', width_field='width') + picture = models.ImageField( + _(u"Image"), upload_to='upload', blank=True, null=True, + height_field='height', width_field='width') height = models.IntegerField(_(u"Height"), blank=True, null=True) width = models.IntegerField(_(u"Width"), blank=True, null=True) has_associated_marker = models.BooleanField(_(u"Has an associated marker"), @@ -1154,7 +1202,7 @@ class Route(GeographicItem): setattr(self, attr_name, val) if not hasattr(self, attr_name + '_set'): setattr(self, attr_name + '_set', - property_setter(self.__class__, pm)) + property_setter(self.__class__, pm)) @property def geometry(self): @@ -1167,8 +1215,10 @@ class Route(GeographicItem): def get_init_multi(self): if not self.associated_marker.count(): return [] - multis = [forms.model_to_dict(multi) - for multi in self.associated_marker.all()[0].multimedia_files.all()] + multis = [ + forms.model_to_dict(multi) + for multi in self.associated_marker.all()[0].multimedia_files.all() + ] return multis def get_init_picture(self): @@ -1206,24 +1256,27 @@ class Route(GeographicItem): ''' if '#' not in color: color = '#' + color - attributes = {"type":"Feature", - "geometry":json.loads(self.route.geojson), - "properties":{"pk":self.id, "name":self.name, - "color":color}} + attributes = {"type": "Feature", + "geometry": json.loads(self.route.geojson), + "properties": {"pk": self.id, "name": self.name, + "color": color}} return json.dumps(attributes) def getTinyUrl(self): - parameters = 'current_feature=%d&checked_categories=%s' % (self.id, - self.categories[0].id) + parameters = 'current_feature=%d&checked_categories=%s' % ( + self.id, self.categories[0].id) return TinyUrl.getUrnByParameters(parameters) pre_save_route_values = {} + + def route_pre_save(sender, **kwargs): if not kwargs['instance']: return geometry_pre_save(Route, pre_save_route_values)(sender, **kwargs) pre_save.connect(route_pre_save, sender=Route) + def route_post_save(sender, **kwargs): if not kwargs['instance']: return @@ -1234,10 +1287,11 @@ def route_post_save(sender, **kwargs): if instance.has_associated_marker: marker_fields = [f.attname for f in Marker._meta.fields] route_fields = [f.attname for f in Route._meta.fields] - marker_dct = dict([(k, getattr(instance, k)) for k in marker_fields - if k in route_fields and k not in ('id', 'ref_item_id')]) - marker_dct['point'] = "SRID=%d;POINT(%f %f)" % (instance.route.srid, - instance.route[0][0], instance.route[0][1]) + marker_dct = dict( + [(k, getattr(instance, k)) for k in marker_fields + if k in route_fields and k not in ('id', 'ref_item_id')]) + marker_dct['point'] = "SRID=%d;POINT(%f %f)" % ( + instance.route.srid, instance.route[0][0], instance.route[0][1]) marker, created = Marker.objects.get_or_create(route=instance, defaults=marker_dct) if not created: @@ -1257,6 +1311,7 @@ def route_post_save(sender, **kwargs): post_save.connect(route_post_save, sender=Route) + def sync_m2m_route(sender, **kwargs): if kwargs['action'] not in ('post_add', 'post_clear', 'post_remove'): return @@ -1272,6 +1327,7 @@ def sync_m2m_route(sender, **kwargs): marker.categories.add(cat) m2m_changed.connect(sync_m2m_route, sender=Route.categories.through) + def getDateCondition(): ''' Return an SQL condition for apparition of dates @@ -1279,9 +1335,9 @@ def getDateCondition(): if not settings.CHIMERE_DAYS_BEFORE_EVENT: return "" now = datetime.datetime.now().strftime('%Y-%m-%d') - after = (datetime.datetime.now() + \ - datetime.timedelta(settings.CHIMERE_DAYS_BEFORE_EVENT) - ).strftime('%Y-%m-%d') + after = (datetime.datetime.now() + + datetime.timedelta(settings.CHIMERE_DAYS_BEFORE_EVENT)).strftime( + '%Y-%m-%d') date_condition = " and (%(alias)s.start_date is null or " date_condition += "(%(alias)s.start_date >= '" + now + "' and " date_condition += "%(alias)s.start_date <='" + after + "')" @@ -1289,6 +1345,7 @@ def getDateCondition(): date_condition += "%(alias)s.end_date >='" + now + "')) " return date_condition + class AggregatedRoute(models.Model): ''' Database view for aggregated routes @@ -1296,6 +1353,7 @@ class AggregatedRoute(models.Model): route = models.MultiLineStringField() subcategory = models.ForeignKey(SubCategory) status = models.CharField(_(u"Status"), max_length=1, choices=STATUS) + class Meta: managed = False db_table = 'chimere_aggregated_routes' @@ -1305,11 +1363,13 @@ class AggregatedRoute(models.Model): ''' if '#' not in color: color = '#' + color - attributes = {'color':color, 'geometry':json.loads(self.route.geojson), - 'type':"Feature", "properties":{"pk": self.id, - "name": u'Aggregated route',}} + attributes = { + 'color': color, 'geometry': json.loads(self.route.geojson), + 'type': "Feature", "properties": { + "pk": self.id, "name": u'Aggregated route'}} return json.dumps(attributes) + class SimplePoint: """ Point in the map (not in the database) @@ -1317,6 +1377,7 @@ class SimplePoint: def __init__(self, x, y): self.x, self.y = x, y + class SimpleArea: """ Rectangular area of a map (not in the database) @@ -1352,7 +1413,7 @@ class SimpleArea: for subcat in subcats: subcategory_pks.append(unicode(subcat.pk)) if filter_available: - wheres += ['subcat.available = TRUE', 'cat.available = TRUE'] + wheres += ['subcat.available = TRUE', 'cat.available = TRUE'] wheres += ['subcat.id in (%s)' % ",".join(subcategory_pks)] where = " where " + " and ".join(wheres) if wheres else "" @@ -1362,13 +1423,13 @@ class SimpleArea: elif status: equal_status = " in ('%s')" % "','".join(status) area = u"ST_GeometryFromText('POLYGON((%f %f,%f %f,%f %f,%f %f, %f %f"\ - u"))', %d)" % (self.upper_left_corner.x, self.upper_left_corner.y, - self.lower_right_corner.x, self.upper_left_corner.y, - self.lower_right_corner.x, self.lower_right_corner.y, - self.upper_left_corner.x, self.lower_right_corner.y, - self.upper_left_corner.x, self.upper_left_corner.y, - settings.CHIMERE_EPSG_DISPLAY_PROJECTION - ) + u"))', %d)" % ( + self.upper_left_corner.x, self.upper_left_corner.y, + self.lower_right_corner.x, self.upper_left_corner.y, + self.lower_right_corner.x, self.lower_right_corner.y, + self.upper_left_corner.x, self.lower_right_corner.y, + self.upper_left_corner.x, self.upper_left_corner.y, + settings.CHIMERE_EPSG_DISPLAY_PROJECTION) date_condition = getDateCondition() sql_main = '''select subcat.id as id, subcat.category_id as category_id, subcat.name as name, subcat.available as available, @@ -1380,10 +1441,10 @@ class SimpleArea: inner join chimere_marker mark on ST_Contains(%s, mark.point)''' % area if equal_status: sql += ' and mark.status' + equal_status - sql += date_condition % {'alias':'mark'} + sql += date_condition % {'alias': 'mark'} sql += ''' - inner join chimere_marker_categories mc on mc.subcategory_id=subcat.id and - mc.marker_id=mark.id''' + inner join chimere_marker_categories mc on mc.subcategory_id=subcat.id + and mc.marker_id=mark.id''' sql += where subcats = set(SubCategory.objects.raw(sql)) sql = sql_main + ''' @@ -1391,17 +1452,18 @@ class SimpleArea: ST_Contains(%s, rt.route))''' % (area, area) if equal_status: sql += ' and rt.status' + equal_status - sql += date_condition % {'alias':'rt'} + sql += date_condition % {'alias': 'rt'} sql += ''' - inner join chimere_route_categories rc on rc.subcategory_id=subcat.id and - rc.route_id=rt.id''' + inner join chimere_route_categories rc on rc.subcategory_id=subcat.id + and rc.route_id=rt.id''' sql += where - # subcats.union(set(SubCategory.objects.raw(sql))) + # subcats.union(set(SubCategory.objects.raw(sql))) # set union behave strangely. Doing it manualy... for c in set(SubCategory.objects.raw(sql)): subcats.add(c) return subcats + class Layer(models.Model): name = models.CharField(_(u"Name"), max_length=150) layer_code = models.TextField(_(u"Layer code"), max_length=300) @@ -1412,6 +1474,7 @@ class Layer(models.Model): class Meta: verbose_name = _("Layer") + class Area(models.Model, SimpleArea): """Rectangular area of the map """ @@ -1422,25 +1485,29 @@ class Area(models.Model, SimpleArea): null=True) order = models.IntegerField(_(u"Order"), unique=True) available = models.BooleanField(_(u"Available")) - upper_left_corner = models.PointField(_(u"Upper left corner"), - default='POINT(0 0)', srid=settings.CHIMERE_EPSG_DISPLAY_PROJECTION) - lower_right_corner = models.PointField(_(u"Lower right corner"), - default='POINT(0 0)', srid=settings.CHIMERE_EPSG_DISPLAY_PROJECTION) - default = models.NullBooleanField(_(u"Default area"), - help_text=_(u"Only one area is set by default")) + upper_left_corner = models.PointField( + _(u"Upper left corner"), default='POINT(0 0)', + srid=settings.CHIMERE_EPSG_DISPLAY_PROJECTION) + lower_right_corner = models.PointField( + _(u"Lower right corner"), default='POINT(0 0)', + srid=settings.CHIMERE_EPSG_DISPLAY_PROJECTION) + default = models.NullBooleanField( + _(u"Default area"), help_text=_(u"Only one area is set by default")) layers = SelectMultipleField(Layer, related_name='areas', through='AreaLayers', blank=True) - default_subcategories = SelectMultipleField(SubCategory, blank=True, - verbose_name=_(u"Sub-categories checked by default")) + default_subcategories = SelectMultipleField( + SubCategory, blank=True, + verbose_name=_(u"Sub-categories checked by default")) dynamic_categories = models.NullBooleanField( _(u"Sub-categories dynamicaly displayed"), - help_text=_(u"If checked, categories are only displayed in the menu if " - u"they are available on the current extent.")) - subcategories = SelectMultipleField(SubCategory, related_name='areas', - blank=True, db_table='chimere_subcategory_areas', - verbose_name=_(u"Restricted to theses sub-categories"), - help_text=_(u"If no sub-category is set all sub-categories are " - u"available")) + help_text=_(u"If checked, categories are only displayed in the menu " + u"if they are available on the current extent.")) + subcategories = SelectMultipleField( + SubCategory, related_name='areas', + blank=True, db_table='chimere_subcategory_areas', + verbose_name=_(u"Restricted to theses sub-categories"), + help_text=_(u"If no sub-category is set all sub-categories are " + u"available")) external_css = models.URLField(_(u"Link to an external CSS"), blank=True, null=True) restrict_to_extent = models.BooleanField(_(u"Restrict to the area extent"), @@ -1462,13 +1529,13 @@ class Area(models.Model, SimpleArea): def getWkt(self): return "SRID=%d;POLYGON((%f %f,%f %f,%f %f,%f %f, %f %f))" % ( - settings.CHIMERE_EPSG_DISPLAY_PROJECTION, - self.upper_left_corner.x, self.upper_left_corner.y, - self.lower_right_corner.x, self.upper_left_corner.y, - self.lower_right_corner.x, self.lower_right_corner.y, - self.upper_left_corner.x, self.lower_right_corner.y, - self.upper_left_corner.x, self.upper_left_corner.y, - ) + settings.CHIMERE_EPSG_DISPLAY_PROJECTION, + self.upper_left_corner.x, self.upper_left_corner.y, + self.lower_right_corner.x, self.upper_left_corner.y, + self.lower_right_corner.x, self.lower_right_corner.y, + self.upper_left_corner.x, self.lower_right_corner.y, + self.upper_left_corner.x, self.upper_left_corner.y, + ) def getIncludeMarker(self): """ @@ -1483,12 +1550,15 @@ class Area(models.Model, SimpleArea): return Q(route__contained=self.getWkt()) pre_save_area_values = {} + + def area_pre_save(sender, **kwargs): if not kwargs['instance']: return geometry_pre_save(Area, pre_save_area_values)(sender, **kwargs) pre_save.connect(area_pre_save, sender=Area) + def area_post_save(sender, **kwargs): if not kwargs['instance']: return @@ -1502,7 +1572,7 @@ def area_post_save(sender, **kwargs): old_urn, old_name = area.urn, area.name if area.pk in pre_save_area_values: old_urn, old_name = pre_save_area_values[area.pk] - perm, old_groups, old_users = None, [], [] + perm = None if area.urn != old_urn: oldmnemo = 'change_area_' + old_urn old_perm = Permission.objects.filter(codename=oldmnemo) @@ -1518,7 +1588,7 @@ def area_post_save(sender, **kwargs): lbl = "Can change " + area.name if not perm.count(): content_type, created = ContentType.objects.get_or_create( - app_label="chimere", model="area") + app_label="chimere", model="area") perm = Permission(name=lbl, content_type_id=content_type.id, codename=mnemo) perm.save() @@ -1545,13 +1615,14 @@ def area_post_save(sender, **kwargs): ('chimere', 'multimediafile'), ('chimere', 'picturefile'), ('chimere', 'routefile')): - ct, created = ContentType.objects.get_or_create(app_label=app_label, - model=model) + ct, created = ContentType.objects.get_or_create( + app_label=app_label, model=model) for p in Permission.objects.filter(content_type=ct).all(): group.permissions.add(p) post_save.connect(area_post_save, sender=Area) + def get_areas_for_user(user): """ Getting subcats for a specific user @@ -1568,12 +1639,14 @@ def get_areas_for_user(user): pass return areas + def get_users_by_area(area): if not area: return [] - perm = 'change_area_'+area.urn - return User.objects.filter(Q(groups__permissions__codename=perm)| - Q(user_permissions__codename=perm)).all() + perm = 'change_area_' + area.urn + return User.objects.filter(Q(groups__permissions__codename=perm) | + Q(user_permissions__codename=perm)).all() + class AreaLayers(models.Model): area = models.ForeignKey(Area) @@ -1586,6 +1659,7 @@ class AreaLayers(models.Model): verbose_name = _("Layers") verbose_name_plural = _("Layers") + class PropertyModel(models.Model): '''Model for a property ''' @@ -1593,10 +1667,11 @@ class PropertyModel(models.Model): order = models.IntegerField(_(u"Order")) available = models.BooleanField(_(u"Available")) mandatory = models.BooleanField(_(u"Mandatory")) - subcategories = SelectMultipleField(SubCategory, related_name='properties', - blank=True, verbose_name=_(u"Restricted to theses sub-categories"), - help_text=_(u"If no sub-category is set all the property applies to all " - u"sub-categories")) + subcategories = SelectMultipleField( + SubCategory, related_name='properties', + blank=True, verbose_name=_(u"Restricted to theses sub-categories"), + help_text=_(u"If no sub-category is set all the property applies to " + u"all sub-categories")) TYPE = (('T', _('Text')), ('L', _('Long text')), ('P', _('Password')), @@ -1604,23 +1679,25 @@ class PropertyModel(models.Model): ('C', _("Choices")), ('B', _("Boolean")), ) - TYPE_WIDGET = {'T':forms.TextInput, - 'L':TextareaWidget, - 'P':forms.PasswordInput, - 'D':DatePickerWidget, - 'C':forms.Select, - 'B':forms.CheckboxInput, + TYPE_WIDGET = {'T': forms.TextInput, + 'L': TextareaWidget, + 'P': forms.PasswordInput, + 'D': DatePickerWidget, + 'C': forms.Select, + 'B': forms.CheckboxInput, } type = models.CharField(_(u"Type"), max_length=1, choices=TYPE) - def __unicode__(self): - return self.name + class Meta: ordering = ('order',) verbose_name = _("Property model") + def __unicode__(self): + return self.name + def getAttrName(self): attr_name = defaultfilters.slugify(self.name) - attr_name = re.sub(r'-','_', attr_name) + attr_name = re.sub(r'-', '_', attr_name) return attr_name def getNamedId(self): @@ -1628,6 +1705,7 @@ class PropertyModel(models.Model): ''' return 'property_%d_%d' % (self.order, self.id) + class PropertyModelChoice(models.Model): '''Choices for property model ''' @@ -1635,12 +1713,14 @@ class PropertyModelChoice(models.Model): verbose_name=_(u"Property model")) value = models.CharField(_(u"Value"), max_length=150) available = models.BooleanField(_(u"Available"), default=True) - def __unicode__(self): - return unicode(self.value) class Meta: verbose_name = _(u"Model property choice") + def __unicode__(self): + return unicode(self.value) + + class Property(models.Model): '''Property for a POI ''' @@ -1648,13 +1728,14 @@ class Property(models.Model): propertymodel = models.ForeignKey(PropertyModel, verbose_name=_(u"Property model")) value = models.TextField(_(u"Value")) + def __unicode__(self): if self.propertymodel.type == 'C': if not self.value: return '' try: return unicode(PropertyModelChoice.objects.get( - pk=self.value).value) + pk=self.value).value) except (self.DoesNotExist, ValueError): return "" return unicode(self.value) @@ -1666,7 +1747,8 @@ class Property(models.Model): def python_value(self): if self.propertymodel.type == 'D': try: - return datetime.date(*[int(val) for val in self.value.split('-')]) + return datetime.date(*[int(val) + for val in self.value.split('-')]) except: return "" if self.propertymodel.type == 'C' and self.value: @@ -1676,4 +1758,3 @@ class Property(models.Model): return None else: return self.value - |