django-leaflet-storage / leaflet_storage /

Full commit
# -*- coding: utf-8 -*-

import simplejson
import os

from django.contrib.gis.db import models
from django.conf import settings
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django.utils.translation import ugettext, ugettext_lazy as _
from django.core.signing import Signer
from django.contrib import messages
from django.template.defaultfilters import slugify
from django.core.files.base import File

from .fields import DictField

class NamedModel(models.Model):
    name = models.CharField(max_length=200, verbose_name=_("name"))

    class Meta:
        abstract = True
        ordering = ('name', )

    def __unicode__(self):

class Licence(NamedModel):
    The licence one map is published on.
    details = models.URLField(
        help_text=_('Link to a page where the licence is detailed.')

    def get_default(cls):
        Returns a default Licence, creates it if it doesn't exist.
        Needed to prevent a licence deletion from deleting all the linked
        return cls.objects.get_or_create(
            # can't use ugettext_lazy for database storage, see #13965
            name=getattr(settings, "LEAFLET_STORAGE_DEFAULT_LICENCE_NAME", ugettext('No licence set'))

    def json(self):
        return {
            'url': self.details

class TileLayer(NamedModel):
    url_template = models.CharField(
        help_text=_("URL template using OSM tile format")
    minZoom = models.IntegerField(default=0)
    maxZoom = models.IntegerField(default=18)
    attribution = models.CharField(max_length=300)
    rank = models.SmallIntegerField(
        help_text=_('Order of the tilelayers in the edit box')

    def json(self):
        return dict((, getattr(self, for field in self._meta.fields)

    def get_default(cls):
        Returns the default tile layer (used for a map when no layer is set).
        return cls.objects.order_by('rank')[0]  # FIXME, make it administrable

    def get_list(cls, selected=None):
        l = []
        for t in cls.objects.all():
            fields = t.json
            if selected and ==
                fields['selected'] = True
        return l

    class Meta:
        ordering = ('rank', 'name', )

class Map(NamedModel):
    A single thematical map.
    EDITORS = 2
    OWNER = 3
        (ANONYMOUS, _('Everyone can edit')),
        (EDITORS, _('Only editors can edit')),
        (OWNER, _('Only owner can edit')),
    slug = models.SlugField(db_index=True)
    description = models.TextField(blank=True, null=True, verbose_name=_("description"))
    center = models.PointField(geography=True, verbose_name=_("center"))
    zoom = models.IntegerField(default=7, verbose_name=_("zoom"))
    locate = models.BooleanField(default=False, verbose_name=_("locate"), help_text=_("Locate user on load?"))
    licence = models.ForeignKey(
        help_text=_("Choose the map licence."),
    modified_at = models.DateTimeField(auto_now=True)
    tilelayer = models.ForeignKey(TileLayer, blank=True, null=True, related_name="maps",  verbose_name=_("background"))
    owner = models.ForeignKey(User, blank=True, null=True, related_name="owned_maps", verbose_name=_("owner"))
    editors = models.ManyToManyField(User, blank=True, verbose_name=_("editors"))
    edit_status = models.SmallIntegerField(choices=EDIT_STATUS, default=OWNER, verbose_name=_("edit status"))
    settings = DictField(blank=True, null=True, verbose_name=_("settings"))

    objects = models.GeoManager()

    def geojson(self):
        settings = self.settings
        if not "properties" in settings:
            settings["properties"] = dict(self.settings)
            settings['properties']['zoom'] = self.zoom
            settings['properties']['name'] =
            settings['properties']['description'] = self.description
            if self.tilelayer:
                settings['properties']['tilelayer'] = self.tilelayer.json
            if self.licence:
                settings['properties']['licence'] = self.licence.json
        if not "geometry" in settings:
            settings["geometry"] = simplejson.loads(
        return settings

    def get_absolute_url(self):
        return reverse("map", kwargs={'slug': self.slug, 'pk':})

    def get_anonymous_edit_url(self):
        signer = Signer()
        signature = signer.sign(
        return reverse('map_anonymous_edit_url', kwargs={'signature': signature})

    def is_anonymous_owner(self, request):
        if self.owner:
            # edit cookies are only valid while map hasn't owner
            return False
        key, value = self.signed_cookie_elements
            has_anonymous_cookie = int(request.get_signed_cookie(key, False)) == value
        except ValueError:
            has_anonymous_cookie = False
        return has_anonymous_cookie

    def can_edit(self, user=None, request=None):
        Define if a user can edit or not the instance, according to his account
        or the request.
        can = False
        if request and not self.owner:
            if (getattr(settings, "LEAFLET_STORAGE_ALLOW_ANONYMOUS", False)
                    and self.is_anonymous_owner(request)):
                can = True
                if user and user.is_authenticated():
                    # if user is authenticated, attach as owner
                    self.owner = user
                    msg = _("Your anonymous map has been attached to your account %s" % user)
          , msg)
        elif self.edit_status == self.ANONYMOUS:
            can = True
        elif not user.is_authenticated():
        elif user == self.owner:
            can = True
        elif self.edit_status == self.EDITORS and user in self.editors.all():
            can = True
        return can

    def signed_cookie_elements(self):
        return ('anonymous_owner|%s' %,

    def get_tilelayer(self):
        return self.tilelayer or TileLayer.get_default()

    def clone(self, **kwargs):
        new = self.__class__.objects.get( = None = u"%s %s" % (_("Clone of"),
        if "owner" in kwargs:
            # can be None in case of anonymous cloning
            new.owner = kwargs["owner"]
        for editor in self.editors.all():
        for datalayer in self.datalayer_set.all():
        return new

class Pictogram(NamedModel):
    An image added to an icon of the map.
    attribution = models.CharField(max_length=300)
    pictogram = models.ImageField(upload_to="pictogram")

    def json(self):
        return {
            "attribution": self.attribution,
            "src": self.pictogram.url

class DataLayer(NamedModel):
    Layer to store Features in.
    def upload_to(instance, filename):
        path = ["datalayer", str([-1]]
        if len(str( > 1:
        path.append("%s.geojson" % (slugify( or "untitled"))
        return os.path.join(*path)
    map = models.ForeignKey(Map)
    description = models.TextField(
    options = DictField(blank=True, null=True, verbose_name=_("options"))
    geojson = models.FileField(upload_to=upload_to, blank=True, null=True)
    display_on_load = models.BooleanField(
        verbose_name=_("display on load"),
        help_text=_("Display this layer on load.")

    def metadata(self):
        return {
            "displayOnLoad": self.display_on_load

    def clone(self, map_inst=None):
        new = self.__class__.objects.get( = None
        if map_inst:
   = map_inst
        new.geojson = File(new.geojson.file.file)
        return new