diff options
Diffstat (limited to 'chimere/models.py')
| -rw-r--r-- | chimere/models.py | 316 | 
1 files changed, 242 insertions, 74 deletions
| diff --git a/chimere/models.py b/chimere/models.py index 77fdaf2..15d264b 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 @@ -506,6 +511,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) @@ -575,6 +582,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") @@ -656,6 +667,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          ''' @@ -673,11 +710,28 @@ class Marker(GeographicItem):                       'icon_hover_path':cat.hover_icon.image \                                         if cat.hover_icon else '',                       'category_name':cat.name}) +            items['weight'] = '' +            if cat.weighted: +                if not self.weight: +                    continue +                items['weight'] = self.weight +                if cat.color_theme and cat.color_theme.colors.count(): +                    items['colors'] += ["#%s"] % '", "#'.join( +                        [color.code for color in cat.color_theme.colors.\ +                                                     order_by('order').all()])              try:                  items['properties'].update({'icon_width':cat.icon.image.width,                                            'icon_height':cat.icon.image.height,})              except IOError:                  pass +            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(items) @@ -691,20 +745,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): @@ -1142,6 +1196,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          ''' @@ -1282,14 +1343,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: @@ -1353,37 +1414,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() @@ -1393,13 +1460,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))" % ( @@ -1423,54 +1549,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(): @@ -1491,36 +1629,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',) @@ -1534,6 +1692,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 " @@ -1542,6 +1701,7 @@ class PropertyModel(models.Model):              ('L', _('Long text')),              ('P', _('Password')),              ('D', _("Date")), +            ('B', _("Boolean")),              ('C', _("Choices")),              ('B', _("Boolean")),              ) @@ -1560,8 +1720,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): @@ -1569,6 +1728,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      ''' @@ -1589,15 +1753,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") | 
