#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2008-2010 É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. """ Models description """ from django.utils.translation import ugettext_lazy as _ from django.contrib.gis.db import models from django.contrib.gis.gdal import SpatialReference from django.contrib import admin from chimere import settings from chimere.main.widgets import PointField, RouteField, \ ManyToManyField_NoSyncdb class News(models.Model): """News of the site """ title = models.CharField(_("Name"), max_length=150) available = models.BooleanField(_("Available")) date = models.DateField(_("Date"), auto_now_add=True) content = models.TextField() def __unicode__(self): ordering = ["-date"] return self.title class Meta: verbose_name = _("News") class TinyUrl(models.Model): """Tinyfied version of permalink parameters """ parameters = models.CharField(_("Parameters"), max_length=500) def __unicode__(self): return self.parameters class Meta: verbose_name = _("TinyUrl") digits = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" base = len(digits) @classmethod def getParametersByUrn(cls, urn): c_id = 0 for idx, char in enumerate(urn): c_id += cls.digits.index(char)*pow(cls.base, idx) try: params = cls.objects.get(id=c_id).parameters except cls.DoesNotExist: return '' return params @classmethod def getUrnByParameters(cls, parameters): try: object = cls.objects.get(parameters=parameters) except cls.DoesNotExist: object = cls(parameters=parameters) object.save() n = object.id urn = '' while 1: idx = n % cls.base urn = cls.digits[idx] + urn n = n / cls.base if n == 0: break return urn class ColorTheme(models.Model): """Color theme """ name = models.CharField(_("Name"), max_length=150) def __unicode__(self): return self.name class Meta: verbose_name = _("Color theme") class Color(models.Model): """Color """ code = models.CharField(_("Code"), max_length=6) order = models.IntegerField(_("Order")) color_theme = models.ForeignKey(ColorTheme, verbose_name=_("Color theme")) def __unicode__(self): return self.code class Meta: ordering = ["order"] verbose_name = _("Color") class Category(models.Model): """Category of Point Of Interest (POI) """ name = models.CharField(_("Name"), max_length=150) available = models.BooleanField(_("Available")) order = models.IntegerField(_("Order")) description = models.TextField(blank=True, null=True) def __unicode__(self): return self.name class Meta: ordering = ["order"] verbose_name = _("Category") class Icon(models.Model): '''Icon ''' name = models.CharField(_("Name"), max_length=150) image = models.ImageField(_("Image"), upload_to='icons', height_field='height', width_field='width') def __unicode__(self): return self.name class Meta: verbose_name = _("Icon") class SubCategory(models.Model): '''Sub-category ''' category = models.ForeignKey(Category, verbose_name=_("Category")) name = models.CharField(_("Name"), max_length=150) available = models.BooleanField(_("Available")) areas = models.ManyToManyField('Area', related_name='areas', blank=True, null=True, db_table=u'subcategory_areas') icon = models.ForeignKey(Icon, verbose_name=_("Icon")) color_theme = models.ForeignKey(ColorTheme, verbose_name=_("Color theme"), blank=True, null=True) order = models.IntegerField(_("Order")) TYPE = (('M', _('Marker')), ('R', _('Route')), ('B', _('Both')),) item_type = models.CharField(_("Item type"), max_length=1, choices=TYPE) def __unicode__(self): return u"%s / %s" % (self.category.name, self.name) class Meta: ordering = ["category", "order"] verbose_name = _("Subcategory") @classmethod def getAvailable(cls, item_types=None, area_name=None): '''Get list of tuples with first the category and second the associated subcategories ''' sub_categories = {} subcategories = cls.objects.filter(category__available=True) if not item_types: subcategories = subcategories.filter(available=True) else: subcategories = subcategories.filter(item_type__in=item_types) if area_name: area = Area.objects.get(urn=area_name) # if there some restrictions with categories limit them if area.subcategories.count(): sub_ids = [sub.id for sub in area.subcategories.all()] # if no area is defined for a category don't filter it sub_ids += [sub.id for sub in subcategories if not sub.areas.count()] subcategories = subcategories.filter(id__in=sub_ids) for sub_category in subcategories: if sub_category.category not in sub_categories: sub_categories[sub_category.category] = [] if sub_category.id in settings.DEFAULT_CATEGORIES: sub_category.selected = True sub_category.category.selected = True sub_categories[sub_category.category].append(sub_category) return [(category, sub_cats) for category, sub_cats \ in sub_categories.items()] class Marker(models.Model): '''Marker for a POI ''' name = models.CharField(_("Name"), max_length=150) subcategory = models.ForeignKey(SubCategory, verbose_name=_("Subcategory")) point = PointField(_("Localisation")) picture = models.ImageField(_("Image"), upload_to='upload', blank=True, height_field='height', width_field='width') STATUS = (('S', _('Submited')), ('A', _('Available')), ('D', _('Disabled')),) STATUS_DCT = {} for key, label in STATUS: STATUS_DCT[key] = label status = models.CharField(_("Status"), max_length=1, choices=STATUS) objects = models.GeoManager() def __unicode__(self): return self.name class Meta: ordering = ('subcategory__category', 'subcategory', 'status', 'name') verbose_name = _("Point of interest") def getLatitude(self): '''Return the latitude ''' return self.point.y def getLongitude(self): '''Return the longitude ''' return self.point.x def getProperty(self, propertymodel, safe=None): """Get the property of an associated property model. If safe set to True, verify if the property is available """ if safe and not propertymodel.available: return try: property = Property.objects.get(propertymodel=propertymodel, marker=self) except Property.DoesNotExist: return return property def getProperties(self): """Get all the property availables """ properties = [] for pm in PropertyModel.objects.filter(available=True): property = self.getProperty(pm) if property: properties.append(property) return properties def getGeoJSON(self): '''Return a GeoJSON string ''' return """{"type":"Feature", "geometry":%(geometry)s, \ "properties":{"pk": %(id)d, "name": "%(name)s", \ "icon_path":"%(icon_path)s", "icon_width":%(icon_width)d, \ "icon_height":%(icon_height)d}}""" % {'id':self.id, 'name':self.name, 'icon_path':self.subcategory.icon.image, 'geometry':self.point.geojson, 'icon_width':self.subcategory.icon.image.width, 'icon_height':self.subcategory.icon.image.height,} class Route(models.Model): '''Route on the map ''' name = models.CharField(_("Name"), max_length=150) subcategory = models.ForeignKey(SubCategory, verbose_name=_("Subcategory")) route = RouteField(_("Route")) picture = models.ImageField(_("Image"), upload_to='upload', blank=True, height_field='height', width_field='width') STATUS = (('S', _('Submited')), ('A', _('Available')), ('D', _('Disabled')),) STATUS_DCT = {} for key, label in STATUS: STATUS_DCT[key] = label status = models.CharField(_("Status"), max_length=1, choices=STATUS) objects = models.GeoManager() def __unicode__(self): return self.name class Meta: ordering = ('subcategory__category', 'subcategory', 'status', 'name') verbose_name = _("Route") def getProperty(self, propertymodel, safe=None): """Get the property of an associated property model. If safe set to True, verify if the property is available """ if safe and not propertymodel.available: return try: property = Property.objects.get(propertymodel=propertymodel, marker=self) except Property.DoesNotExist: return return property def getProperties(self): """Get all the property availables """ properties = [] for pm in PropertyModel.objects.filter(available=True): property = self.getProperty(pm) if property: properties.append(property) return properties def getGeoJSON(self, color="#000"): '''Return a GeoJSON string ''' if '#' not in color: color = '#' + color return """{"type":"Feature", "geometry":%(geometry)s, \ "properties":{"pk": %(id)d, "name": "%(name)s", \ "color":"%(color)s"}}""" % {'id':self.id, 'name':self.name, 'color':color, 'geometry':self.route.geojson,} class SimplePoint: """ Point in the map (not in the database) """ def __init__(self, x, y): self.x, self.y = x, y class SimpleArea: """ Rectangular area of a map (not in the database) """ def __init__(self, area): """ Defining upper left corner ans lower right corner from a tuple """ assert len(area) == 4 x1, y1, x2, y2 = area self.upper_left_corner = SimplePoint(x1, y1) self.lower_right_corner = SimplePoint(x2, y2) def isIn(self, area): """ Verify if the current area is in the designated area """ if self.upper_left_corner.x >= area.upper_left_corner.x and \ self.upper_left_corner.y <= area.upper_left_corner.x and \ self.lower_right_corner.x <= area.lower_right_corner.x and \ self.lower_right_corner.y >= area.lower_right_corner.y: return True return False def getCategories(self, status='A', filter_available=True): """ Get categories for this area """ equal_status = '' if len(status) == 1: equal_status = "='%s'" % status[0] elif status: equal_status = " in ('%s')" % "','".join(status) area = "ST_GeometryFromText('POLYGON((%f %f,%f %f,%f %f,%f %f, %f %f))'\ , %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.EPSG_DISPLAY_PROJECTION ) sql_main = '''select subcat.id as id, subcat.category_id as category_id, subcat.name as name, subcat.available as available, subcat.icon_id as icon_id, subcat.color_theme_id as color_theme_id, subcat.order as order, subcat.item_type as item_type from main_subcategory subcat''' sql = sql_main + ''' inner join main_marker mark on mark.subcategory_id=subcat.id and ST_Contains(%s, mark.point)''' % area if equal_status: sql += ' and mark.status' + equal_status if filter_available: sql += ' where subcat.available = TRUE' # django > 1.1 #subcats = SubCategory.objects.raw(sql) from django.db import connection, transaction cursor = connection.cursor() cursor.execute(sql, []) subcats = set() for r in cursor.fetchall(): subcats.add(SubCategory.objects.get(id=r[0])) sql = sql_main + ''' inner join main_route rt on rt.subcategory_id=subcat.id and (ST_Intersects(%s, rt.route) or ST_Contains(%s, rt.route))''' % (area, area) if equal_status: sql += ' and rt.status' + equal_status if filter_available: sql += ' where subcat.available = TRUE' # django > 1.1 #subcats += SubCategory.objects.raw(sql) cursor.execute(sql, []) for r in cursor.fetchall(): subcats.add(SubCategory.objects.get(id=r[0])) return subcats class Area(models.Model, SimpleArea): """Rectangular area of the map """ name = models.CharField(_("Name"), max_length=150) urn = models.SlugField(_("Area urn"), max_length=50, blank=True, unique=True) subcategories = ManyToManyField_NoSyncdb(SubCategory, related_name='subcategories', blank=True, null=True, db_table=u'subcategory_areas') order = models.IntegerField(_("Order")) available = models.BooleanField(_("Available")) upper_left_corner = models.PointField(_("Upper left corner"), default='POINT(0 0)') lower_right_corner = models.PointField(_("Lower right corner"), default='POINT(0 0)') objects = models.GeoManager() def __unicode__(self): return self.name class Meta: ordering = ('order', 'name') verbose_name = _("Area") @classmethod def getAvailable(cls): '''Get available areas ''' return cls.objects.filter(available=True) def getIncludeSql(self, geometry='"main_marker".point'): """ Get the sql statement for the test if the point is included in the area """ area = "ST_GeometryFromText('POLYGON((%f %f,%f %f,%f %f,%f %f, %f %f))'\ , %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.EPSG_DISPLAY_PROJECTION ) sql = "ST_Contains(" + area + ", " + geometry + ")" return sql class PropertyModel(models.Model): '''Model for a property ''' name = models.CharField(_("Name"), max_length=150) order = models.IntegerField(_("Order")) available = models.BooleanField(_("Available")) TYPE = (('T', _('Text')), ('L', _('Long text')), ('P', _('Password'))) TYPE_WIDGET = {'T':'forms.TextInput', 'L':'TextareaWidget', 'P':'forms.PasswordInput'} type = models.CharField(_("Type"), max_length=1, choices=TYPE) def __unicode__(self): return self.name class Meta: ordering = ('order',) verbose_name = _("Property model") def getNamedId(self): '''Get the name used as named id (easily sortable) ''' return 'property_%d_%d' % (self.order, self.id) class Property(models.Model): '''Property for a POI ''' marker = models.ForeignKey(Marker, verbose_name=_("Point of interest")) propertymodel = models.ForeignKey(PropertyModel, verbose_name=_("Property model")) value = models.TextField(_("Value")) def __unicode__(self): return "%s : %s" % (str(self.propertymodel), self.value) class Meta: verbose_name = _("Property")