diff options
Diffstat (limited to 'chimere/models.py')
-rw-r--r-- | chimere/models.py | 313 |
1 files changed, 237 insertions, 76 deletions
diff --git a/chimere/models.py b/chimere/models.py index 20efb2d..4c77211 100644 --- a/chimere/models.py +++ b/chimere/models.py @@ -106,7 +106,7 @@ class News(models.Model): date = models.DateField(_(u"Date")) content = models.TextField() url = models.URLField(_(u"Url"), max_length=200, blank=True, null=True) - areas = SelectMultipleField('Area', verbose_name=_(u"Associated areas"), + maps = SelectMultipleField('Map', verbose_name=_(u"Associated maps"), blank=True, null=True) def __unicode__(self): ordering = ["-date"] @@ -172,7 +172,8 @@ class Color(models.Model): """ code = models.CharField(_(u"Code"), max_length=6) order = models.IntegerField(_(u"Order")) - color_theme = models.ForeignKey(ColorTheme, verbose_name=_(u"Color theme")) + color_theme = models.ForeignKey(ColorTheme, verbose_name=_(u"Color theme"), + related_name='colors') def __unicode__(self): return self.code class Meta: @@ -183,6 +184,7 @@ class Category(models.Model): """Category of Point Of Interest (POI) """ name = models.CharField(_(u"Name"), max_length=150) + slug = models.SlugField() available = models.BooleanField(_(u"Available")) order = models.IntegerField(_(u"Order")) description = models.TextField(blank=True, null=True) @@ -211,9 +213,12 @@ class SubCategory(models.Model): category = models.ForeignKey(Category, verbose_name=_(u"Category"), related_name='subcategories') name = models.CharField(_(u"Name"), max_length=150) + slug = models.SlugField() available = models.BooleanField(_(u"Available"), default=True) submission = models.BooleanField(_(u"Available for submission"), default=True) + weighted = models.BooleanField(_(u"Has an associated quantity"), + default=False) TYPE = (('M', _(u'Marker')), ('R', _(u'Route')), ('B', _(u'Both')),) @@ -237,7 +242,7 @@ class SubCategory(models.Model): verbose_name_plural = _(u"Sub-categories") @classmethod - def getAvailable(cls, item_types=None, area_name=None, public=False): + def getAvailable(cls, item_types=None, map_name=None, public=False): '''Get list of tuples with first the category and second the associated subcategories ''' @@ -250,14 +255,14 @@ class SubCategory(models.Model): if public: subcategories = subcategories.filter(submission=True) selected_cats = [] - if area_name: - area = Area.objects.get(urn=area_name) + if map_name: + map = Map.objects.get(urn=map_name) # if there some restrictions with categories limit them - if area.subcategories.count(): - sub_ids = [sub.id for sub in area.subcategories.all()] + if map.subcategories.count(): + sub_ids = [sub.id for sub in map.subcategories.all()] subcategories = subcategories.filter(id__in=sub_ids) selected_cats = [subcat.pk - for subcat in area.default_subcategories.all()] + for subcat in map.default_subcategories.all()] for sub_category in subcategories.order_by('order'): if sub_category.category not in sub_categories: sub_categories[sub_category.category] = [] @@ -273,10 +278,10 @@ class SubCategory(models.Model): return subcategories @classmethod - def getAvailableTuples(cls, item_types=None, area_name=None): + def getAvailableTuples(cls, item_types=None, map_name=None): cats = [] for cat, subcats in cls.getAvailable(item_types=item_types, - area_name=area_name): + map_name=map_name): cats.append((unicode(cat), [(subcat.pk, subcat.name) for subcat in subcats])) return cats @@ -504,6 +509,8 @@ class Marker(GeographicItem): null=True) # used by feeds route = models.ForeignKey(u"Route", blank=True, null=True, related_name='associated_marker') + weight = models.IntegerField(_(u"Quantity"), blank=True, null=True, + default=0) description = models.TextField(_(u"Description"), blank=True, null=True) is_front_page = models.NullBooleanField(_(u"Is front page"), blank=True, null=True) @@ -573,6 +580,10 @@ class Marker(GeographicItem): def geom_attr(self): return 'point' + @property + def has_weight(self): + return bool(self.categories.filter(weighted=True).count()) + class Meta: ordering = ('status', 'name') verbose_name = _(u"Point of interest") @@ -654,6 +665,32 @@ class Marker(GeographicItem): val = values[unicode(propertymodel.id)] self.setProperty(propertymodel, val) + PROPERTIES_KEYS = ['point', 'pk', 'name', 'weight'] + @classmethod + def _getJson(cls, values, base_dct={"properties":{}}): + item = base_dct.copy() + item["geometry"] = {"type": "Point", + "coordinates": [values['point'].x, + values['point'].y]} + item["properties"]['pk'] = values['pk'] + item["properties"]['name'] = values['name'] + if values['weight']: + item["properties"]['weight'] = values['weight'] + return item + + def _getItems(self, base_dct={"properties":{}}): + '''Return a dict representation for json + ''' + item = base_dct.copy() + item["geometry"] = {"type": "Point", + "coordinates": [ self.point.x, self.point.y ] + } + item["properties"]['pk'] = self.pk + item["properties"]['name'] = self.name + if self.weight: + item["properties"]['weight'] = self.weight + return item + def getGeoJSON(self, categories_id=[]): '''Return a GeoJSON string ''' @@ -668,14 +705,24 @@ class Marker(GeographicItem): if cat.hover_icon else '', 'icon_width':cat.icon.image.width, 'icon_height':cat.icon.image.height, - 'category_name':json.dumps(cat.name)} + 'category_name':json.dumps(cat.name),} + items['weight'] = '' + if cat.weighted: + if not self.weight: + continue + items['weight'] = u', "weight":%d' % self.weight + if cat.color_theme and cat.color_theme.colors.count(): + items['weight'] += u', "colors":["#%s"]' % '", "#'.join( + [color.code for color in cat.color_theme.colors.\ + order_by('order').all()]) jsons.append(u'{"type":"Feature", "geometry":%(geometry)s, '\ u'"properties":{"pk": %(id)d, "name": %(name)s, '\ u'"icon_path":"%(icon_path)s", '\ u'"icon_hover_path":"%(icon_hover_path)s", '\ u'"icon_width":%(icon_width)d, '\ u'"icon_height":%(icon_height)d, '\ - u'"category_name":%(category_name)s}}' % items) + u'"category_name":%(category_name)s'\ + u'%(weight)s}}' % items) return ",".join(jsons) @property @@ -686,20 +733,20 @@ class Marker(GeographicItem): if cats.count(): return cats.all()[0] - def get_absolute_url(self, area_name=''): + def get_absolute_url(self, map_name=''): parameters = 'current_feature=%d' % self.id if self.default_category: parameters += '&checked_categories=%s' % self.default_category.pk urn = TinyUrl.getUrnByParameters(parameters) - area_name = area_name + '/' if area_name else '' - url = reverse('chimere:tiny', args=[area_name, urn]) + map_name = map_name + '/' if map_name else '' + url = reverse('chimere:tiny', args=[map_name, urn]) return url PRE_ATTRS = { 'Marker':('name', 'geometry', 'import_version', 'modified_since_import'), 'Route':('name', 'geometry', 'import_version', 'modified_since_import'), - 'Area':('urn', 'name'), + 'Map':('urn', 'name'), } def geometry_pre_save(cls, pre_save_geom_values): def geom_pre_save(sender, **kwargs): @@ -1137,6 +1184,13 @@ class Route(GeographicItem): properties.append(property) return properties + def _getItems(self, dct={'properties':{}}): + dct['geometry'] = { "type": "LineString", + "coordinates": [[point.x, point.y] + for point in self.route]} + dct['properties'].update({'id':self.id, 'name':self.name}) + return dct + def getGeoJSON(self, color="#000"): '''Return a GeoJSON string ''' @@ -1278,14 +1332,14 @@ class SimpleArea: return True return False - def getCategories(self, status='A', filter_available=True, area_name=None): + def getCategories(self, status='A', filter_available=True, map_name=None): """ - Get categories for this area + Get categories for this map """ wheres = [] - if area_name: + if map_name: subcategory_pks = [] - for cat, subcats in SubCategory.getAvailable(area_name=area_name): + for cat, subcats in SubCategory.getAvailable(map_name=map_name): for subcat in subcats: subcategory_pks.append(unicode(subcat.pk)) if filter_available: @@ -1349,37 +1403,43 @@ class Layer(models.Model): class Meta: verbose_name = _("Layer") -class Area(models.Model, SimpleArea): - """Rectangular area of the map +class Map(models.Model, SimpleArea): + """A map """ name = models.CharField(_(u"Name"), max_length=150) - urn = models.SlugField(_(u"Area urn"), max_length=50, blank=True, + available = models.BooleanField(_(u"Available")) + users = models.ManyToManyField(User, through='MapUsers') + urn = models.SlugField(_(u"Map urn"), max_length=50, blank=True, unique=True) welcome_message = models.TextField(_(u"Welcome message"), blank=True, 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")) - layers = SelectMultipleField(Layer, related_name='areas', - through='AreaLayers', blank=True) + default = models.BooleanField(_(u"Default map"), default=False, + help_text=_(u"Only one map is set by default")) + layers = SelectMultipleField(Layer, related_name='maps', + through='MapLayers', blank=True) 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', + subcategories = SelectMultipleField(SubCategory, related_name='maps', + blank=True, db_table='chimere_subcategory_maps', 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) + cluster = models.BooleanField(u"Clustering map (weight of items are added)", + default=False) + public_read = models.BooleanField(_(u"Public can read the map")) + public_propose = models.BooleanField(_(u"Public can propose item to the map")) + public_write = models.BooleanField(_(u"Public can write without moderation to the map")) restrict_to_extent = models.BooleanField(_(u"Restrict to the area extent"), default=False) objects = models.GeoManager() @@ -1389,13 +1449,72 @@ class Area(models.Model, SimpleArea): class Meta: ordering = ('order', 'name') - verbose_name = _("Area") + verbose_name = _("Map") + + def can_write(self, user=None): + return bool(self.getAvailable(user=user, urn=self.urn, single=True, + edit=True)) + + def can_propose(self, user=None): + return bool(self.getAvailable(user=user, urn=self.urn, single=True, + propose=True)) @classmethod - def getAvailable(cls): - '''Get available areas + def getAvailable(cls, user=None, urn=None, single=False, edit=False, + propose=False): + '''Get available maps ''' - return cls.objects.filter(available=True) + map_filter = {'available':True} + if urn: + map_filter['urn'] = urn + elif single: + map_filter['default'] = True + filters = [] + if not propose and not edit: + filters = [{'public_write':True}, + {'public_propose':True}, + {'public_read':True}] + elif propose: + filters = [{'public_write':True}, + {'public_propose':True}] + elif edit: + filters = [{'public_write':True}] + if user and user.is_authenticated(): + if not propose and not edit: + filters += [ + {'mapusers__user':user, 'mapusers__read':True}, + {'mapusers__user':user, 'mapusers__write':True}, + {'mapusers__user':user, 'mapusers__propose':True}, + {'mapgroups__group__user':user, 'mapgroups__read':True}, + {'mapgroups__group__user':user, 'mapgroups__write':True}, + {'mapgroups__group__user':user, 'mapgroups__propose':True} + ] + elif propose: + filters += [ + {'mapusers__user':user, 'mapusers__write':True}, + {'mapusers__user':user, 'mapusers__propose':True}, + {'mapgroups__group__user':user, 'mapgroups__write':True}, + {'mapgroups__group__user':user, 'mapgroups__propose':True} + ] + elif edit: + filters += [ + {'mapusers__user':user, 'mapusers__write':True}, + {'mapgroups__group__user':user, 'mapgroups__write':True}, + ] + query = None + for fltr in filters: + fltr.update(map_filter) + if not query: + query = Q(**fltr) + else: + query = query | Q(**fltr) + maps = cls.objects.filter(query).distinct() + if single: + if not maps.count(): + return + return maps.all()[0] + else: + return maps.all() def getWkt(self): return "SRID=%d;POLYGON((%f %f,%f %f,%f %f,%f %f, %f %f))" % ( @@ -1419,54 +1538,66 @@ class Area(models.Model, SimpleArea): """ return Q(route__contained=self.getWkt()) -pre_save_area_values = {} -def area_pre_save(sender, **kwargs): +pre_save_map_values = {} +def map_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) + geometry_pre_save(Map, pre_save_map_values)(sender, **kwargs) +pre_save.connect(map_pre_save, sender=Map) -def area_post_save(sender, **kwargs): +def map_post_save(sender, **kwargs): if not kwargs['instance']: return - area = kwargs['instance'] - if area.default: - defaults = Area.objects.filter(default=True).exclude(pk=area.pk) + map = kwargs['instance'] + if map.default: + defaults = Map.objects.filter(default=True).exclude(pk=map.pk) for default in defaults: default.default = False default.save() # manage permissions - 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] + old_urn, old_name = map.urn, map.name + if map.pk in pre_save_map_values: + old_urn, old_name = pre_save_map_values[map.pk] perm, old_groups, old_users = None, [], [] - if area.urn != old_urn: - oldmnemo = 'change_area_' + old_urn + + if map.urn != old_urn: + oldmnemo = 'change_map_' + old_urn old_perm = Permission.objects.filter(codename=oldmnemo) if old_perm.count(): perm = old_perm.all()[0] - perm.codename = 'change_area_' + area.urn - perm.save() - if not area.urn: - area.urn = defaultfilters.slugify(area.name) - area.save() - mnemo = 'change_area_' + area.urn + codename = 'change_map_' + map.urn + if not Permission.objects.filter(codename=codename).count(): + perm.codename = codename + perm.save() + if not map.urn: + map.urn = defaultfilters.slugify(map.name) + map.save() + + # fix old mnemo + oldmnemo = 'change_area_' + old_urn + old_perm = Permission.objects.filter(codename=oldmnemo) + if old_perm.count(): + perm = old_perm.all()[0] + perm.codename = 'change_map_' + map.urn + perm.save() + + mnemo = 'change_map_' + map.urn perm = Permission.objects.filter(codename=mnemo) - lbl = "Can change " + area.name + lbl = "Can change " + map.name if not perm.count(): content_type, created = ContentType.objects.get_or_create( - app_label="chimere", model="area") + app_label="chimere", model="map") perm = Permission(name=lbl, content_type_id=content_type.id, codename=mnemo) perm.save() else: perm = perm.all()[0] - if old_name != area.name: + if old_name != map.name: perm.name = lbl perm.save() # manage moderation group - groupname = area.name + " moderation" - if old_name != area.name: + groupname = map.name + " moderation" + if old_name != map.name: old_groupname = old_name + " moderation" old_gp = Group.objects.filter(name=old_groupname) if old_gp.count(): @@ -1487,36 +1618,56 @@ def area_post_save(sender, **kwargs): for p in Permission.objects.filter(content_type=ct).all(): group.permissions.add(p) -post_save.connect(area_post_save, sender=Area) +post_save.connect(map_post_save, sender=Map) -def get_areas_for_user(user): +def get_maps_for_user(user): """ - Getting subcats for a specific user + Getting maps for a specific user """ perms = user.get_all_permissions() - areas = set() - prefix = 'chimere.change_area_' + maps = set() + prefix = 'chimere.change_map_' for perm in perms: if perm.startswith(prefix): try: - area = Area.objects.get(urn=perm[len(prefix):]) - areas.add(area) + map = Map.objects.get(urn=perm[len(prefix):]) + maps.add(map) except ObjectDoesNotExist: pass - return areas + return maps -def get_users_by_area(area): - if not area: +def get_users_by_map(map): + if not map: return [] - perm = 'change_area_'+area.urn + perm = 'change_map_'+map.urn return User.objects.filter(Q(groups__permissions__codename=perm)| Q(user_permissions__codename=perm)).all() -class AreaLayers(models.Model): - area = models.ForeignKey(Area) +class MapUsers(models.Model): + map = models.ForeignKey(Map, related_name='mapusers') + user = models.ForeignKey(User, related_name='mapusers') + read = models.BooleanField(_(u"Can read the map")) + propose = models.BooleanField(_(u"Can propose item to the map")) + write = models.BooleanField(_(u"Can write without moderation to the map")) + class Meta: + verbose_name = _("Map - user") + verbose_name_plural = _("Map - users") + +class MapGroups(models.Model): + map = models.ForeignKey(Map, related_name='mapgroups') + group = models.ForeignKey(Group, related_name='mapgroups') + read = models.BooleanField(_(u"Can read the map")) + propose = models.BooleanField(_(u"Can propose item to the map")) + write = models.BooleanField(_(u"Can write without moderation to the map")) + class Meta: + verbose_name = _("Map - group") + verbose_name_plural = _("Map - groups") + +class MapLayers(models.Model): + map = models.ForeignKey(Map) layer = models.ForeignKey(Layer) order = models.IntegerField(_(u"Order")) - default = models.NullBooleanField(_(u"Default layer")) + default = models.BooleanField(_(u"Default layer"), default=False) class Meta: ordering = ('order',) @@ -1530,6 +1681,7 @@ class PropertyModel(models.Model): order = models.IntegerField(_(u"Order")) available = models.BooleanField(_(u"Available")) mandatory = models.BooleanField(_(u"Mandatory")) + slug = models.SlugField() 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 " @@ -1538,6 +1690,7 @@ class PropertyModel(models.Model): ('L', _('Long text')), ('P', _('Password')), ('D', _("Date")), + ('B', _("Boolean")), ('C', _("Choices")), ('B', _("Boolean")), ) @@ -1556,8 +1709,7 @@ class PropertyModel(models.Model): verbose_name = _("Property model") def getAttrName(self): - attr_name = defaultfilters.slugify(self.name) - attr_name = re.sub(r'-','_', attr_name) + attr_name = self.slug.replace('-', '_') return attr_name def getNamedId(self): @@ -1565,6 +1717,11 @@ class PropertyModel(models.Model): ''' return 'property_%d_%d' % (self.order, self.id) + def save(self, *args, **kwargs): + if not self.slug: + self.slug = defaultfilters.slugify(self.name) + super(PropertyModel, self).save(*args, **kwargs) + class PropertyModelChoice(models.Model): '''Choices for property model ''' @@ -1585,15 +1742,19 @@ 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 self.value and self.propertymodel.type == 'C': try: return unicode(PropertyModelChoice.objects.get( pk=self.value).value) - except self.DoesNotExist: + except (self.DoesNotExist, ValueError): return "" return unicode(self.value) + def label(self): + return unicode(self) + class Meta: verbose_name = _(u"Property") |