From f88541bedcffdfaff485ef71287be88a58c745c2 Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Wed, 15 Feb 2012 16:59:28 +0100 Subject: Large reorganization (refs #316), south migration script to new model names (refs #319) --- .gitignore | 1 + chimere/actions.py | 43 ++ chimere/admin.py | 142 ++++++ chimere/default_settings.py | 160 +------ chimere/forms.py | 331 ++++++++++++++ chimere/main/__init__.py | 0 chimere/main/actions.py | 43 -- chimere/main/admin.py | 142 ------ chimere/main/forms.py | 331 -------------- chimere/main/models.py | 612 ------------------------- chimere/main/templatetags/__init__.py | 1 - chimere/main/templatetags/sanitize.py | 31 -- chimere/main/templatetags/unlocalize_point.py | 16 - chimere/main/views.py | 470 -------------------- chimere/main/widgets.py | 354 --------------- chimere/manage.py | 11 - chimere/migrations/0001_initial.py | 341 ++++++++++++++ chimere/migrations/0002_rename_models.py | 154 +++++++ chimere/migrations/__init__.py | 0 chimere/models.py | 615 ++++++++++++++++++++++++++ chimere/rss/__init__.py | 1 - chimere/rss/feeds.py | 230 ---------- chimere/rss/templates/rss.html | 73 --- chimere/rss/templates/rss_descr.html | 8 - chimere/rss/templates/rss_title.html | 2 - chimere/rss/urls.py | 42 -- chimere/rss/views.py | 140 ------ chimere/templatetags/__init__.py | 1 + chimere/templatetags/sanitize.py | 31 ++ chimere/templatetags/unlocalize_point.py | 16 + chimere/urls.py | 82 ++-- chimere/views.py | 470 ++++++++++++++++++++ chimere/widgets.py | 357 +++++++++++++++ chimere_rss/__init__.py | 1 + chimere_rss/feeds.py | 230 ++++++++++ chimere_rss/templates/rss.html | 73 +++ chimere_rss/templates/rss_descr.html | 8 + chimere_rss/templates/rss_title.html | 2 + chimere_rss/urls.py | 42 ++ chimere_rss/views.py | 140 ++++++ example_project/__init__.py | 0 example_project/manage.py | 14 + example_project/settings.py.example | 159 +++++++ example_project/urls.py | 43 ++ 44 files changed, 3267 insertions(+), 2696 deletions(-) create mode 100644 chimere/actions.py create mode 100644 chimere/admin.py create mode 100644 chimere/forms.py delete mode 100644 chimere/main/__init__.py delete mode 100644 chimere/main/actions.py delete mode 100644 chimere/main/admin.py delete mode 100644 chimere/main/forms.py delete mode 100644 chimere/main/models.py delete mode 100644 chimere/main/templatetags/__init__.py delete mode 100644 chimere/main/templatetags/sanitize.py delete mode 100644 chimere/main/templatetags/unlocalize_point.py delete mode 100644 chimere/main/views.py delete mode 100644 chimere/main/widgets.py delete mode 100755 chimere/manage.py create mode 100644 chimere/migrations/0001_initial.py create mode 100644 chimere/migrations/0002_rename_models.py create mode 100644 chimere/migrations/__init__.py create mode 100644 chimere/models.py delete mode 100644 chimere/rss/__init__.py delete mode 100644 chimere/rss/feeds.py delete mode 100644 chimere/rss/templates/rss.html delete mode 100644 chimere/rss/templates/rss_descr.html delete mode 100644 chimere/rss/templates/rss_title.html delete mode 100644 chimere/rss/urls.py delete mode 100644 chimere/rss/views.py create mode 100644 chimere/templatetags/__init__.py create mode 100644 chimere/templatetags/sanitize.py create mode 100644 chimere/templatetags/unlocalize_point.py create mode 100644 chimere/views.py create mode 100644 chimere/widgets.py create mode 100644 chimere_rss/__init__.py create mode 100644 chimere_rss/feeds.py create mode 100644 chimere_rss/templates/rss.html create mode 100644 chimere_rss/templates/rss_descr.html create mode 100644 chimere_rss/templates/rss_title.html create mode 100644 chimere_rss/urls.py create mode 100644 chimere_rss/views.py create mode 100644 example_project/__init__.py create mode 100755 example_project/manage.py create mode 100644 example_project/settings.py.example create mode 100644 example_project/urls.py diff --git a/.gitignore b/.gitignore index b4363d3..8a081eb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.swp *.bak *.mo +*.ignore chimere/settings.py chimere/static/icons/* chimere/static/upload/* diff --git a/chimere/actions.py b/chimere/actions.py new file mode 100644 index 0000000..6f82028 --- /dev/null +++ b/chimere/actions.py @@ -0,0 +1,43 @@ +#!/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. + +""" +Actions available in the main interface +""" +from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth import models + +from chimere.settings import EXTRA_URL, EMAIL_HOST, INSTALLED_APPS + +class Action: + def __init__(self, id, path, label): + self.id, self.path, self.label = id, path, label + +actions = [(Action('view', '', _('View')), []), + (Action('contribute', 'edit', _('Contribute')), + (Action('edit', 'edit', _('Add a new point of interest')), + Action('edit_route', 'edit_route', _('Add a new route'))), + ),] + +if 'chimere.rss' in INSTALLED_APPS: + actions.append((Action('rss', 'rss', _('RSS feeds')), [])) + +if EMAIL_HOST: + actions.append((Action('contact', 'contact', _('Contact us')), []),) + diff --git a/chimere/admin.py b/chimere/admin.py new file mode 100644 index 0000000..2ad749c --- /dev/null +++ b/chimere/admin.py @@ -0,0 +1,142 @@ +#!/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. + +""" +Settings for administration pages +""" + +from chimere import settings +from chimere.models import Category, Icon, SubCategory, Marker, \ + PropertyModel, News, Route, Area, ColorTheme, Color, RouteFile +from chimere.forms import MarkerAdminForm, RouteAdminForm, AreaAdminForm,\ + NewsAdminForm, CategoryAdminForm +from chimere.widgets import TextareaWidget + +from django.contrib import admin + +def get_areas_for_user(user): + """ + Getting subcats for a specific user + """ + perms = user.get_all_permissions() + areas = set() + prefix = 'chimere.change_area_' + for perm in perms: + if perm.startswith(prefix): + area = Area.objects.get(urn=perm[len(prefix):]) + areas.add(area) + return areas + +class MarkerAdmin(admin.ModelAdmin): + """ + Specialized the Point field. + """ + search_fields = ("name",) + list_display = ('name', 'status') + list_filter = ('status', 'categories') + exclude = ['height', 'width'] + if 'chimere.rss' in settings.INSTALLED_APPS: + exclude.append('available_date') + form = MarkerAdminForm + + def queryset(self, request): + qs = self.model._default_manager.get_query_set() + if not request.user.is_superuser: + areas = get_areas_for_user(request.user) + if areas: + in_areas = " or ".join([area.getIncludeSql() for area in areas]) + qs = qs.extra(where=[in_areas]) + ordering = self.ordering or () + if ordering: + qs = qs.order_by(*ordering) + return qs + +class RouteAdmin(admin.ModelAdmin): + """ + Specialized the Route field. + """ + search_fields = ("name",) + list_display = ('name', 'status') + list_filter = ('status', 'categories') + exclude = ['height', 'width'] + form = RouteAdminForm + readonly_fields = ('associated_file',) + + def queryset(self, request): + qs = self.model._default_manager.get_query_set() + if not request.user.is_superuser: + areas = get_areas_for_user(request.user) + if areas: + in_areas = " or ".join([area.getIncludeSql( + geometry='"chimere_route".route') for area in areas]) + qs = qs.extra(where=[in_areas]) + ordering = self.ordering or () + if ordering: + qs = qs.order_by(*ordering) + return qs + +class AreaAdmin(admin.ModelAdmin): + """ + Specialized the area field. + """ + form = AreaAdminForm + exclude = ['upper_left_corner', 'lower_right_corner'] + +class SubCategoryAdmin(admin.ModelAdmin): + """ + Specialized the subcategory admin + """ + list_display = ('name', 'category', 'available') + list_filter = ('category',) + +class NewsAdmin(admin.ModelAdmin): + """ + Use the TinyMCE widget for the news content + """ + form = NewsAdminForm + +class CategoryAdmin(admin.ModelAdmin): + """ + Use the TinyMCE widget for categories + """ + form = CategoryAdminForm + +class ColorInline(admin.TabularInline): + model = Color + +class ColorThemeAdmin(admin.ModelAdmin): + inlines = [ColorInline,] + +class IconAdmin(admin.ModelAdmin): + exclude = ['height', 'width'] + +class RouteFileAdmin(admin.ModelAdmin): + list_display = ['name', 'file_type'] + +# register of differents database fields +admin.site.register(News, NewsAdmin) +admin.site.register(Category, CategoryAdmin) +admin.site.register(Icon, IconAdmin) +admin.site.register(SubCategory, SubCategoryAdmin) +admin.site.register(Marker, MarkerAdmin) +admin.site.register(RouteFile, RouteFileAdmin) +admin.site.register(Route, RouteAdmin) +admin.site.register(PropertyModel) +admin.site.register(Area, AreaAdmin) +admin.site.register(ColorTheme, ColorThemeAdmin) diff --git a/chimere/default_settings.py b/chimere/default_settings.py index 5a1d1fe..fc5177a 100644 --- a/chimere/default_settings.py +++ b/chimere/default_settings.py @@ -1,158 +1,46 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- - -# Django settings for chimere project. -PROJECT_NAME = u'Chimère' - -ROOT_PATH = '/var/local/django/chimere/' - -SERVER_URL = "http://www.peacefrogs.net/" -EXTRA_URL = 'chimere/' -BASE_URL = SERVER_URL + EXTRA_URL -EMAIL_HOST = 'localhost' - -TINYMCE_URL = SERVER_URL + 'tinymce/' -JQUERY_URL = SERVER_URL + 'jquery/jquery-1.4.4.min.js' -GPSBABEL = '/usr/bin/gpsbabel' -GPSBABEL_OPTIONS = 'simplify,crosstrack,error=0.005k' # simplify with an - # error of 5 meters -#GPSBABEL_OPTIONS = 'simplify,count=100' - -## chimere specific ## -# center of the map -DEFAULT_CENTER = (-1.679444, 48.114722) +""" +Here are the default settings for the Chimere app. +Feel free to set these settings in your project, they will override these defaults. +""" +# The height and width of the POI icons +CHIMERE_ICON_HEIGHT = 20 +CHIMERE_ICON_WIDTH = 20 +# The offset of the POI icons +CHIMERE_ICON_OFFSET_X = 0 +CHIMERE_ICON_OFFSET_Y = 0 + +# default center of the map +CHIMERE_DEFAULT_CENTER = (2.49, 48.7) +# Default zoom level +CHIMERE_DEFAULT_ZOOM = 10 # projection used by the main map # most public map providers use spherical mercator : 900913 -EPSG_PROJECTION = 900913 +CHIMERE_EPSG_PROJECTION = 900913 # projection displayed to the end user by openlayers # chimere use the same projection to save its data in the database -EPSG_DISPLAY_PROJECTION = 4326 +CHIMERE_EPSG_DISPLAY_PROJECTION = 4326 # to restrict the map to a defined bounding box set it here # (left, bottom, right, top) -RESTRICTED_EXTENT = None +CHIMERE_RESTRICTED_EXTENT = None # dynamic load of categories on the main map -DYNAMIC_CATEGORIES = False +CHIMERE_DYNAMIC_CATEGORIES = False # display of shortcuts for areas -DISPLAY_AREAS = True +CHIMERE_DISPLAY_AREAS = True # specific css for areas -CSS_AREAS = True +CHIMERE_CSS_AREAS = False # number of day before an event to display # if equal to 0: disable event management # if you change this value from 0 to a value in a production environnement # don't forget to run the upgrade.py script to create appropriate fields in # the database -DAYS_BEFORE_EVENT = 30 +CHIMERE_DAYS_BEFORE_EVENT = 30 # default id category to check on the map -DEFAULT_CATEGORIES = [1] +CHIMERE_DEFAULT_CATEGORIES = [1] # JS definition of the main map cf. OpenLayers documentation for more details #MAP_LAYER = '''new OpenLayers.Layer.OSM.CycleMap("Cycle map", { #displayOutsideMaxExtent: true, wrapDateLine: true})''' # OSM cyclemap -MAP_LAYER = "new OpenLayers.Layer.OSM.Mapnik('Mapnik')" # OSM mapnik map - -DEBUG = True -TEMPLATE_DEBUG = DEBUG - -ADMINS = ( - # ('Your Name', 'your_email@domain.com'), -) - -MANAGERS = ADMINS - -DATABASES = { - 'default': { - 'NAME': 'ratatouille', - 'ENGINE': 'django.contrib.gis.db.backends.postgis', - 'HOST': 'localhost', - 'PORT': '5432', - 'USER': 'ratatouille', - 'PASSWORD': 'wiki', - }, -} - -# Local time zone for this installation. Choices can be found here: -# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE -# although not all variations may be possible on all operating systems. -# If running in a Windows environment this must be set to the same as your -# system time zone. -TIME_ZONE = 'Europe/Paris' - -# Language code for this installation. All choices can be found here: -# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes -# http://blogs.law.harvard.edu/tech/stories/storyReader$15 -LANGUAGE_CODE = 'fr-fr' - -SITE_ID = 1 - -# If you set this to False, Django will make some optimizations so as not -# to load the internationalization machinery. -USE_I18N = True -USE_L10N = True - -# Absolute path to the directory that holds media. -# Example: "/home/media/media.lawrence.com/" -MEDIA_ROOT = ROOT_PATH + 'static/' - -# URL that handles the media served from MEDIA_ROOT. -# Example: "http://media.lawrence.com" -MEDIA_URL = '/' + EXTRA_URL + 'static/' - -# share with -SHARE_NETWORKS = ( -("Email", 'mailto:?subject=%(text)s&body=%(url)s', - MEDIA_URL + 'icons/email.png'), -("Facebook", 'http://www.facebook.com/sharer.php?t=%(text)s&u=%(url)s', - MEDIA_URL + 'icons/facebook.png'), -("Twitter", 'http://twitter.com/home?status=%(text)s %(url)s', - MEDIA_URL + 'icons/twitter.png'), -("Identi.ca", 'http://identi.ca/index.php?action=newnotice&status_textarea=%(text)s %(url)s', - MEDIA_URL + 'icons/identica.png'), -) - -# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a -# trailing slash. -# Examples: "http://foo.com/media/", "/media/". -ADMIN_MEDIA_PREFIX = '/' + EXTRA_URL + 'media/' - -# Make this unique, and don't share it with anybody. -SECRET_KEY = 'achanger_!ToChange!' - -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.load_template_source', - 'django.template.loaders.app_directories.load_template_source', -# 'django.template.loaders.eggs.load_template_source', -) - -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.middleware.doc.XViewMiddleware', -) - -ROOT_URLCONF = 'chimere.urls' - -TEMPLATE_DIRS = [ - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. - ROOT_PATH + 'templates', -] - -INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.admin', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'chimere.main', - # activate it if you want to use migration scripts - 'chimere.scripts', - # activate it if you want to use RSS feeds - 'chimere.rss' -) +CHIMERE_MAP_LAYER = "new OpenLayers.Layer.OSM.Mapnik('Mapnik')" # OSM mapnik map -if 'chimere.rss' in INSTALLED_APPS: - TEMPLATE_DIRS.append(ROOT_PATH + 'rss/templates') diff --git a/chimere/forms.py b/chimere/forms.py new file mode 100644 index 0000000..3691408 --- /dev/null +++ b/chimere/forms.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2008-2011 É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. + +""" +Forms +""" +from django import forms +from django.contrib.gis.db import models +from django.utils.translation import ugettext as _ +from django.contrib.auth.models import User, Permission, ContentType +from django.contrib.admin.widgets import AdminDateWidget +from django.core.mail import EmailMessage, BadHeaderError + +from chimere import settings + +from chimere.models import Marker, Route, PropertyModel, Property, Area,\ + News, Category, SubCategory, RouteFile +from chimere.widgets import AreaField, PointField, TextareaWidget + +from datetime import timedelta, datetime, tzinfo + +ZERO = timedelta(0) + +class UTC(tzinfo): + """UTC time zone""" + + def utcoffset(self, dt): + return ZERO + + def tzname(self, dt): + return settings.TIME_ZONE + + def dst(self, dt): + return ZERO + +def notifyStaff(subject, body, sender=None): + if not settings.EMAIL_HOST: + return + if settings.PROJECT_NAME: + subject = u'[%s] %s' % (settings.PROJECT_NAME, subject) + user_list = [u.email for u in + User.objects.filter(is_staff=True).exclude(email="").order_by('id')] + headers = {} + if sender: + headers['Reply-To'] = sender + email = EmailMessage(subject, body, user_list[0], user_list, + headers=headers) + try: + email.send() + except BadHeaderError: + return False + return True + +def notifySubmission(geo_object): + category = u" - ".join([unicode(cat) for cat in geo_object.categories.all()]) + subject = u'%s %s' % (_(u"New submission for"), category) + message = _(u'The new item "%s" has been submited in the category: ') % \ + geo_object.name + category + message += "\n\n" + _(u"To valid, precise or unvalid this item: ") + message += settings.BASE_URL + 'admin' + message += u"\n\n--\nChimère" + return notifyStaff(subject, message) + +class ContactForm(forms.Form): + """ + Main form for categories + """ + email = forms.EmailField(label=_("Email (optional)"), required=False) + content = forms.CharField(label=_("Object"), widget=forms.Textarea) + +class NewsAdminForm(forms.ModelForm): + """ + Main form for news + """ + content = forms.CharField(widget=TextareaWidget) + class Meta: + model = News + +class CategoryAdminForm(forms.ModelForm): + """ + Main form for categories + """ + description = forms.CharField(widget=TextareaWidget, required=False) + class Meta: + model = Category + +class MarkerAdminForm(forms.ModelForm): + """ + Main form for marker + """ + # declare properties + for property in PropertyModel.objects.filter(available=True): + exec('property_%d_%d = forms.CharField(label="%s", widget=%s, '\ + 'required=False)' % (property.order, property.id, property.name, + PropertyModel.TYPE_WIDGET[property.type])) + class Meta: + model = Marker + + def __init__(self, *args, **keys): + """ + Custom initialization method in order to manage properties + """ + if 'instance' in keys and keys['instance']: + instance = keys['instance'] + property_dct = {} + for pm in PropertyModel.objects.filter(available=True): + property = instance.getProperty(pm) + if property: + property_dct[pm.getNamedId()] = property.value + if 'initial' in keys: + keys['initial'].update(property_dct) + else: + keys['initial'] = property_dct + super(MarkerAdminForm, self).__init__(*args, **keys) + if settings.DAYS_BEFORE_EVENT: + self.fields['start_date'].widget = AdminDateWidget() + self.fields['end_date'].widget = AdminDateWidget() + + def clean(self): + ''' + Verify that a start date is provided when an end date is set + ''' + if not settings.DAYS_BEFORE_EVENT: + return self.cleaned_data + if self.cleaned_data['end_date'] and \ + not self.cleaned_data['start_date']: + msg = _(u"End date has been set with no start date") + self._errors["end_date"] = self.error_class([msg]) + del self.cleaned_data['end_date'] + return self.cleaned_data + + def save(self, *args, **keys): + """ + Custom save method in order to manage associated properties + """ + new_marker = super(MarkerAdminForm, self).save(*args, **keys) + if 'status' not in self.cleaned_data: + new_marker.status = 'S' + if new_marker.status == 'A': + tz = UTC() + new_marker.available_date = datetime.replace(datetime.utcnow(), + tzinfo=tz) + new_marker.save() + # save properties + properties = dict([(k.split('_')[-1], self.cleaned_data[k]) \ + for k in self.cleaned_data.keys() if k.startswith('property_')]) + new_marker.saveProperties(properties) + return new_marker + +class MarkerForm(MarkerAdminForm): + """ + Form for the edit page + """ + class Meta: + model = Marker + exclude = ('status',) + +class RouteAdminForm(forms.ModelForm): + """ + Main form for route + """ + class Meta: + model = Route + + def __init__(self, *args, **keys): + """ + Custom initialization method in order to manage properties + """ + if 'instance' in keys and keys['instance']: + instance = keys['instance'] + property_dct = {} + for pm in PropertyModel.objects.filter(available=True): + property = instance.getProperty(pm) + if property: + property_dct[pm.getNamedId()] = property.value + if 'initial' in keys: + keys['initial'].update(property_dct) + else: + keys['initial'] = property_dct + super(RouteAdminForm, self).__init__(*args, **keys) + if settings.DAYS_BEFORE_EVENT: + self.fields['start_date'].widget = AdminDateWidget() + self.fields['end_date'].widget = AdminDateWidget() + + def save(self, *args, **keys): + """ + Custom save method in order to manage status + """ + new_route = super(RouteAdminForm, self).save(*args, **keys) + if 'status' not in self.cleaned_data: + new_route.status = 'S' + new_route.save() + return new_route + +class RouteForm(RouteAdminForm): + """ + Form for the edit page + """ + picture = forms.ImageField(label=_("Image"), required=False) + point = forms.CharField(label=" ", required=False, widget=forms.HiddenInput) + associated_file_id = forms.CharField(label=" ", required=False, + widget=forms.HiddenInput) + class Meta: + model = Route + exclude = ('status',) + # marker properties + for property in PropertyModel.objects.filter(available=True): + exec('property_%d_%d = forms.CharField(label="%s", widget=%s, '\ + 'required=False)' % (property.order, property.id, property.name, + PropertyModel.TYPE_WIDGET[property.type])) + + def save(self, *args, **keys): + """ + Custom save method in order to manage associated marker and file + """ + new_route = super(RouteForm, self).save(*args, **keys) + # associate a route file + if 'associated_file_id' in self.cleaned_data and \ + self.cleaned_data['associated_file_id']: + #try: + file_pk = int(self.cleaned_data['associated_file_id']) + new_route.associated_file = RouteFile.objects.get(pk=file_pk) + new_route.save() + #except: + #pass + marker_fields = [f.attname for f in Marker._meta.fields] + marker_dct = dict([(k, self.cleaned_data[k]) for k in self.cleaned_data + if k in marker_fields]) + marker_dct['route'] = new_route + if 'status' not in marker_dct: + marker_dct['status'] = "S" + categories = [] + new_marker = Marker(**marker_dct) + new_marker.save() + for category in self.cleaned_data['categories']: + new_marker.categories.add(category) + new_marker.save() + # save properties + properties = dict([(k.split('_')[-1], self.cleaned_data[k]) \ + for k in self.cleaned_data.keys() if k.startswith('property_')]) + new_marker.saveProperties(properties) + return new_route + +class FileForm(forms.Form): + raw_file = forms.FileField(label=_(u"File")) + + def clean_raw_file(self): + data = self.cleaned_data['raw_file'] + if '.' not in data.name or \ + data.name.split('.')[-1].lower() not in ('kml', 'gpx'): + raise forms.ValidationError(_(u"Bad file format: this must be a "\ + u"GPX or KML file")) + return data + +class FullFileForm(FileForm): + name = forms.CharField(label=_(u"Name"), max_length=150) + def __init__(self, *args, **kwargs): + super(FullFileForm, self).__init__(*args, **kwargs) + self.fields.keyOrder = ['name', 'raw_file'] + +class AreaAdminForm(forms.ModelForm): + """ + Admin page to create an area + """ + area = AreaField(label=_("Area"), fields=(PointField(), PointField())) + class Meta: + model = Area + + def __init__(self, *args, **keys): + """ + Custom initialization method in order to manage area + """ + if 'instance' in keys and keys['instance']: + instance = keys['instance'] + dct = {'area':(instance.upper_left_corner, + instance.lower_right_corner)} + if 'initial' in keys: + keys['initial'].update(dct) + else: + keys['initial'] = dct + super(AreaAdminForm, self).__init__(*args, **keys) + + def save(self, *args, **keys): + """ + Custom save method in order to manage area + """ + new_area = super(AreaAdminForm, self).save(*args, **keys) + area = self.cleaned_data['area'] + new_area.upper_left_corner = 'POINT(%s %s)' % (area[0][0], area[0][1]) + new_area.lower_right_corner = 'POINT(%s %s)' % (area[1][0], + area[1][1]) + content_type = ContentType.objects.get(app_label="chimere", + model="area") + if new_area.urn: + mnemo = 'change_area_' + new_area.urn + perm = Permission.objects.filter(codename=mnemo) + if not perm: + perm = Permission(name='Can change ' + new_area.name, + content_type_id=content_type.id, codename=mnemo) + perm.save() + else: + if 'urn' in self.initial: + mnemo = 'change_area_' + self.initial['urn'] + perm = Permission.objects.filter(codename=mnemo) + if perm: + perm[0].delete() + return new_area + +class AreaForm(AreaAdminForm): + """ + Form for the edit page + """ + class Meta: + model = Area + diff --git a/chimere/main/__init__.py b/chimere/main/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/chimere/main/actions.py b/chimere/main/actions.py deleted file mode 100644 index 6f82028..0000000 --- a/chimere/main/actions.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/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. - -""" -Actions available in the main interface -""" -from django.utils.translation import ugettext_lazy as _ -from django.contrib.auth import models - -from chimere.settings import EXTRA_URL, EMAIL_HOST, INSTALLED_APPS - -class Action: - def __init__(self, id, path, label): - self.id, self.path, self.label = id, path, label - -actions = [(Action('view', '', _('View')), []), - (Action('contribute', 'edit', _('Contribute')), - (Action('edit', 'edit', _('Add a new point of interest')), - Action('edit_route', 'edit_route', _('Add a new route'))), - ),] - -if 'chimere.rss' in INSTALLED_APPS: - actions.append((Action('rss', 'rss', _('RSS feeds')), [])) - -if EMAIL_HOST: - actions.append((Action('contact', 'contact', _('Contact us')), []),) - diff --git a/chimere/main/admin.py b/chimere/main/admin.py deleted file mode 100644 index c3333c7..0000000 --- a/chimere/main/admin.py +++ /dev/null @@ -1,142 +0,0 @@ -#!/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. - -""" -Settings for administration pages -""" - -from chimere import settings -from chimere.main.models import Category, Icon, SubCategory, Marker, \ - PropertyModel, News, Route, Area, ColorTheme, Color, RouteFile -from chimere.main.forms import MarkerAdminForm, RouteAdminForm, AreaAdminForm,\ - NewsAdminForm, CategoryAdminForm -from chimere.main.widgets import TextareaWidget - -from django.contrib import admin - -def get_areas_for_user(user): - """ - Getting subcats for a specific user - """ - perms = user.get_all_permissions() - areas = set() - prefix = 'main.change_area_' - for perm in perms: - if perm.startswith(prefix): - area = Area.objects.get(urn=perm[len(prefix):]) - areas.add(area) - return areas - -class MarkerAdmin(admin.ModelAdmin): - """ - Specialized the Point field. - """ - search_fields = ("name",) - list_display = ('name', 'status') - list_filter = ('status', 'categories') - exclude = ['height', 'width'] - if 'chimere.rss' in settings.INSTALLED_APPS: - exclude.append('available_date') - form = MarkerAdminForm - - def queryset(self, request): - qs = self.model._default_manager.get_query_set() - if not request.user.is_superuser: - areas = get_areas_for_user(request.user) - if areas: - in_areas = " or ".join([area.getIncludeSql() for area in areas]) - qs = qs.extra(where=[in_areas]) - ordering = self.ordering or () - if ordering: - qs = qs.order_by(*ordering) - return qs - -class RouteAdmin(admin.ModelAdmin): - """ - Specialized the Route field. - """ - search_fields = ("name",) - list_display = ('name', 'status') - list_filter = ('status', 'categories') - exclude = ['height', 'width'] - form = RouteAdminForm - readonly_fields = ('associated_file',) - - def queryset(self, request): - qs = self.model._default_manager.get_query_set() - if not request.user.is_superuser: - areas = get_areas_for_user(request.user) - if areas: - in_areas = " or ".join([area.getIncludeSql( - geometry='"main_route".route') for area in areas]) - qs = qs.extra(where=[in_areas]) - ordering = self.ordering or () - if ordering: - qs = qs.order_by(*ordering) - return qs - -class AreaAdmin(admin.ModelAdmin): - """ - Specialized the area field. - """ - form = AreaAdminForm - exclude = ['upper_left_corner', 'lower_right_corner'] - -class SubCategoryAdmin(admin.ModelAdmin): - """ - Specialized the subcategory admin - """ - list_display = ('name', 'category', 'available') - list_filter = ('category',) - -class NewsAdmin(admin.ModelAdmin): - """ - Use the TinyMCE widget for the news content - """ - form = NewsAdminForm - -class CategoryAdmin(admin.ModelAdmin): - """ - Use the TinyMCE widget for categories - """ - form = CategoryAdminForm - -class ColorInline(admin.TabularInline): - model = Color - -class ColorThemeAdmin(admin.ModelAdmin): - inlines = [ColorInline,] - -class IconAdmin(admin.ModelAdmin): - exclude = ['height', 'width'] - -class RouteFileAdmin(admin.ModelAdmin): - list_display = ['name', 'file_type'] - -# register of differents database fields -admin.site.register(News, NewsAdmin) -admin.site.register(Category, CategoryAdmin) -admin.site.register(Icon, IconAdmin) -admin.site.register(SubCategory, SubCategoryAdmin) -admin.site.register(Marker, MarkerAdmin) -admin.site.register(RouteFile, RouteFileAdmin) -admin.site.register(Route, RouteAdmin) -admin.site.register(PropertyModel) -admin.site.register(Area, AreaAdmin) -admin.site.register(ColorTheme, ColorThemeAdmin) diff --git a/chimere/main/forms.py b/chimere/main/forms.py deleted file mode 100644 index 20027ee..0000000 --- a/chimere/main/forms.py +++ /dev/null @@ -1,331 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2008-2011 É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. - -""" -Forms -""" -from django import forms -from django.contrib.gis.db import models -from django.utils.translation import ugettext as _ -from django.contrib.auth.models import User, Permission, ContentType -from django.contrib.admin.widgets import AdminDateWidget -from django.core.mail import EmailMessage, BadHeaderError - -from chimere import settings - -from chimere.main.models import Marker, Route, PropertyModel, Property, Area,\ - News, Category, SubCategory, RouteFile -from chimere.main.widgets import AreaField, PointField, TextareaWidget - -from datetime import timedelta, datetime, tzinfo - -ZERO = timedelta(0) - -class UTC(tzinfo): - """UTC time zone""" - - def utcoffset(self, dt): - return ZERO - - def tzname(self, dt): - return settings.TIME_ZONE - - def dst(self, dt): - return ZERO - -def notifyStaff(subject, body, sender=None): - if not settings.EMAIL_HOST: - return - if settings.PROJECT_NAME: - subject = u'[%s] %s' % (settings.PROJECT_NAME, subject) - user_list = [u.email for u in - User.objects.filter(is_staff=True).exclude(email="").order_by('id')] - headers = {} - if sender: - headers['Reply-To'] = sender - email = EmailMessage(subject, body, user_list[0], user_list, - headers=headers) - try: - email.send() - except BadHeaderError: - return False - return True - -def notifySubmission(geo_object): - category = u" - ".join([unicode(cat) for cat in geo_object.categories.all()]) - subject = u'%s %s' % (_(u"New submission for"), category) - message = _(u'The new item "%s" has been submited in the category: ') % \ - geo_object.name + category - message += "\n\n" + _(u"To valid, precise or unvalid this item: ") - message += settings.BASE_URL + 'admin' - message += u"\n\n--\nChimère" - return notifyStaff(subject, message) - -class ContactForm(forms.Form): - """ - Main form for categories - """ - email = forms.EmailField(label=_("Email (optional)"), required=False) - content = forms.CharField(label=_("Object"), widget=forms.Textarea) - -class NewsAdminForm(forms.ModelForm): - """ - Main form for news - """ - content = forms.CharField(widget=TextareaWidget) - class Meta: - model = News - -class CategoryAdminForm(forms.ModelForm): - """ - Main form for categories - """ - description = forms.CharField(widget=TextareaWidget, required=False) - class Meta: - model = Category - -class MarkerAdminForm(forms.ModelForm): - """ - Main form for marker - """ - # declare properties - for property in PropertyModel.objects.filter(available=True): - exec('property_%d_%d = forms.CharField(label="%s", widget=%s, '\ - 'required=False)' % (property.order, property.id, property.name, - PropertyModel.TYPE_WIDGET[property.type])) - class Meta: - model = Marker - - def __init__(self, *args, **keys): - """ - Custom initialization method in order to manage properties - """ - if 'instance' in keys and keys['instance']: - instance = keys['instance'] - property_dct = {} - for pm in PropertyModel.objects.filter(available=True): - property = instance.getProperty(pm) - if property: - property_dct[pm.getNamedId()] = property.value - if 'initial' in keys: - keys['initial'].update(property_dct) - else: - keys['initial'] = property_dct - super(MarkerAdminForm, self).__init__(*args, **keys) - if settings.DAYS_BEFORE_EVENT: - self.fields['start_date'].widget = AdminDateWidget() - self.fields['end_date'].widget = AdminDateWidget() - - def clean(self): - ''' - Verify that a start date is provided when an end date is set - ''' - if not settings.DAYS_BEFORE_EVENT: - return self.cleaned_data - if self.cleaned_data['end_date'] and \ - not self.cleaned_data['start_date']: - msg = _(u"End date has been set with no start date") - self._errors["end_date"] = self.error_class([msg]) - del self.cleaned_data['end_date'] - return self.cleaned_data - - def save(self, *args, **keys): - """ - Custom save method in order to manage associated properties - """ - new_marker = super(MarkerAdminForm, self).save(*args, **keys) - if 'status' not in self.cleaned_data: - new_marker.status = 'S' - if new_marker.status == 'A': - tz = UTC() - new_marker.available_date = datetime.replace(datetime.utcnow(), - tzinfo=tz) - new_marker.save() - # save properties - properties = dict([(k.split('_')[-1], self.cleaned_data[k]) \ - for k in self.cleaned_data.keys() if k.startswith('property_')]) - new_marker.saveProperties(properties) - return new_marker - -class MarkerForm(MarkerAdminForm): - """ - Form for the edit page - """ - class Meta: - model = Marker - exclude = ('status',) - -class RouteAdminForm(forms.ModelForm): - """ - Main form for route - """ - class Meta: - model = Route - - def __init__(self, *args, **keys): - """ - Custom initialization method in order to manage properties - """ - if 'instance' in keys and keys['instance']: - instance = keys['instance'] - property_dct = {} - for pm in PropertyModel.objects.filter(available=True): - property = instance.getProperty(pm) - if property: - property_dct[pm.getNamedId()] = property.value - if 'initial' in keys: - keys['initial'].update(property_dct) - else: - keys['initial'] = property_dct - super(RouteAdminForm, self).__init__(*args, **keys) - if settings.DAYS_BEFORE_EVENT: - self.fields['start_date'].widget = AdminDateWidget() - self.fields['end_date'].widget = AdminDateWidget() - - def save(self, *args, **keys): - """ - Custom save method in order to manage status - """ - new_route = super(RouteAdminForm, self).save(*args, **keys) - if 'status' not in self.cleaned_data: - new_route.status = 'S' - new_route.save() - return new_route - -class RouteForm(RouteAdminForm): - """ - Form for the edit page - """ - picture = forms.ImageField(label=_("Image"), required=False) - point = forms.CharField(label=" ", required=False, widget=forms.HiddenInput) - associated_file_id = forms.CharField(label=" ", required=False, - widget=forms.HiddenInput) - class Meta: - model = Route - exclude = ('status',) - # marker properties - for property in PropertyModel.objects.filter(available=True): - exec('property_%d_%d = forms.CharField(label="%s", widget=%s, '\ - 'required=False)' % (property.order, property.id, property.name, - PropertyModel.TYPE_WIDGET[property.type])) - - def save(self, *args, **keys): - """ - Custom save method in order to manage associated marker and file - """ - new_route = super(RouteForm, self).save(*args, **keys) - # associate a route file - if 'associated_file_id' in self.cleaned_data and \ - self.cleaned_data['associated_file_id']: - #try: - file_pk = int(self.cleaned_data['associated_file_id']) - new_route.associated_file = RouteFile.objects.get(pk=file_pk) - new_route.save() - #except: - #pass - marker_fields = [f.attname for f in Marker._meta.fields] - marker_dct = dict([(k, self.cleaned_data[k]) for k in self.cleaned_data - if k in marker_fields]) - marker_dct['route'] = new_route - if 'status' not in marker_dct: - marker_dct['status'] = "S" - categories = [] - new_marker = Marker(**marker_dct) - new_marker.save() - for category in self.cleaned_data['categories']: - new_marker.categories.add(category) - new_marker.save() - # save properties - properties = dict([(k.split('_')[-1], self.cleaned_data[k]) \ - for k in self.cleaned_data.keys() if k.startswith('property_')]) - new_marker.saveProperties(properties) - return new_route - -class FileForm(forms.Form): - raw_file = forms.FileField(label=_(u"File")) - - def clean_raw_file(self): - data = self.cleaned_data['raw_file'] - if '.' not in data.name or \ - data.name.split('.')[-1].lower() not in ('kml', 'gpx'): - raise forms.ValidationError(_(u"Bad file format: this must be a "\ - u"GPX or KML file")) - return data - -class FullFileForm(FileForm): - name = forms.CharField(label=_(u"Name"), max_length=150) - def __init__(self, *args, **kwargs): - super(FullFileForm, self).__init__(*args, **kwargs) - self.fields.keyOrder = ['name', 'raw_file'] - -class AreaAdminForm(forms.ModelForm): - """ - Admin page to create an area - """ - area = AreaField(label=_("Area"), fields=(PointField(), PointField())) - class Meta: - model = Area - - def __init__(self, *args, **keys): - """ - Custom initialization method in order to manage area - """ - if 'instance' in keys and keys['instance']: - instance = keys['instance'] - dct = {'area':(instance.upper_left_corner, - instance.lower_right_corner)} - if 'initial' in keys: - keys['initial'].update(dct) - else: - keys['initial'] = dct - super(AreaAdminForm, self).__init__(*args, **keys) - - def save(self, *args, **keys): - """ - Custom save method in order to manage area - """ - new_area = super(AreaAdminForm, self).save(*args, **keys) - area = self.cleaned_data['area'] - new_area.upper_left_corner = 'POINT(%s %s)' % (area[0][0], area[0][1]) - new_area.lower_right_corner = 'POINT(%s %s)' % (area[1][0], - area[1][1]) - content_type = ContentType.objects.get(app_label="main", - model="area") - if new_area.urn: - mnemo = 'change_area_' + new_area.urn - perm = Permission.objects.filter(codename=mnemo) - if not perm: - perm = Permission(name='Can change ' + new_area.name, - content_type_id=content_type.id, codename=mnemo) - perm.save() - else: - if 'urn' in self.initial: - mnemo = 'change_area_' + self.initial['urn'] - perm = Permission.objects.filter(codename=mnemo) - if perm: - perm[0].delete() - return new_area - -class AreaForm(AreaAdminForm): - """ - Form for the edit page - """ - class Meta: - model = Area - diff --git a/chimere/main/models.py b/chimere/main/models.py deleted file mode 100644 index 144b49d..0000000 --- a/chimere/main/models.py +++ /dev/null @@ -1,612 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2008-2011 É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 -""" -import os, string, json -import lxml.etree as ElementTree -from datetime import datetime, timedelta -from subprocess import Popen, PIPE - -from django.core.files import File - -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, SelectMultipleField - -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") - verbose_name_plural = _("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(reversed(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: - obj = cls.objects.get(parameters=parameters) - except cls.DoesNotExist: - obj = cls(parameters=parameters) - obj.save() - n = obj.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') - height = models.IntegerField(_("Height")) - width = models.IntegerField(_("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 = SelectMultipleField('Area', related_name='areas', blank=True) - 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.subcategory_set.count(): - sub_ids = [sub.id for sub in area.subcategory_set.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) - categories = SelectMultipleField(SubCategory) - point = PointField(_("Localisation"), srid=settings.EPSG_DISPLAY_PROJECTION) - picture = models.ImageField(_("Image"), upload_to='upload', blank=True, - null=True, height_field='height', width_field='width') - height = models.IntegerField(_("Height"), blank=True, null=True) - width = models.IntegerField(_("Width"), blank=True, null=True) - 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) - if settings.DAYS_BEFORE_EVENT: - start_date = models.DateField(_("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(_("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")) - if 'chimere.rss' in settings.INSTALLED_APPS: - available_date = models.DateTimeField(_("Available Date"), blank=True, - null=True) - route = models.ForeignKey("Route", blank=True, null=True) - objects = models.GeoManager() - - def __unicode__(self): - return self.name - - @property - def date(self): - if settings.DAYS_BEFORE_EVENT: - return self.start_date - - class Meta: - ordering = ('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 saveProperties(self, values): - """ - Save properties - """ - for propertymodel in PropertyModel.objects.filter(available=True): - properties = Property.objects.filter(marker=self, - propertymodel=propertymodel).all() - # in case of multiple edition as the same time delete arbitrary - # the others - if len(properties) > 1: - for property in properties[1:]: - property.delete() - val = u"" - if unicode(propertymodel.id) in values: - val = values[unicode(propertymodel.id)] - # new property - if not properties: - new_property = Property.objects.create(marker=self, - propertymodel=propertymodel, - value=val) - new_property.save() - else: - property = properties[0] - property.value = val - property.save() - - def getGeoJSON(self, categories_id=[]): - '''Return a GeoJSON string - ''' - jsons = [] - for cat in self.categories.all(): - if categories_id and cat.id not in categories_id: - continue - items = {'id':self.id, 'name':json.dumps(self.name), - 'geometry':self.point.geojson, - 'icon_path':cat.icon.image, - 'icon_width':cat.icon.image.width, - 'icon_height':cat.icon.image.height,} - jsons.append(u'{"type":"Feature", "geometry":%(geometry)s, '\ - u'"properties":{"pk": %(id)d, "name": %(name)s, '\ - u'"icon_path":"%(icon_path)s", "icon_width":%(icon_width)d, '\ - u'"icon_height":%(icon_height)d}}' % items) - return ",".join(jsons) - - def get_absolute_url(self): - parameters = 'current_feature=%d&checked_categories=%s' % (self.id, - self.categories.all()[0].id) - return settings.BASE_URL + 'ty/' + TinyUrl.getUrnByParameters(parameters) - -class RouteFile(models.Model): - name = models.CharField(_(u"Name"), max_length=150) - raw_file = models.FileField(_(u"Raw file (gpx or kml)"), upload_to='upload') - simplified_file = models.FileField(_(u"Simplified file"), - upload_to='upload', blank=True, null=True) - TYPE = (('K', _(u'KML')), ('G', _(u'GPX'))) - file_type = models.CharField(max_length=1, choices=TYPE) - - def __unicode__(self): - return self.name - - def process(self): - if self.simplified_file: - return - input_name = settings.MEDIA_ROOT + self.raw_file.name - output_name = settings.MEDIA_ROOT + self.raw_file.name[:-4] + \ - "_simplified" + ".gpx" - cli_args = [settings.GPSBABEL, '-i'] - if self.file_type == 'K': - cli_args.append('kml') - elif self.file_type == 'G': - cli_args.append('gpx') - cli_args += ['-f', input_name, '-x', settings.GPSBABEL_OPTIONS, - '-o', 'gpx', '-F', output_name] - p = Popen(cli_args, stderr=PIPE) - p.wait() - if p.returncode: - print p.stderr.read() - #logger.error(p.stderr.read()) - else: - self.simplified_file = File(open(output_name)) - self.save() - os.remove(output_name) - - @property - def route(self): - if not self.simplified_file: - return - mainNS = string.Template("{http://www.topografix.com/GPX/1/0}$tag") - trkpt = mainNS.substitute(tag="trkpt") - file_name = settings.MEDIA_ROOT + self.simplified_file.name - et = ElementTree.parse(open(file_name)) - pts = [] - for pt in et.findall("//" + trkpt): - 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]) - -class Route(models.Model): - '''Route on the map - ''' - name = models.CharField(_("Name"), max_length=150) - categories = SelectMultipleField(SubCategory) - route = RouteField(_("Route"), srid=settings.EPSG_DISPLAY_PROJECTION) - associated_file = models.ForeignKey(RouteFile, blank=True, null=True, - verbose_name=_(u"Associated file")) - picture = models.ImageField(_("Image"), upload_to='upload', blank=True, - null=True, height_field='height', width_field='width') - height = models.IntegerField(_("Height"), blank=True, null=True) - width = models.IntegerField(_("Width"), blank=True, null=True) - STATUS = (('S', _('Submited')), - ('A', _('Available')), - ('D', _('Disabled')),) - STATUS_DCT = {} - for key, label in STATUS: - STATUS_DCT[key] = label - if settings.DAYS_BEFORE_EVENT: - start_date = models.DateField(_("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(_("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")) - status = models.CharField(_("Status"), max_length=1, choices=STATUS) - objects = models.GeoManager() - - def __unicode__(self): - return self.name - - class Meta: - ordering = ('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 - attributes = {'id':self.id, 'name':json.dumps(self.name), - 'color':color, 'geometry':self.route.geojson,} - return u'{"type":"Feature", "geometry":%(geometry)s, '\ - u'"properties":{"pk": %(id)d, "name": %(name)s, '\ - u'"color":"%(color)s"}}' % attributes - - def getTinyUrl(self): - parameters = 'current_feature=%d&checked_categories=%s' % (self.id, - self.categories[0].id) - return TinyUrl.getUrnByParameters(parameters) - -def getDateCondition(): - ''' - Return an SQL condition for apparition of dates - ''' - if not settings.DAYS_BEFORE_EVENT: - return "" - now = datetime.now().strftime('%Y-%m-%d') - after = (datetime.now() + timedelta(settings.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 + "')" - date_condition += " or (%(alias)s.start_date <='" + now + "' and " - date_condition += "%(alias)s.end_date >='" + now + "') " - return date_condition - -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 = 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.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, - 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 - inner join main_category cat on cat.id=subcat.category_id''' - sql = sql_main + ''' - inner join main_marker mark on ST_Contains(%s, mark.point)''' % area - if equal_status: - sql += ' and mark.status' + equal_status - sql += date_condition % {'alias':'mark'} - sql += ''' - inner join main_marker_categories mc on mc.subcategory_id=subcat.id and - mc.marker_id=mark.id''' - if filter_available: - sql += ' where subcat.available = TRUE and cat.available = TRUE' - subcats = set(SubCategory.objects.raw(sql)) - sql = sql_main + ''' - inner join main_route rt on (ST_Intersects(%s, rt.route) or - ST_Contains(%s, rt.route))''' % (area, area) - if equal_status: - sql += ' and rt.status' + equal_status - sql += date_condition % {'alias':'rt'} - sql += ''' - inner join main_route_categories rc on rc.subcategory_id=subcat.id and - rc.route_id=rt.id''' - if filter_available: - sql += ' where subcat.available = TRUE and cat.available = TRUE' - subcats.union(SubCategory.objects.raw(sql)) - 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) - order = models.IntegerField(_("Order")) - available = models.BooleanField(_("Available")) - upper_left_corner = models.PointField(_("Upper left corner"), - default='POINT(0 0)', srid=settings.EPSG_DISPLAY_PROJECTION) - lower_right_corner = models.PointField(_("Lower right corner"), - default='POINT(0 0)', srid=settings.EPSG_DISPLAY_PROJECTION) - 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") - diff --git a/chimere/main/templatetags/__init__.py b/chimere/main/templatetags/__init__.py deleted file mode 100644 index 792d600..0000000 --- a/chimere/main/templatetags/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# diff --git a/chimere/main/templatetags/sanitize.py b/chimere/main/templatetags/sanitize.py deleted file mode 100644 index ccb936c..0000000 --- a/chimere/main/templatetags/sanitize.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from django import template -from BeautifulSoup import BeautifulSoup, Comment -import re - -register = template.Library() - -def sanitize(value, allowed_tags): - """Argument should be in form 'tag2:attr1:attr2 tag2:attr1 tag3', where tags - are allowed HTML tags, and attrs are the allowed attributes for that tag. - """ - js_regex = re.compile(r'[\s]*(&#x.{1,7})?'.join(list('javascript'))) - allowed_tags = [tag.split(':') for tag in allowed_tags.split()] - allowed_tags = dict((tag[0], tag[1:]) for tag in allowed_tags) - - soup = BeautifulSoup(value) - for comment in soup.findAll(text=lambda text: isinstance(text, Comment)): - comment.extract() - - for tag in soup.findAll(True): - if tag.name not in allowed_tags: - tag.hidden = True - else: - tag.attrs = [(attr, js_regex.sub('', val)) for attr, val in tag.attrs - if attr in allowed_tags[tag.name]] - return soup.renderContents().decode('utf8') - -register.filter(sanitize) - diff --git a/chimere/main/templatetags/unlocalize_point.py b/chimere/main/templatetags/unlocalize_point.py deleted file mode 100644 index f52a90a..0000000 --- a/chimere/main/templatetags/unlocalize_point.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from django import template -import re - -register = template.Library() - -def unlocalize_point(value): - """ - Basic unlocalize filter for django 1.2 - """ - return unicode(value).replace(',', '.') - -register.filter(unlocalize_point) - diff --git a/chimere/main/views.py b/chimere/main/views.py deleted file mode 100644 index 25ad630..0000000 --- a/chimere/main/views.py +++ /dev/null @@ -1,470 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2008-2011 É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. - -""" -Views of the project -""" - -import datetime -from itertools import groupby - -from django.utils.translation import ugettext as _ -from django.shortcuts import render_to_response -from django.template import loader -from django.http import HttpResponseRedirect, HttpResponse -from django.core import serializers -from django.utils.http import urlquote -from django.db.models import Q -from django.utils import simplejson - -from chimere import settings -from chimere.main.actions import actions -from chimere.main.models import Category, SubCategory, PropertyModel, \ - Marker, Route, News, SimpleArea, Area, Color, TinyUrl, RouteFile - -from chimere.main.widgets import getMapJS, PointChooserWidget, \ - RouteChooserWidget, URL_OSM_JS, URL_OSM_CSS -from chimere.main.forms import MarkerForm, RouteForm, ContactForm, \ - FileForm, FullFileForm, notifySubmission, notifyStaff - -def get_base_response(area_name=""): - """ - Get the base url - """ - base_response_dct = {'media_path':settings.MEDIA_URL,} - base_url = settings.EXTRA_URL - if not base_url.startswith('/'): - base_url = '/' + base_url - if area_name: - if base_url[-1] != '/': - base_url += '/' - base_url += area_name + '/' - base_response_dct['extra_url'] = base_url - if settings.CSS_AREAS and area_name: - base_response_dct['css_area'] = area_name + ".css" - base_response_dct['area_name'] = area_name - base_response_dct['JQUERY_URL'] = settings.JQUERY_URL - return base_response_dct - -def index(request, area_name=None, default_area=None, simple=False): - """ - Main page - """ - extra = "" - tab = " "*4 - for url in URL_OSM_CSS: - extra += tab + '' % url - for url in URL_OSM_JS + ["%sbase.js" % settings.MEDIA_URL, - "%smain_map.js" % settings.MEDIA_URL,]: - extra += tab + '\n' % url - # show the welcome page - today = datetime.date.today().strftime('%y-%m-%d') - display_welcome = None - if not 'last_visit' in request.session or \ - request.session['last_visit'] != today: - request.session['last_visit'] = today - display_welcome = True - response_dct = get_base_response(area_name) - areas = None - if settings.DISPLAY_AREAS: - areas = Area.getAvailable() - response_dct.update({'actions':actions, 'action_selected':('view',), - 'error_message':'', 'default_area':default_area, - 'extra_head':extra + getMapJS(area_name), - 'welcome':welcome(request, display_welcome), - 'areas':areas, 'map_layer':settings.MAP_LAYER, - 'dynamic_categories':settings.DYNAMIC_CATEGORIES, - }) - # manage permalink - if request.GET: - for key in ('zoom', 'lon', 'lat', 'display_submited', - 'current_feature'): - if key in request.GET and request.GET[key]: - response_dct['p_'+key] = request.GET[key] - else: - response_dct['p_'+key] = None - if 'checked_categories' in request.GET \ - and request.GET['checked_categories']: - cats = request.GET['checked_categories'].split('_') - response_dct['p_checked_categories'] = ",".join(cats) - else: - response_dct['p_checked_categories'] = ''; - tpl = 'main_map.html' - if simple: - tpl = 'main_map_simple.html' - return render_to_response(tpl, response_dct) - -def edit(request, area_name=""): - """ - Edition page - """ - # If the form has been submited - if request.method == 'POST': - form = MarkerForm(request.POST, request.FILES) - # All validation rules pass - if form.is_valid(): - marker = form.save() - # set the submited status - marker.status = 'S' - marker.save() - notifySubmission(marker) - response_dct = get_base_response(area_name) - return HttpResponseRedirect(response_dct['extra_url'] + \ - 'submited/edit') - else: - # An unbound form - form = MarkerForm() - # get the « manualy » declared_fields. Ie: properties - declared_fields = form.declared_fields.keys() - response_dct = get_base_response(area_name) - response_dct.update({'actions':actions, - 'action_selected':('contribute', 'edit'), - 'error_message':'', - 'map_layer':settings.MAP_LAYER, - 'form':form, - 'dated':settings.DAYS_BEFORE_EVENT, - 'extra_head':form.media, - 'sub_categories':SubCategory.getAvailable(['M', 'B'], - area_name), - 'point_widget':PointChooserWidget().render('point', None, - area_name=area_name), - 'properties':declared_fields, - }) - # manualy populate the custom widget - if 'subcategory' in form.data and form.data['subcategory']: - response_dct['current_category'] = int(form.data['subcategory']) - return render_to_response('edit.html', response_dct) - -def uploadFile(request, category_id='', area_name=''): - response_dct = get_base_response(area_name) - Form = FileForm if not category_id else FullFileForm - category = None - if category_id: - try: - category = SubCategory.objects.get(pk=category_id) - response_dct['category'] = unicode(category) - except: - pass - # If the form has been submited - if request.method == 'POST': - form = Form(request.POST, request.FILES) - # All validation rules pass - if form.is_valid(): - raw_file = form.cleaned_data['raw_file'] - name = raw_file.name.split('.')[0] - file_type = raw_file.name.split('.')[-1][0].upper() - routefile = RouteFile(raw_file=raw_file, name=name, - file_type=file_type) - routefile.save() - if not category_id: - response_dct['gpx_id'] = routefile.pk - return render_to_response('upload_file.html', response_dct) - routefile.process() - if not routefile.route: - response_dct['errors'] = _(u"Bad file. Please check it with an " - u"external software.") - response_dct.update({'form':form}) - return render_to_response('upload_file.html', response_dct) - route = Route(name=form.cleaned_data['name'], route=routefile.route, - associated_file=routefile, status='S') - route.save() - route.categories.add(category) - route.save() - response_dct['thanks'] = True - form = Form() - else: - # An unbound form - form = Form() - response_dct.update({'form':form}) - return render_to_response('upload_file.html', response_dct) - -def processRouteFile(request, area_name='', file_id=None): - if file_id: - try: - route_file = RouteFile.objects.get(pk=file_id) - route_file.process() - route = route_file.route - if not route: - return HttpResponse(status=500) - return HttpResponse(simplejson.dumps({'wkt':route, - 'file_id':file_id}), - 'application/javascript', status=200) - except: - return HttpResponse(status=500) - else: - return HttpResponse(status=400) - -def editRoute(request, area_name=""): - """ - Route edition page - """ - # If the form has been submited - if request.method == 'POST': - form = RouteForm(request.POST, request.FILES) - # All validation rules pass - if form.is_valid(): - route = form.save() - # set the submited status - route.status = 'S' - route.save() - notifySubmission(route) - response_dct = get_base_response(area_name) - return HttpResponseRedirect(response_dct['extra_url'] + \ - 'submited/edit') - else: - # An unbound form - form = RouteForm() - # get the "manualy" declared_fields. Ie: properties - declared_fields = form.declared_fields.keys() - response_dct = get_base_response(area_name) - response_dct.update({'actions':actions, - 'action_selected':('contribute', 'edit_route'), - 'error_message':'', - 'map_layer':settings.MAP_LAYER, - 'form':form, - 'dated':settings.DAYS_BEFORE_EVENT, - 'extra_head':form.media, - 'sub_categories':SubCategory.getAvailable(['R', 'B'], - area_name), - 'route_widget':RouteChooserWidget().render('route', '', - area_name=area_name, - routefile_id='',), - 'properties':declared_fields - }) - # manualy populate the custom widget - if 'subcategory' in form.data and form.data['subcategory']: - response_dct['current_category'] = int(form.data['subcategory']) - return render_to_response('edit_route.html', response_dct) - -def welcome(request, display=None): - """ - Welcome string - """ - response_dct = {'display':display} - news = list(News.objects.filter(available=True).all()) - if settings.DAYS_BEFORE_EVENT: - q = checkDate(Q(status='A', start_date__isnull=False)) - news += list(Marker.objects.filter(q).all()) - news.sort(key=lambda x:x.date) - response_dct['news_lst'] = news - return loader.render_to_string('welcome.html', response_dct) - -def submited(request, area_name="", action=""): - """ - Successful submission page - """ - response_dct = get_base_response(area_name) - response_dct.update({'actions':actions, 'action_selected':action,}) - return render_to_response('submited.html', response_dct) - -def charte(request, area_name=""): - """ - Affichage de la charte - """ - response_dct = get_base_response(area_name) - response_dct.update({'actions':actions, 'action_selected':('charte',)}) - return render_to_response('charte.html', response_dct) - -def contactus(request, area_name=""): - """ - Contact page - """ - form = None - msg = '' - # If the form has been submited - if request.method == 'POST': - form = ContactForm(request.POST) - # All validation rules pass - if form.is_valid(): - response = notifyStaff(_(u"Comments/request on the map"), - form.cleaned_data['content'], form.cleaned_data['email']) - if response: - msg = _(u"Thank you for your contribution. It will be taken \ -into account. If you have left your email you may be contacted soon for more \ -details.") - else: - msg = _(u"Temporary error. Renew your message later.") - else: - form = ContactForm() - response_dct = get_base_response(area_name) - response_dct.update({'actions':actions, 'action_selected':('contact',), - 'contact_form':form, 'message':msg}) - return render_to_response('contactus.html', response_dct) - -def getDetail(request, area_name, marker_id): - ''' - Get the detail for a marker - ''' - try: - marker = Marker.objects.filter(id=int(marker_id), status__in=['A', 'S'])[0] - except (ValueError, IndexError): - return HttpResponse('no results') - response_dct = get_base_response() - response_dct['marker'] = marker - if request.method == 'GET': - if 'simple' in request.GET and request.GET['simple']: - response_dct['simple'] = True - parameters = u'current_feature=%s' % marker_id - parameters += u"&checked_categories=%s" % "_".join([str(m.id) \ - for m in marker.categories.all()]) - net_dct = getTinyfiedUrl(parameters, area_name) - share_networks = [] - for network in settings.SHARE_NETWORKS: - share_networks.append((network[0], network[1] % net_dct, network[2])) - response_dct['share_networks'] = share_networks - response_dct['dated'] = settings.DAYS_BEFORE_EVENT and marker.start_date - return render_to_response('detail.html', response_dct) - -def getDescriptionDetail(request, area_name, category_id): - ''' - Get the description for a category - ''' - try: - category = Category.objects.filter(id=int(category_id))[0] - except (ValueError, IndexError): - return HttpResponse('no results') - response_dct = get_base_response(area_name) - response_dct['category'] = category - return render_to_response('category_detail.html', response_dct) - -def checkDate(q): - """ - Filter a queryset to manage dates - """ - if not settings.DAYS_BEFORE_EVENT: - return q - today = datetime.date.today() - after = today + datetime.timedelta(settings.DAYS_BEFORE_EVENT) - - q = q & ( Q(start_date__isnull=True) - | Q(start_date__gte=today, start_date__lte=after) - | Q(start_date__lte=today, end_date__gte=today) - ) - return q - -def getGeoObjects(request, area_name, category_ids, status): - ''' - Get the JSON for markers and routes - ''' - if not status: - status = 'A' - status = status.split('_') - category_ids = category_ids.split('_') - try: - q = checkDate(Q(status__in=status, categories__in=category_ids)) - query = Route.objects.filter(q) - except: - return HttpResponse('no results') - query.order_by('categories') - routes = list(query) - jsons = [] - current_cat, colors, idx = None, None, 0 - for route in routes: - c_cat = route.categories.all()[0] - if not current_cat or current_cat != c_cat: - idx = 0 - current_cat = c_cat - colors = list(Color.objects.filter(color_theme = c_cat.color_theme)) - if colors: - jsons.append(route.getGeoJSON(color=colors[idx % len(colors)].code)) - else: - jsons.append(route.getGeoJSON(color='000')) - idx += 1 - try: - q = checkDate(Q(status__in=status, categories__in=category_ids)) - query = Marker.objects.filter(q) - except: - return HttpResponse('no results') - category_ids = [int(cat_id) for cat_id in category_ids] - jsons += [geo_object.getGeoJSON(category_ids) for geo_object in list(query)] - if not jsons: - return HttpResponse('no results') - data = '{"type": "FeatureCollection", "features":[%s]}' % ",".join(jsons) - return HttpResponse(data) - -def getAvailableCategories(request, area_name=None, area=None, status='A', - force=None): - ''' - Get categories for a designed area - ''' - if settings.DYNAMIC_CATEGORIES and not area: - return "" - response_dct = get_base_response('area_name') - if not settings.DYNAMIC_CATEGORIES: - subcategories = SubCategory.getAvailable() - response_dct['sub_categories'] = subcategories - return render_to_response('categories.html', response_dct) - default_message = "

%s

" % \ - _("No category available in this area.") - if not status: # there must be a status - status = 'A' - try: - status = status.split('_') - area = area.replace('M', '-').replace('D', '.') - area = SimpleArea([float(pt) for pt in area.split('_')]) - except: - # bad area format - return HttpResponse(default_message) - # if not force and area.isIn(SimpleArea(cookie.AREA):return - categories = area.getCategories(status) - if not categories: - return HttpResponse(default_message) - get_cat = lambda subcat: subcat.category - get_cat_order = lambda subcat: (subcat.category.order, subcat.category, - subcat.order) - categories = sorted(categories, key=get_cat_order) - subcategories = [(cat, list(subcats)) \ - for cat, subcats in groupby(categories, get_cat)] - response_dct['sub_categories'] = subcategories - return render_to_response('categories.html', response_dct) - -def getTinyfiedUrl(parameters, area_name=''): - ''' - Get the tinyfied version of parameters - ''' - data = {"urn": "", "url":"", "text":""} - try: - urn = TinyUrl.getUrnByParameters(parameters) - except: - return {} - response_dct = get_base_response(area_name) - url = settings.SERVER_URL - if url[-1] == '/': - url = url[:-1] - url += response_dct['extra_url'] + 'ty/' + urn - text = settings.PROJECT_NAME - if 'current_feature' in parameters: - for item in parameters.split('&'): - if 'current_feature' in item: - try: - text = unicode(Marker.objects.get(id=item.split('=')[1])) - except (IndexError, Marker.DoesNotExist): - pass - data["urn"] = urlquote(urn) - data["url"] = urlquote(url) - data["text"] = urlquote(text) - return data - -def redirectFromTinyURN(request, area_name='', tiny_urn=''): - """ - Redirect from a tiny Urn - """ - parameters = '?' + TinyUrl.getParametersByUrn(tiny_urn) - response_dct = get_base_response(area_name) - return HttpResponseRedirect(response_dct['extra_url'] + parameters) diff --git a/chimere/main/widgets.py b/chimere/main/widgets.py deleted file mode 100644 index 1af6637..0000000 --- a/chimere/main/widgets.py +++ /dev/null @@ -1,354 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2008-2011 É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. - -""" -Extra widgets and fields -""" - -from django import forms -from django.utils.safestring import mark_safe -from django.utils.translation import ugettext as _ -from django.contrib.gis.geos import fromstr - -from chimere import settings -from django.contrib.gis.db import models - -URL_OSM_CSS = ["http://www.openlayers.org/api/theme/default/style.css"] -URL_OSM_JS = [settings.MEDIA_URL+"OpenLayers.js", - "http://www.openstreetmap.org/openlayers/OpenStreetMap.js"] - -def getMapJS(area_name=''): - '''Variable initialization for drawing the map - ''' - # projection, center and bounds definitions - js = u"var epsg_display_projection = new OpenLayers.Projection('EPSG:%d')\ -;\n" % settings.EPSG_DISPLAY_PROJECTION - js += u"var epsg_projection = new OpenLayers.Projection('EPSG:%d');\n" % \ - settings.EPSG_PROJECTION - js += u"var centerLonLat = new OpenLayers.LonLat(%f,\ -%f).transform(epsg_display_projection, epsg_projection);\n" % \ - settings.DEFAULT_CENTER - js += u"var media_path = '%s';\n" % settings.MEDIA_URL - js += u"var map_layer = %s;\n" % settings.MAP_LAYER - js += u"var restricted_extent;\n" - - if area_name: - js += u"var area_name='%s';\n" % area_name - if settings.RESTRICTED_EXTENT: - restricted_extent_str = [str(coord) \ - for coord in settings.RESTRICTED_EXTENT] - js += u"restricted_extent = new OpenLayers.Bounds(%s);\n" %\ - ", ".join(restricted_extent_str) - js = u""" -""" % js - return js - -class TextareaWidget(forms.Textarea): - """ - Manage the edition of a text using TinyMCE - """ - class Media: - js = ["%stiny_mce.js" % settings.TINYMCE_URL, - "%stextareas.js" % settings.MEDIA_URL,] - -class PointChooserWidget(forms.TextInput): - """ - Manage the edition of point on a map - """ - class Media: - css = { - "all": URL_OSM_CSS + ["%sforms.css" % settings.MEDIA_URL,] - } - js = URL_OSM_JS + ["%sedit_map.js" % settings.MEDIA_URL, - "%sbase.js" % settings.MEDIA_URL,] - - def render(self, name, value, attrs=None, area_name=''): - ''' - Render a map and latitude, longitude information field - ''' - val = '0' - value_x, value_y = 0, 0 - if value: - val = str(value) - if hasattr(value, 'x') and hasattr(value, 'y'): - value_x, value_y = value.x, value.y - elif isinstance(value, unicode) and value.startswith('POINT('): - try: - value_x, value_y = value.split('(')[1][:-1].split(' ') - value_x, value_y = float(value_x), float(value_y) - except: - value = None - else: - value = None - tpl = getMapJS(area_name) - tpl += u'\n' % settings.MEDIA_URL - tpl += u"""
-
-

\ -

-

-
- -""" % (_("Latitude"), value_y, _("Longitude"), value_x, name, name, val) - tpl += " -
-""" - return mark_safe(tpl) - -class PointField(models.PointField): - ''' - Set the widget for the form field - ''' - def formfield(self, **keys): - defaults = {'widget': PointChooserWidget} - keys.update(defaults) - return super(PointField, self).formfield(**keys) - - def clean(self, value, instance=None): - if len(value) != 2 and self.required: - raise ValidationError(_("Invalid point")) - return value - -class RouteChooserWidget(forms.TextInput): - """ - Manage the edition of route on a map - """ - class Media: - css = { - "all": URL_OSM_CSS + ["%sforms.css" % settings.MEDIA_URL,] - } - js = ["%sedit_route_map.js" % settings.MEDIA_URL, - "%sbase.js" % settings.MEDIA_URL,] + URL_OSM_JS - - def render(self, name, value, attrs=None, area_name='', routefile_id=None): - ''' - Render a map and latitude, longitude information field - ''' - tpl = getMapJS(area_name) - help_create = '' - if not value: - help_create = """

%s

-

%s

-

%s

-

%s

-

%s

-

%s

""" % (_(u"Creation mode"), -_(u"To start drawing the route click on the toggle button: \"Draw\"."), -_(u"Then click on the map to begin the drawing."), -_(u"You can add points by clicking again."), -_(u"To finish the drawing double click. When the drawing is finished you can \ -edit it."), -_(u"While creating to undo a drawing click again on the toggle button \"Stop \ -drawing\".")) - help_modify = """

%s

-

%s

-

%s

-

%s

""" % (_(u"Modification mode"), -_(u"To move a point click on it and drag it to the desired position."), -_(u"To delete a point move the mouse cursor over it and press the \"d\" or \ -\"Del\" key."), -_(u"To add a point click in the middle of a segment and drag the new point to \ -the desired position")) - tpl += u'\n' % \ - settings.MEDIA_URL - if not value: - # upload a file - tpl += u""" -""" % _(u"Give a name and set category before uploading a file.") - tpl += u'' % ( - _(u"Upload a route file (GPX or KML)")) - tpl += u"""\n

%s

\n""" % _(u"or") - tpl += u"""
-%s
-
-
""" % (_(u"Start \"hand\" drawing")) - if value: - tpl += """ -
""" - else: - tpl += """ -
-
- %s - %s
-
""" % (_(u"Move on the map"), _(u"Draw")) - tpl += ''' -
%s
''' % help_create - style = '' - if value: - style = " style='display:block'" - tpl += """ -
%s
-
- - -""" % (style, help_modify, name, name, value, routefile_id) - tpl += " -""" - return mark_safe(tpl) - -class RouteField(models.LineStringField): - ''' - Set the widget for the form field - ''' - def formfield(self, **keys): - defaults = {'widget': RouteChooserWidget} - keys.update(defaults) - return super(RouteField, self).formfield(**keys) - -class AreaWidget(forms.TextInput): - """ - Manage the edition of an area on the map - """ - class Media: - css = { - "all": URL_OSM_CSS + ["%sforms.css" % settings.MEDIA_URL,] - } - js = URL_OSM_JS + ["%sedit_area.js" % settings.MEDIA_URL, - "%sbase.js" % settings.MEDIA_URL,] - - def render(self, name, value, attrs=None): - """ - Render a map - """ - upper_left_lat, upper_left_lon = 0, 0 - lower_right_lat, lower_right_lon = 0, 0 - if value: - if len(value) == 2: - upper_left = value[0] - lower_right = value[1] - if hasattr(upper_left, 'x') and hasattr(upper_left, 'y'): - upper_left_lon, upper_left_lat = upper_left.x, upper_left.y - if hasattr(lower_right, 'x') and hasattr(lower_right, 'y'): - lower_right_lon, lower_right_lat = lower_right.x, \ - lower_right.y - tpl = getMapJS() - tpl += u"""
- - - - -""" % (upper_left_lat, upper_left_lon, lower_right_lat, lower_right_lon) - tpl += """ -
-""" - return mark_safe(tpl) - - def value_from_datadict(self, data, files, name): - """ - Return the appropriate values - """ - values = [] - for keys in (('upper_left_lon', 'upper_left_lat',), - ('lower_right_lon', 'lower_right_lat')): - value = [] - for key in keys: - val = data.get(key, None) - if not val: - return [] - value.append(val) - values.append(value) - return values - -class AreaField(forms.MultiValueField): - ''' - Set the widget for the form field - ''' - widget = AreaWidget - - def compress(self, data_list): - if not data_list: - return None - return data_list - -class MultiSelectWidget(forms.SelectMultiple): - class Media: - css = {'all': ( - settings.MEDIA_URL + 'jquery/bsmSelect/css/jquery.bsmselect.css', - settings.MEDIA_URL + 'jquery/css/jquery.bsmselect.custom.css', - ) - } - js = ( - settings.MEDIA_URL + 'jquery/bsmSelect/js/jquery.bsmselect.js', - settings.MEDIA_URL + 'jquery/bsmSelect/js/jquery.bsmselect.compatibility.js', - ) - - def render(self, name, value, attrs=None): - rendered = super(MultiSelectWidget, self).render(name, value, attrs) - return mark_safe(rendered + u'''
''' % {'name':name, 'title':_("Select...")}) - -class SelectMultipleField(models.ManyToManyField): - ''' - Set the widget for the category field - ''' - def formfield(self, **keys): - self.help_text = "" - defaults = {'widget': MultiSelectWidget} - keys.update(defaults) - return super(SelectMultipleField, self).formfield(**keys) - diff --git a/chimere/manage.py b/chimere/manage.py deleted file mode 100755 index bcdd55e..0000000 --- a/chimere/manage.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/python -from django.core.management import execute_manager -try: - import settings # Assumed to be in the same directory. -except ImportError: - import sys - sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) - sys.exit(1) - -if __name__ == "__main__": - execute_manager(settings) diff --git a/chimere/migrations/0001_initial.py b/chimere/migrations/0001_initial.py new file mode 100644 index 0000000..3ba693f --- /dev/null +++ b/chimere/migrations/0001_initial.py @@ -0,0 +1,341 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'News' + db.create_table('main_news', ( + ('available', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)), + ('date', self.gf('django.db.models.fields.DateField')(auto_now_add=True, blank=True)), + ('content', self.gf('django.db.models.fields.TextField')()), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=150)), + )) + db.send_create_signal('chimere', ['News']) + + # Adding model 'TinyUrl' + db.create_table('main_tinyurl', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('parameters', self.gf('django.db.models.fields.CharField')(max_length=500)), + )) + db.send_create_signal('chimere', ['TinyUrl']) + + # Adding model 'ColorTheme' + db.create_table('main_colortheme', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=150)), + )) + db.send_create_signal('chimere', ['ColorTheme']) + + # Adding model 'Color' + db.create_table('main_color', ( + ('code', self.gf('django.db.models.fields.CharField')(max_length=6)), + ('color_theme', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['chimere.ColorTheme'])), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('order', self.gf('django.db.models.fields.IntegerField')()), + )) + db.send_create_signal('chimere', ['Color']) + + # Adding model 'Category' + db.create_table('main_category', ( + ('available', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)), + ('description', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('order', self.gf('django.db.models.fields.IntegerField')()), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=150)), + )) + db.send_create_signal('chimere', ['Category']) + + # Adding model 'Icon' + db.create_table('main_icon', ( + ('width', self.gf('django.db.models.fields.IntegerField')()), + ('image', self.gf('django.db.models.fields.files.ImageField')(max_length=100)), + ('height', self.gf('django.db.models.fields.IntegerField')()), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=150)), + )) + db.send_create_signal('chimere', ['Icon']) + + # Adding model 'SubCategory' + db.create_table('main_subcategory', ( + ('category', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['chimere.Category'])), + ('available', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=150)), + ('order', self.gf('django.db.models.fields.IntegerField')()), + ('item_type', self.gf('django.db.models.fields.CharField')(max_length=1)), + ('color_theme', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['chimere.ColorTheme'], null=True, blank=True)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('icon', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['chimere.Icon'])), + )) + db.send_create_signal('chimere', ['SubCategory']) + + # Adding M2M table for field areas on 'SubCategory' + db.create_table('main_subcategory_areas', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('subcategory', models.ForeignKey(orm['chimere.subcategory'], null=False)), + ('area', models.ForeignKey(orm['chimere.area'], null=False)) + )) + db.create_unique('main_subcategory_areas', ['subcategory_id', 'area_id']) + + # Adding model 'Marker' + db.create_table('main_marker', ( + ('status', self.gf('django.db.models.fields.CharField')(max_length=1)), + ('picture', self.gf('django.db.models.fields.files.ImageField')(max_length=100, null=True, blank=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=150)), + ('end_date', self.gf('django.db.models.fields.DateField')(null=True, blank=True)), + ('point', self.gf('chimere.widgets.PointField')()), + ('route', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['chimere.Route'], null=True, blank=True)), + ('height', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), + ('width', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), + ('start_date', self.gf('django.db.models.fields.DateField')(null=True, blank=True)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + )) + db.send_create_signal('chimere', ['Marker']) + + # Adding M2M table for field categories on 'Marker' + db.create_table('main_marker_categories', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('marker', models.ForeignKey(orm['chimere.marker'], null=False)), + ('subcategory', models.ForeignKey(orm['chimere.subcategory'], null=False)) + )) + db.create_unique('main_marker_categories', ['marker_id', 'subcategory_id']) + + # Adding model 'RouteFile' + db.create_table('main_routefile', ( + ('file_type', self.gf('django.db.models.fields.CharField')(max_length=1)), + ('raw_file', self.gf('django.db.models.fields.files.FileField')(max_length=100)), + ('simplified_file', self.gf('django.db.models.fields.files.FileField')(max_length=100, null=True, blank=True)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=150)), + )) + db.send_create_signal('chimere', ['RouteFile']) + + # Adding model 'Route' + db.create_table('main_route', ( + ('status', self.gf('django.db.models.fields.CharField')(max_length=1)), + ('picture', self.gf('django.db.models.fields.files.ImageField')(max_length=100, null=True, blank=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=150)), + ('end_date', self.gf('django.db.models.fields.DateField')(null=True, blank=True)), + ('route', self.gf('chimere.widgets.RouteField')()), + ('height', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), + ('width', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), + ('associated_file', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['chimere.RouteFile'], null=True, blank=True)), + ('start_date', self.gf('django.db.models.fields.DateField')(null=True, blank=True)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + )) + db.send_create_signal('chimere', ['Route']) + + # Adding M2M table for field categories on 'Route' + db.create_table('main_route_categories', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('route', models.ForeignKey(orm['chimere.route'], null=False)), + ('subcategory', models.ForeignKey(orm['chimere.subcategory'], null=False)) + )) + db.create_unique('main_route_categories', ['route_id', 'subcategory_id']) + + # Adding model 'Area' + db.create_table('chimere_area', ( + ('available', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=150)), + ('urn', self.gf('django.db.models.fields.SlugField')(db_index=True, unique=True, max_length=50, blank=True)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('upper_left_corner', self.gf('django.contrib.gis.db.models.fields.PointField')(default='POINT(0 0)')), + ('order', self.gf('django.db.models.fields.IntegerField')()), + ('lower_right_corner', self.gf('django.contrib.gis.db.models.fields.PointField')(default='POINT(0 0)')), + )) + db.send_create_signal('chimere', ['Area']) + + # Adding model 'PropertyModel' + db.create_table('main_propertymodel', ( + ('available', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)), + ('order', self.gf('django.db.models.fields.IntegerField')()), + ('type', self.gf('django.db.models.fields.CharField')(max_length=1)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=150)), + )) + db.send_create_signal('chimere', ['PropertyModel']) + + # Adding model 'Property' + db.create_table('main_property', ( + ('marker', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['chimere.Marker'])), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('value', self.gf('django.db.models.fields.TextField')()), + ('propertymodel', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['chimere.PropertyModel'])), + )) + db.send_create_signal('chimere', ['Property']) + + + def backwards(self, orm): + + # Deleting model 'News' + db.delete_table('main_news') + + # Deleting model 'TinyUrl' + db.delete_table('main_tinyurl') + + # Deleting model 'ColorTheme' + db.delete_table('main_colortheme') + + # Deleting model 'Color' + db.delete_table('main_color') + + # Deleting model 'Category' + db.delete_table('main_category') + + # Deleting model 'Icon' + db.delete_table('main_icon') + + # Deleting model 'SubCategory' + db.delete_table('main_subcategory') + + # Removing M2M table for field areas on 'SubCategory' + db.delete_table('main_subcategory_areas') + + # Deleting model 'Marker' + db.delete_table('main_marker') + + # Removing M2M table for field categories on 'Marker' + db.delete_table('main_marker_categories') + + # Deleting model 'RouteFile' + db.delete_table('main_routefile') + + # Deleting model 'Route' + db.delete_table('main_route') + + # Removing M2M table for field categories on 'Route' + db.delete_table('main_route_categories') + + # Deleting model 'Area' + db.delete_table('chimere_area') + + # Deleting model 'PropertyModel' + db.delete_table('main_propertymodel') + + # Deleting model 'Property' + db.delete_table('main_property') + + + models = { + 'chimere.area': { + 'Meta': {'object_name': 'Area'}, + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'lower_right_corner': ('django.contrib.gis.db.models.fields.PointField', [], {'default': "'POINT(0 0)'"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'order': ('django.db.models.fields.IntegerField', [], {}), + 'upper_left_corner': ('django.contrib.gis.db.models.fields.PointField', [], {'default': "'POINT(0 0)'"}), + 'urn': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'unique': 'True', 'max_length': '50', 'blank': 'True'}) + }, + 'chimere.category': { + 'Meta': {'object_name': 'Category', 'db_table': "'main_category'"}, + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'order': ('django.db.models.fields.IntegerField', [], {}) + }, + 'chimere.color': { + 'Meta': {'object_name': 'Color', 'db_table': "'main_color'"}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '6'}), + 'color_theme': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.ColorTheme']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.IntegerField', [], {}) + }, + 'chimere.colortheme': { + 'Meta': {'object_name': 'ColorTheme', 'db_table': "'main_colortheme'"}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}) + }, + 'chimere.icon': { + 'Meta': {'object_name': 'Icon', 'db_table': "'main_icon'"}, + 'height': ('django.db.models.fields.IntegerField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'width': ('django.db.models.fields.IntegerField', [], {}) + }, + 'chimere.marker': { + 'Meta': {'object_name': 'Marker', 'db_table': "'main_marker'"}, + 'categories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False'}), + 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'picture': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'point': ('chimere.widgets.PointField', [], {}), + 'route': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Route']", 'null': 'True', 'blank': 'True'}), + 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'chimere.news': { + 'Meta': {'object_name': 'News', 'db_table': "'main_news'"}, + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'content': ('django.db.models.fields.TextField', [], {}), + 'date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '150'}) + }, + 'chimere.property': { + 'Meta': {'object_name': 'Property', 'db_table': "'main_property'"}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'marker': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Marker']"}), + 'propertymodel': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.PropertyModel']"}), + 'value': ('django.db.models.fields.TextField', [], {}) + }, + 'chimere.propertymodel': { + 'Meta': {'object_name': 'PropertyModel', 'db_table': "'main_propertymodel'"}, + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'order': ('django.db.models.fields.IntegerField', [], {}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '1'}) + }, + 'chimere.route': { + 'Meta': {'object_name': 'Route', 'db_table': "'main_route'"}, + 'associated_file': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.RouteFile']", 'null': 'True', 'blank': 'True'}), + 'categories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False'}), + 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'picture': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'route': ('chimere.widgets.RouteField', [], {}), + 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'chimere.routefile': { + 'Meta': {'object_name': 'RouteFile', 'db_table': "'main_routefile'"}, + 'file_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'raw_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'simplified_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}) + }, + 'chimere.subcategory': { + 'Meta': {'object_name': 'SubCategory', 'db_table': "'main_subcategory'"}, + 'areas': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'areas'", 'blank': 'True', 'to': "orm['chimere.Area']"}), + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Category']"}), + 'color_theme': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.ColorTheme']", 'null': 'True', 'blank': 'True'}), + 'icon': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Icon']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'item_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'order': ('django.db.models.fields.IntegerField', [], {}) + }, + 'chimere.tinyurl': { + 'Meta': {'object_name': 'TinyUrl', 'db_table': "'main_tinyurl'"}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'parameters': ('django.db.models.fields.CharField', [], {'max_length': '500'}) + } + } + + complete_apps = ['chimere'] diff --git a/chimere/migrations/0002_rename_models.py b/chimere/migrations/0002_rename_models.py new file mode 100644 index 0000000..84a91b8 --- /dev/null +++ b/chimere/migrations/0002_rename_models.py @@ -0,0 +1,154 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.rename_table('main_news', 'chimere_news') + db.rename_table('main_tinyurl', 'chimere_tinyurl') + db.rename_table('main_colortheme', 'chimere_colortheme') + db.rename_table('main_color', 'chimere_color') + db.rename_table('main_category', 'chimere_category') + db.rename_table('main_icon', 'chimere_icon') + db.rename_table('main_subcategory', 'chimere_subcategory') + db.rename_table('main_marker', 'chimere_marker') + db.rename_table('main_routefile', 'chimere_routefile') + db.rename_table('main_route', 'chimere_route') + db.rename_table('main_propertymodel', 'chimere_propertymodel') + db.rename_table('main_property', 'chimere_property') + + def backwards(self, orm): + db.rename_table('chimere_news', 'main_news') + db.rename_table('chimere_tinyurl', 'main_tinyurl') + db.rename_table('chimere_colortheme', 'main_colortheme') + db.rename_table('chimere_color', '_color') + db.rename_table('chimere_category', 'main_category') + db.rename_table('chimere_icon', 'main_icon') + db.rename_table('chimere_subcategory', 'main_subcategory') + db.rename_table('chimere_marker', 'main_marker') + db.rename_table('chimere_routefile', 'main_routefile') + db.rename_table('chimere_route', 'main_route') + db.rename_table('chimere_propertymodel', 'main_propertymodel') + db.rename_table('chimere_property', 'main_property') + + models = { + 'chimere.area': { + 'Meta': {'object_name': 'Area'}, + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'lower_right_corner': ('django.contrib.gis.db.models.fields.PointField', [], {'default': "'POINT(0 0)'"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'order': ('django.db.models.fields.IntegerField', [], {}), + 'upper_left_corner': ('django.contrib.gis.db.models.fields.PointField', [], {'default': "'POINT(0 0)'"}), + 'urn': ('django.db.models.fields.SlugField', [], {'db_index': 'True', 'unique': 'True', 'max_length': '50', 'blank': 'True'}) + }, + 'chimere.category': { + 'Meta': {'object_name': 'Category', 'db_table': "'main_category'"}, + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'order': ('django.db.models.fields.IntegerField', [], {}) + }, + 'chimere.color': { + 'Meta': {'object_name': 'Color', 'db_table': "'main_color'"}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '6'}), + 'color_theme': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.ColorTheme']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.IntegerField', [], {}) + }, + 'chimere.colortheme': { + 'Meta': {'object_name': 'ColorTheme', 'db_table': "'main_colortheme'"}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}) + }, + 'chimere.icon': { + 'Meta': {'object_name': 'Icon', 'db_table': "'main_icon'"}, + 'height': ('django.db.models.fields.IntegerField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'width': ('django.db.models.fields.IntegerField', [], {}) + }, + 'chimere.marker': { + 'Meta': {'object_name': 'Marker', 'db_table': "'main_marker'"}, + 'categories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False'}), + 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'picture': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'point': ('chimere.widgets.PointField', [], {}), + 'route': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Route']", 'null': 'True', 'blank': 'True'}), + 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'chimere.news': { + 'Meta': {'object_name': 'News', 'db_table': "'main_news'"}, + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'content': ('django.db.models.fields.TextField', [], {}), + 'date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '150'}) + }, + 'chimere.property': { + 'Meta': {'object_name': 'Property', 'db_table': "'main_property'"}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'marker': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Marker']"}), + 'propertymodel': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.PropertyModel']"}), + 'value': ('django.db.models.fields.TextField', [], {}) + }, + 'chimere.propertymodel': { + 'Meta': {'object_name': 'PropertyModel', 'db_table': "'main_propertymodel'"}, + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'order': ('django.db.models.fields.IntegerField', [], {}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '1'}) + }, + 'chimere.route': { + 'Meta': {'object_name': 'Route', 'db_table': "'main_route'"}, + 'associated_file': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.RouteFile']", 'null': 'True', 'blank': 'True'}), + 'categories': ('chimere.widgets.SelectMultipleField', [], {'to': "orm['chimere.SubCategory']", 'symmetrical': 'False'}), + 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'picture': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'route': ('chimere.widgets.RouteField', [], {}), + 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'chimere.routefile': { + 'Meta': {'object_name': 'RouteFile', 'db_table': "'main_routefile'"}, + 'file_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'raw_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'simplified_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}) + }, + 'chimere.subcategory': { + 'Meta': {'object_name': 'SubCategory', 'db_table': "'main_subcategory'"}, + 'areas': ('chimere.widgets.SelectMultipleField', [], {'symmetrical': 'False', 'related_name': "'areas'", 'blank': 'True', 'to': "orm['chimere.Area']"}), + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Category']"}), + 'color_theme': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.ColorTheme']", 'null': 'True', 'blank': 'True'}), + 'icon': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['chimere.Icon']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'item_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'order': ('django.db.models.fields.IntegerField', [], {}) + }, + 'chimere.tinyurl': { + 'Meta': {'object_name': 'TinyUrl', 'db_table': "'main_tinyurl'"}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'parameters': ('django.db.models.fields.CharField', [], {'max_length': '500'}) + } + } + + complete_apps = ['chimere'] diff --git a/chimere/migrations/__init__.py b/chimere/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chimere/models.py b/chimere/models.py new file mode 100644 index 0000000..8c80fab --- /dev/null +++ b/chimere/models.py @@ -0,0 +1,615 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2008-2011 É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 +""" +import os, string, json +import lxml.etree as ElementTree +from datetime import datetime, timedelta +from subprocess import Popen, PIPE + +from django.conf import settings +from django.contrib.gis.db import models +from django.contrib.gis.gdal import SpatialReference +from django.contrib import admin +from django.core.files import File +from django.utils.translation import ugettext_lazy as _ + +from chimere.widgets import PointField, RouteField, SelectMultipleField + +class News(models.Model): + """News of the site + """ + title = models.CharField(_(u"Name"), max_length=150) + available = models.BooleanField(_(u"Available")) + date = models.DateField(_(u"Date"), auto_now_add=True) + content = models.TextField() + def __unicode__(self): + ordering = ["-date"] + return self.title + class Meta: + verbose_name = _(u"News") + verbose_name_plural = _(u"News") + +class TinyUrl(models.Model): + """Tinyfied version of permalink parameters + """ + parameters = models.CharField(_("uParameters"), max_length=500) + def __unicode__(self): + return self.parameters + class Meta: + verbose_name = _(u"TinyUrl") + digits = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + base = len(digits) + + @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) + try: + params = cls.objects.get(id=c_id).parameters + except cls.DoesNotExist: + return '' + return params + + @classmethod + def getUrnByParameters(cls, parameters): + try: + obj = cls.objects.get(parameters=parameters) + except cls.DoesNotExist: + obj = cls(parameters=parameters) + obj.save() + n = obj.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(_(u"Name"), max_length=150) + def __unicode__(self): + return self.name + class Meta: + verbose_name = _(u"Color theme") + +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") + +class Category(models.Model): + """Category of Point Of Interest (POI) + """ + name = models.CharField(_(u"Name"), max_length=150) + 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") + +class Icon(models.Model): + '''Icon + ''' + name = models.CharField(_(u"Name"), max_length=150) + image = models.ImageField(_(u"Image"), upload_to='icons', + height_field='height', width_field='width') + height = models.IntegerField(_(u"Height")) + width = models.IntegerField(_(u"Width")) + def __unicode__(self): + return self.name + class Meta: + verbose_name = _(u"Icon") + +class SubCategory(models.Model): + '''Sub-category + ''' + category = models.ForeignKey(Category, verbose_name=_(u"Category")) + name = models.CharField(_(u"Name"), max_length=150) + available = models.BooleanField(_(u"Available")) + areas = SelectMultipleField('Area', related_name='areas', blank=True) + icon = models.ForeignKey(Icon, verbose_name=_(u"Icon")) + color_theme = models.ForeignKey(ColorTheme, verbose_name=_(u"Color theme"), + blank=True, null=True) + order = models.IntegerField(_(u"Order")) + TYPE = (('M', _(u'Marker')), + ('R', _(u'Route')), + ('B', _(u'Both')),) + item_type = models.CharField(_(u"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 = _(u"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.subcategory_set.count(): + sub_ids = [sub.id for sub in area.subcategory_set.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(_(u"Name"), max_length=150) + categories = SelectMultipleField(SubCategory) + point = PointField(_(u"Localisation"), + srid=settings.EPSG_DISPLAY_PROJECTION) + 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) + STATUS = (('S', _(u'Submited')), + ('A', _(u'Available')), + ('D', _(u'Disabled')),) + STATUS_DCT = {} + for key, label in STATUS: + STATUS_DCT[key] = label + status = models.CharField(_(u"Status"), max_length=1, choices=STATUS) + if settings.DAYS_BEFORE_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 "\ + u"event. Format YYYY-MM-DD")) + if 'chimere.rss' in settings.INSTALLED_APPS: + available_date = models.DateTimeField(_(u"Available Date"), blank=True, + null=True) + route = models.ForeignKey(u"Route", blank=True, null=True) + objects = models.GeoManager() + + def __unicode__(self): + return self.name + + @property + def date(self): + if settings.DAYS_BEFORE_EVENT: + return self.start_date + + class Meta: + ordering = ('status', 'name') + verbose_name = _(u"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 saveProperties(self, values): + """ + Save properties + """ + for propertymodel in PropertyModel.objects.filter(available=True): + properties = Property.objects.filter(marker=self, + propertymodel=propertymodel).all() + # in case of multiple edition as the same time delete arbitrary + # the others + if len(properties) > 1: + for property in properties[1:]: + property.delete() + val = u"" + if unicode(propertymodel.id) in values: + val = values[unicode(propertymodel.id)] + # new property + if not properties: + new_property = Property.objects.create(marker=self, + propertymodel=propertymodel, + value=val) + new_property.save() + else: + property = properties[0] + property.value = val + property.save() + + def getGeoJSON(self, categories_id=[]): + '''Return a GeoJSON string + ''' + jsons = [] + for cat in self.categories.all(): + if categories_id and cat.id not in categories_id: + continue + items = {'id':self.id, 'name':json.dumps(self.name), + 'geometry':self.point.geojson, + 'icon_path':cat.icon.image, + 'icon_width':cat.icon.image.width, + 'icon_height':cat.icon.image.height,} + jsons.append(u'{"type":"Feature", "geometry":%(geometry)s, '\ + u'"properties":{"pk": %(id)d, "name": %(name)s, '\ + u'"icon_path":"%(icon_path)s", "icon_width":%(icon_width)d, '\ + u'"icon_height":%(icon_height)d}}' % items) + return ",".join(jsons) + + def get_absolute_url(self): + parameters = 'current_feature=%d&checked_categories=%s' % (self.id, + self.categories.all()[0].id) + return settings.BASE_URL + 'ty/' + TinyUrl.getUrnByParameters(parameters) + +class RouteFile(models.Model): + name = models.CharField(_(u"Name"), max_length=150) + raw_file = models.FileField(_(u"Raw file (gpx or kml)"), upload_to='upload') + simplified_file = models.FileField(_(u"Simplified file"), + upload_to='upload', blank=True, null=True) + TYPE = (('K', _(u'KML')), ('G', _(u'GPX'))) + file_type = models.CharField(max_length=1, choices=TYPE) + + class Meta: + ordering = ('name',) + verbose_name = _(u"Route file") + + def __unicode__(self): + return self.name + + def process(self): + if self.simplified_file: + return + input_name = settings.MEDIA_ROOT + self.raw_file.name + output_name = settings.MEDIA_ROOT + self.raw_file.name[:-4] + \ + "_simplified" + ".gpx" + cli_args = [settings.GPSBABEL, '-i'] + if self.file_type == 'K': + cli_args.append('kml') + elif self.file_type == 'G': + cli_args.append('gpx') + cli_args += ['-f', input_name, '-x', settings.GPSBABEL_OPTIONS, + '-o', 'gpx', '-F', output_name] + p = Popen(cli_args, stderr=PIPE) + p.wait() + if p.returncode: + print p.stderr.read() + #logger.error(p.stderr.read()) + else: + self.simplified_file = File(open(output_name)) + self.save() + os.remove(output_name) + + @property + def route(self): + if not self.simplified_file: + return + mainNS = string.Template("{http://www.topografix.com/GPX/1/0}$tag") + trkpt = mainNS.substitute(tag="trkpt") + file_name = settings.MEDIA_ROOT + self.simplified_file.name + et = ElementTree.parse(open(file_name)) + pts = [] + for pt in et.findall("//" + trkpt): + 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]) + +class Route(models.Model): + '''Route on the map + ''' + name = models.CharField(_(u"Name"), max_length=150) + categories = SelectMultipleField(SubCategory) + route = RouteField(_(u"Route"), srid=settings.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') + height = models.IntegerField(_(u"Height"), blank=True, null=True) + width = models.IntegerField(_(u"Width"), blank=True, null=True) + STATUS = (('S', _(u'Submited')), + ('A', _(u'Available')), + ('D', _(u'Disabled')),) + STATUS_DCT = {} + for key, label in STATUS: + STATUS_DCT[key] = label + if settings.DAYS_BEFORE_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 "\ + u"event. Format YYYY-MM-DD")) + status = models.CharField(_(u"Status"), max_length=1, choices=STATUS) + objects = models.GeoManager() + + def __unicode__(self): + return self.name + + class Meta: + ordering = ('status', 'name') + verbose_name = _(u"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 + attributes = {'id':self.id, 'name':json.dumps(self.name), + 'color':color, 'geometry':self.route.geojson,} + return u'{"type":"Feature", "geometry":%(geometry)s, '\ + u'"properties":{"pk": %(id)d, "name": %(name)s, '\ + u'"color":"%(color)s"}}' % attributes + + def getTinyUrl(self): + parameters = 'current_feature=%d&checked_categories=%s' % (self.id, + self.categories[0].id) + return TinyUrl.getUrnByParameters(parameters) + +def getDateCondition(): + ''' + Return an SQL condition for apparition of dates + ''' + if not settings.DAYS_BEFORE_EVENT: + return "" + now = datetime.now().strftime('%Y-%m-%d') + after = (datetime.now() + timedelta(settings.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 + "')" + date_condition += " or (%(alias)s.start_date <='" + now + "' and " + date_condition += "%(alias)s.end_date >='" + now + "') " + return date_condition + +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 = 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.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, + 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 chimere_subcategory subcat + inner join chimere_category cat on cat.id=subcat.category_id''' + sql = sql_main + ''' + 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 += ''' + inner join chimere_marker_categories mc on mc.subcategory_id=subcat.id and + mc.marker_id=mark.id''' + if filter_available: + sql += ' where subcat.available = TRUE and cat.available = TRUE' + subcats = set(SubCategory.objects.raw(sql)) + sql = sql_main + ''' + inner join chimere_route rt on (ST_Intersects(%s, rt.route) or + ST_Contains(%s, rt.route))''' % (area, area) + if equal_status: + sql += ' and rt.status' + equal_status + sql += date_condition % {'alias':'rt'} + sql += ''' + inner join chimere_route_categories rc on rc.subcategory_id=subcat.id and + rc.route_id=rt.id''' + if filter_available: + sql += ' where subcat.available = TRUE and cat.available = TRUE' + subcats.union(SubCategory.objects.raw(sql)) + return subcats + +class Area(models.Model, SimpleArea): + """Rectangular area of the map + """ + name = models.CharField(_(u"Name"), max_length=150) + urn = models.SlugField(_(u"Area urn"), max_length=50, blank=True, + unique=True) + order = models.IntegerField(_(u"Order")) + available = models.BooleanField(_(u"Available")) + upper_left_corner = models.PointField(_(u"Upper left corner"), + default='POINT(0 0)', srid=settings.EPSG_DISPLAY_PROJECTION) + lower_right_corner = models.PointField(_(u"Lower right corner"), + default='POINT(0 0)', srid=settings.EPSG_DISPLAY_PROJECTION) + 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='"chimere_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(_(u"Name"), max_length=150) + order = models.IntegerField(_(u"Order")) + available = models.BooleanField(_(u"Available")) + TYPE = (('T', _('Text')), + ('L', _('Long text')), + ('P', _('Password'))) + TYPE_WIDGET = {'T':'forms.TextInput', + 'L':'TextareaWidget', + 'P':'forms.PasswordInput'} + 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 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=_(u"Point of interest")) + propertymodel = models.ForeignKey(PropertyModel, + verbose_name=_(u"Property model")) + value = models.TextField(_(u"Value")) + def __unicode__(self): + return "%s : %s" % (str(self.propertymodel), self.value) + class Meta: + verbose_name = _(u"Property") + diff --git a/chimere/rss/__init__.py b/chimere/rss/__init__.py deleted file mode 100644 index 792d600..0000000 --- a/chimere/rss/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# diff --git a/chimere/rss/feeds.py b/chimere/rss/feeds.py deleted file mode 100644 index 18a4259..0000000 --- a/chimere/rss/feeds.py +++ /dev/null @@ -1,230 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2010 Pierre Clarenc , -# Samuel Renard , -# É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. - -from django.utils.translation import ugettext as _ -from django.contrib.syndication.feeds import Feed -from django.contrib.syndication.feeds import FeedDoesNotExist -from chimere.main.models import Category, SubCategory, Marker, Area -from django.core.exceptions import ObjectDoesNotExist -from django.contrib.gis.geos import * - -from chimere import settings - -class BaseFeed(Feed): - """ - Base feed for Chimere objects - """ - def item_link(self, item): - ''' Return POI permalink ''' - coord = item.point - cat = 0 - if item.categories.all() and item.categories.all()[0]: - cat = item.categories.all()[0].pk - return settings.BASE_URL + '?zoom=16&lat=%d&lon=%d¤t_feature=%d&\ -checked_categories=%d' % (coord.y, coord.x, item.id, cat) - - def item_pubdate(self, item): - """ - Date of the Marker when it has been available - """ - return item.available_date - - def description(self, obj): - return "" - -class LatestPOIsByCategory(BaseFeed): - ''' - Last Points of interests by category in Feeds - ''' - title_template = "rss_title.html" - description_template = "rss_descr.html" - - def get_object(self, bits): - """ - Get extra url, after rss/category/ id of category - """ - if len(bits) != 1: - raise ObjectDoesNotExist - return Category.objects.get(id__exact=bits[0]) - - def title(self, obj): - """ - Define the title of the feed - """ - return u"%s - %s" % (settings.PROJECT_NAME, obj.name) - - def link(self, obj): - """ - Define the link of the feed. - """ - if not obj: - raise FeedDoesNotExist - return settings.BASE_URL + 'rss/category/' + str(obj.id) - - def items(self, obj): - """ - Requests to marker where its category match the category is requested - and its status is available - This returns a list of the 15 last markers/POIs ordering by date - """ - q = Marker.objects.filter(status__exact='A', - categories__category__id__exact=obj.id, - available_date__isnull=False).order_by('-available_date')[:15] - return q - -class LatestPOIsBySubCategory(BaseFeed): - ''' - Last Points of interests by SubCategory in Feeds - ''' - title_template = "rss_title.html" - description_template = "rss_descr.html" - - def get_object(self, bits): - if len(bits) != 1: - raise ObjectDoesNotExist - return SubCategory.objects.get(id__exact=bits[0]) - - def title(self, obj): - return u"%s - %s - %s" % (settings.PROJECT_NAME, obj.category.name, - obj.name) - - def link(self, obj): - if not obj: - raise FeedDoesNotExist - return settings.BASE_URL + 'rss/subcategory/' + str(obj.id) - - def items(self, obj): - q = Marker.objects.filter(categories__id__exact=obj.id, - available_date__isnull=False, status__exact='A').order_by( - '-available_date')[:15] - return q - -class LatestPOIs(BaseFeed): - ''' - Last Points of interests - ''' - title_template = "rss_title.html" - description_template = "rss_descr.html" - - def title(self): - return settings.PROJECT_NAME + u" - " + _(u"Last points of interest") - - def link(self): - return settings.BASE_URL + 'rss/categories/' - - def description(self): - return _("Latest points of interest from ") + settings.PROJECT_NAME - - def items(self): - q = Marker.objects.filter(status__exact='A', - available_date__isnull=False).order_by('-available_date')[:15] - return q - -class LatestPOIsByZone(BaseFeed): - ''' - Last Points of interests by zone by coordinates - ''' - title_template = "rss_title.html" - description_template = "rss_descr.html" - upper_left_lat = 0 - upper_left_lon = 0 - lower_right_lat = 0 - lower_right_lon = 0 - - def get_object(self, bits): - """ - Get the extra url. Parameters are the coordinates of the zone (the - upper left and lower right points) - """ - if len(bits) != 1: - raise ObjectDoesNotExist - # Then define the upper right and lower left points - coordinates = str(bits[0]).split('_') - upper_left_lat = float(coordinates[0]) - upper_left_lon = float(coordinates[1]) - lower_right_lat = float(coordinates[2]) - lower_right_lon = float(coordinates[3]) - upper_right_lat = upper_left_lat - upper_right_lon = lower_right_lon - lower_left_lat = lower_right_lat - lower_left_lon = upper_left_lon - # Define a Polygon with the 4 points of the zone. - areaBox = Polygon(((upper_left_lon, upper_left_lat), - (upper_right_lon, upper_right_lat), - (lower_right_lon, lower_right_lat), - (lower_left_lon, lower_left_lat), - (upper_left_lon, upper_left_lat)), - srid=settings.EPSG_DISPLAY_PROJECTION) - return areaBox - - def title(self, obj): - return settings.PROJECT_NAME + u" - " +\ - _(u"Last points of interest by area") - - def link(self, obj): - """ - Define the link of the feed. It's the same url as we get in the method - get_object - """ - if not obj: - raise FeedDoesNotExist - return settings.BASE_URL + 'rss/area/' \ - + str(self.upper_left_lat) + '_' + str(self.upper_left_lon) + \ - '_' + str(self.lower_right_lat) + '_' + str(self.lower_right_lon) - - def items(self, obj): - """ - Request to return Markers WHERE there points are containes in the zone - which is requested. - This returns a list of the 15 last markers/POIs ordering by date - """ - q = Marker.objects.filter(point__contained=obj, status__exact='A', - available_date__isnull=False).order_by('-available_date')[:15] - return q - -class LatestPOIsByZoneID(BaseFeed): - ''' - Last Points of interests by zone by id - ''' - title_template = "rss_title.html" - description_template = "rss_descr.html" - - def get_object(self, bits): - if len(bits) != 1: - raise ObjectDoesNotExist - return Area.objects.get(id__exact=bits[0]) - - def title(self, obj): - return settings.PROJECT_NAME + u" - " + \ - _(u"Last points of interest") + u" - " + obj.name - - def link(self, obj): - if not obj: - raise FeedDoesNotExist - return settings.BASE_URL + 'rss/areaid/' + str(obj.id) - - def items(self, obj): - sql = 'select * from "main_marker" where ' + obj.getIncludeSql() - sql += ' and "main_marker".available_date is not null' - sql += ' and "main_marker".status=\'A\'' - sql += ' order by "main_marker".available_date desc limit 15' - q = Marker.objects.raw(sql) - return q diff --git a/chimere/rss/templates/rss.html b/chimere/rss/templates/rss.html deleted file mode 100644 index 0c895ed..0000000 --- a/chimere/rss/templates/rss.html +++ /dev/null @@ -1,73 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} - -{% block sidebar %} -{% endblock %} - -{% block content %} -
-
-{% trans "Subscribe to RSS feed" %} - -

{{ error_message }}

- -
-{%if not category_rss_feed %} -
- - -
-{% endif %} - -{%ifequal category_rss_feed "category" %} -

{% trans "New points of interest by category" %}

-
- - -
-{% endifequal %} - -{%ifequal category_rss_feed "area" %} -

{% trans "New points of interest by area" %}

-{% if area_id %} -
- - -
- -
- -
-{% endif %} -
- -
- {{form.area}} -
-
-

-{% endifequal %} - -
- -
-
-{% endblock %} diff --git a/chimere/rss/templates/rss_descr.html b/chimere/rss/templates/rss_descr.html deleted file mode 100644 index 4f75ac6..0000000 --- a/chimere/rss/templates/rss_descr.html +++ /dev/null @@ -1,8 +0,0 @@ -{% load i18n %} -{% load sanitize %} -
-{% if obj.picture %}{{obj.name}}{%endif%} -
{% for property in obj.getProperties %} -

{{ property.value|sanitize:"p b i br hr strong em span:style a:href:target ul li ol h1 h2 h3 h4"|safe }}

-{% endfor %}
-
diff --git a/chimere/rss/templates/rss_title.html b/chimere/rss/templates/rss_title.html deleted file mode 100644 index 5b379e7..0000000 --- a/chimere/rss/templates/rss_title.html +++ /dev/null @@ -1,2 +0,0 @@ -{% load i18n %} -{{ obj.name }} diff --git a/chimere/rss/urls.py b/chimere/rss/urls.py deleted file mode 100644 index e5e9a24..0000000 --- a/chimere/rss/urls.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2010 Pierre Clarenc , -# Samuel Renard , -# É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. - - -from django.conf.urls.defaults import * - -from chimere.rss.feeds import LatestPOIsByCategory, LatestPOIsBySubCategory, \ - LatestPOIs, LatestPOIsByZone, LatestPOIsByZoneID -from chimere.urls import EXTRA_NO_AREA as EXTRA - -feeds = { - 'category': LatestPOIsByCategory, - 'subcategory': LatestPOIsBySubCategory, - 'global': LatestPOIs, - 'area': LatestPOIsByZone, - 'areaid': LatestPOIsByZoneID -} - -urlpatterns = patterns('', - (EXTRA + r'rss/$', 'chimere.rss.views.rss'), - (EXTRA + r'rss/(?P.*)/$', - 'django.contrib.syndication.views.feed', {'feed_dict': feeds}), -) - diff --git a/chimere/rss/views.py b/chimere/rss/views.py deleted file mode 100644 index 1d6381d..0000000 --- a/chimere/rss/views.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2010 Pierre Clarenc , -# Samuel Renard , -# É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. - -""" -Views of the project -""" - -from django.shortcuts import render_to_response -from django.http import HttpResponseRedirect -from django.utils.translation import ugettext as _ - -from chimere import settings -from chimere.main.views import get_base_response -from chimere.main.actions import actions -from chimere.main.models import SubCategory,Area -from chimere.main.forms import AreaForm -from chimere.main.widgets import AreaWidget - -def rss(request, area_name=''): - ''' - Redirect to RSS subscription page - ''' - response_dct = get_base_response() - response_dct.update({'actions':actions, 'action_selected':('rss',), - 'category_rss_feed':'',}) - # If the form has been submited - if request.method == "POST": - # User has defined the kind of POI he is interested in : POI in a area - # (GET method is used for the link with RSS icon in the browser) - if 'rss_category' in request.POST: - #User wants to follow all the new POI - if request.POST['rss_category'] == 'global': - feeds_link = '/' + settings.EXTRA_URL + 'rss/global/' - return HttpResponseRedirect(feeds_link) - # User wants to follow all the new POI by category or subcategory - elif request.POST['rss_category'] == 'poi': - response_dct['category_rss_feed'] = 'category' - response_dct['sub_categories'] = SubCategory.getAvailable() - return render_to_response('rss.html', response_dct) - # User wants to follow all the new POI situated in a defined area - elif request.POST['rss_category'] == 'area': - # An unbound form - form = AreaForm() - area_widget = AreaWidget().render('area', None) - response_dct.update({'map_layer':settings.MAP_LAYER, - 'extra_head':form.media, - 'form':form, - 'category_rss_feed':'area', - 'area_id':Area.getAvailable(), - 'area_widget':area_widget - }) - return render_to_response('rss.html', response_dct) - # Error when submitting the form - else: - error = _("Incorrect choice in the list") - response_dct.update({'error_message':error, - 'category_rss_feed':'', - 'sub_categories':SubCategory.getAvailable()}) - return render_to_response('rss.html', response_dct) - - # User has specified the category or subcategory he wants to follow => - # we redirect him towards the related rss feed - if 'subcategory' in request.POST and request.POST['subcategory'] != '': - idCat = request.POST['subcategory'] - if idCat.find("cat_") != -1 : - list_Cat = idCat.split('_') - feeds_link = '/' + settings.EXTRA_URL + 'rss/category/' - feeds_link += list_Cat[1] - return HttpResponseRedirect(feeds_link) - - else: - feeds_link = '/' + settings.EXTRA_URL + 'rss/subcategory/' + \ - idCat - return HttpResponseRedirect(feeds_link) - - # User has specified the ID of the area he wants to follow - if 'id_area' in request.POST and request.POST['id_area'] != '': - feeds_link = '/' + settings.EXTRA_URL + 'rss/areaid/' \ - + request.POST['id_area'] - return HttpResponseRedirect(feeds_link) - - # User has specified the area he wants to follow => we redirect him - # towards the related rss feed (using upper left and lower right - # coordinates) - elif 'upper_left_lat' in request.POST and \ - request.POST['upper_left_lat'] != '' and \ - 'upper_left_lon' in request.POST and \ - request.POST['upper_left_lon'] != '' and \ - 'lower_right_lon' in request.POST and \ - request.POST['lower_right_lon'] != '' and \ - 'lower_right_lat' in request.POST and \ - request.POST['lower_right_lat'] != '' : - feeds_link = '/' + settings.EXTRA_URL + 'rss/area/' + \ -request.POST['upper_left_lat'] + '_' + request.POST['upper_left_lon'] + '_' + \ -request.POST['lower_right_lat'] + '_' + request.POST['lower_right_lon'] - return HttpResponseRedirect(feeds_link) - - - # GET method is used for linking with the RSS icon in the browser when user - # wants to choose a category to follow - elif request.method == "GET" and 'rss_category' in request.GET: - if request.GET['rss_category'] == 'global': - feeds_link = '/' + settings.EXTRA_URL + 'rss/global/' - return HttpResponseRedirect(feeds_link) - if request.GET['rss_category'] == 'poi': - response_dct['category_rss_feed'] = 'category' - response_dct['sub_categories'] = SubCategory.getAvailable(['M','B']) - return render_to_response('rss.html', response_dct) - if request.GET['rss_category'] == 'area': - # An unbound form - form = AreaForm() - response_dct.update({'map_layer':settings.MAP_LAYER, - 'extra_head':form.media, - 'form':form, - 'category_rss_feed':'area', - 'area_id':Area.getAvailable(), - 'area_widget':AreaWidget().render('area', None)}) - return render_to_response('rss.html', response_dct) - - # User access to the RSS tab - else: - return render_to_response('rss.html', response_dct) diff --git a/chimere/templatetags/__init__.py b/chimere/templatetags/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/chimere/templatetags/__init__.py @@ -0,0 +1 @@ +# diff --git a/chimere/templatetags/sanitize.py b/chimere/templatetags/sanitize.py new file mode 100644 index 0000000..ccb936c --- /dev/null +++ b/chimere/templatetags/sanitize.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from django import template +from BeautifulSoup import BeautifulSoup, Comment +import re + +register = template.Library() + +def sanitize(value, allowed_tags): + """Argument should be in form 'tag2:attr1:attr2 tag2:attr1 tag3', where tags + are allowed HTML tags, and attrs are the allowed attributes for that tag. + """ + js_regex = re.compile(r'[\s]*(&#x.{1,7})?'.join(list('javascript'))) + allowed_tags = [tag.split(':') for tag in allowed_tags.split()] + allowed_tags = dict((tag[0], tag[1:]) for tag in allowed_tags) + + soup = BeautifulSoup(value) + for comment in soup.findAll(text=lambda text: isinstance(text, Comment)): + comment.extract() + + for tag in soup.findAll(True): + if tag.name not in allowed_tags: + tag.hidden = True + else: + tag.attrs = [(attr, js_regex.sub('', val)) for attr, val in tag.attrs + if attr in allowed_tags[tag.name]] + return soup.renderContents().decode('utf8') + +register.filter(sanitize) + diff --git a/chimere/templatetags/unlocalize_point.py b/chimere/templatetags/unlocalize_point.py new file mode 100644 index 0000000..f52a90a --- /dev/null +++ b/chimere/templatetags/unlocalize_point.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from django import template +import re + +register = template.Library() + +def unlocalize_point(value): + """ + Basic unlocalize filter for django 1.2 + """ + return unicode(value).replace(',', '.') + +register.filter(unlocalize_point) + diff --git a/chimere/urls.py b/chimere/urls.py index 41a3729..6aebf72 100644 --- a/chimere/urls.py +++ b/chimere/urls.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2008-2011 Étienne Loks +# Copyright (C) 2008-2012 É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 @@ -17,70 +17,46 @@ # See the file COPYING for details. - +from django.conf import settings from django.conf.urls.defaults import * - from django.contrib import admin admin.autodiscover() -from settings import ROOT_PATH, EXTRA_URL, INSTALLED_APPS - -from main.models import Area +from chimere.models import Area def i18n_javascript(request): return admin.site.i18n_javascript(request) -BASE = '^' + EXTRA_URL -urlpatterns = patterns('', - (BASE + r'admin/jsi18n/$', i18n_javascript), - (BASE + r'admin/(.*)', admin.site.root), - (BASE + r'static/(?P.*)$', 'django.views.static.serve', - {'document_root': ROOT_PATH + 'static/'}), - (BASE + r'media/(?P.*)$', 'django.views.static.serve', - {'document_root': ROOT_PATH + 'media/'}), - (BASE + r'charte/$', 'chimere.main.views.charte'), +urlpatterns = patterns('chimere.views', + url(r'^simple(/?P\w+)?$', 'index', {'simple':True}, + name="simple_index") ) -urlpatterns += patterns('chimere.main.views', +urlpatterns += patterns('chimere.views', + url(r'^charte/?$', 'charte', name="charte"), + url(r'^(?P\w+)?/?contact/?$', 'contactus', name="contact"), + url(r'^(?P\w+)?/?submited/(?P\w+)/?$', 'submited', + name="submit"), + url(r'^(?P\w+)?/?edit/?$', 'edit', + name="edit"), + url(r'^(?P\w+)?/?edit_route/?$', 'editRoute', + name="edit_route"), + url(r'^(?P\w+)?/?getDetail/(?P\d+)/?$', 'getDetail', + name="get_detail"), + url(r'^(?P\w+)?/?getDescriptionDetail/?(?P\d+)/?$', + 'getDescriptionDetail', name="get_description_detail"), + url(r'^(?:(?P\w+)?/)?getGeoObjects/(?P\w+)(/(?P\w+))?$', + 'getGeoObjects', name="getgeoobjects"), + url(r'^(?P\w+)?/?getAvailableCategories/$', + 'get_available_categories', name="get_categories"), + url(r'^(?P\w+)?/?ty/(?P\w+)$', + 'redirectFromTinyURN', name="tiny"), ) -url_areas = Area.objects.filter(urn__isnull=False) -urlpatterns += patterns('chimere.main.views', (BASE + r'$', 'index'), - (BASE + r'simple/?$', 'index', {'simple':True}) ) - -for area in url_areas: - urlpatterns += patterns('chimere.main.views', - (BASE + '(' + area.urn + ')/?$', 'index', {'default_area':area}), - (BASE + '(' + area.urn + ')/simple/?$', 'index', {'default_area':area, - 'simple':True}),) - -EXTRA = "|".join([area.urn for area in url_areas]) -default_dct = {} -EXTRA_NO_AREA = EXTRA -if EXTRA: - EXTRA_NO_AREA = "(%s)?/?" % EXTRA - EXTRA = "(?P%s)?/?" % EXTRA -else: - default_dct = {'area_name':''} -urlpatterns += patterns('chimere.main.views', -(BASE + EXTRA + r'contact/$', 'contactus', default_dct), -(BASE + EXTRA + r'edit/$', 'edit', default_dct), -(BASE + EXTRA + r'edit_route/$', 'editRoute', default_dct), -(BASE + EXTRA + r'upload_file/((?P\w+)/)?$', 'uploadFile', - default_dct), -(BASE + EXTRA + r'process_route_file/(?P\d+)/$', 'processRouteFile', default_dct), -(BASE + EXTRA + r'submited/(?P\w+)/$', 'submited', default_dct), -(BASE + EXTRA + r'getDetail/(?P\d+)/$', 'getDetail', default_dct), -(BASE + EXTRA + r'getDescriptionDetail/(?P\d+)/$', - 'getDescriptionDetail', default_dct), -(BASE + EXTRA + r'getGeoObjects/(?P\w+)(/(?P\w+))?$', - 'getGeoObjects', default_dct), -(BASE + EXTRA + r'getAvailableCategories/((?P\w+))?(/(?P\w+))?(/(?P\w+))?$', - 'getAvailableCategories', default_dct), -(BASE + EXTRA + r'ty/(?P\w+)$', 'redirectFromTinyURN', default_dct), +# At the end, because it catches large +urlpatterns += patterns('chimere.views', + url(r'^(?P\w+)?', 'index', name="index"), ) -if 'chimere.rss' in INSTALLED_APPS: - urlpatterns += patterns('', - (r'^' + EXTRA_URL, include('chimere.rss.urls')),) + diff --git a/chimere/views.py b/chimere/views.py new file mode 100644 index 0000000..24948ad --- /dev/null +++ b/chimere/views.py @@ -0,0 +1,470 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2008-2011 É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. + +""" +Views of the project +""" + +import datetime +from itertools import groupby + +from django.utils.translation import ugettext as _ +from django.shortcuts import render_to_response +from django.template import loader +from django.http import HttpResponseRedirect, HttpResponse +from django.core import serializers +from django.utils.http import urlquote +from django.db.models import Q +from django.utils import simplejson + +from chimere import settings +from chimere.actions import actions +from chimere.models import Category, SubCategory, PropertyModel, \ + Marker, Route, News, SimpleArea, Area, Color, TinyUrl, RouteFile + +from chimere.widgets import getMapJS, PointChooserWidget, \ + RouteChooserWidget, URL_OSM_JS, URL_OSM_CSS +from chimere.forms import MarkerForm, RouteForm, ContactForm, \ + FileForm, FullFileForm, notifySubmission, notifyStaff + +def get_base_response(area_name=""): + """ + Get the base url + """ + base_response_dct = {'media_path':settings.MEDIA_URL,} + base_url = settings.EXTRA_URL + if not base_url.startswith('/'): + base_url = '/' + base_url + if area_name: + if base_url[-1] != '/': + base_url += '/' + base_url += area_name + '/' + base_response_dct['extra_url'] = base_url + if settings.CSS_AREAS and area_name: + base_response_dct['css_area'] = area_name + ".css" + base_response_dct['area_name'] = area_name + base_response_dct['JQUERY_URL'] = settings.JQUERY_URL + return base_response_dct + +def index(request, area_name=None, default_area=None, simple=False): + """ + Main page + """ + extra = "" + tab = " "*4 + for url in URL_OSM_CSS: + extra += tab + '' % url + for url in URL_OSM_JS + ["%sbase.js" % settings.MEDIA_URL, + "%smain_map.js" % settings.MEDIA_URL,]: + extra += tab + '\n' % url + # show the welcome page + today = datetime.date.today().strftime('%y-%m-%d') + display_welcome = None + if not 'last_visit' in request.session or \ + request.session['last_visit'] != today: + request.session['last_visit'] = today + display_welcome = True + response_dct = get_base_response(area_name) + areas = None + if settings.DISPLAY_AREAS: + areas = Area.getAvailable() + response_dct.update({'actions':actions, 'action_selected':('view',), + 'error_message':'', 'default_area':default_area, + 'extra_head':extra + getMapJS(area_name), + 'welcome':welcome(request, display_welcome), + 'areas':areas, 'map_layer':settings.MAP_LAYER, + 'dynamic_categories':settings.DYNAMIC_CATEGORIES, + }) + # manage permalink + if request.GET: + for key in ('zoom', 'lon', 'lat', 'display_submited', + 'current_feature'): + if key in request.GET and request.GET[key]: + response_dct['p_'+key] = request.GET[key] + else: + response_dct['p_'+key] = None + if 'checked_categories' in request.GET \ + and request.GET['checked_categories']: + cats = request.GET['checked_categories'].split('_') + response_dct['p_checked_categories'] = ",".join(cats) + else: + response_dct['p_checked_categories'] = ''; + tpl = 'main_map.html' + if simple: + tpl = 'main_map_simple.html' + return render_to_response(tpl, response_dct) + +def edit(request, area_name=""): + """ + Edition page + """ + # If the form has been submited + if request.method == 'POST': + form = MarkerForm(request.POST, request.FILES) + # All validation rules pass + if form.is_valid(): + marker = form.save() + # set the submited status + marker.status = 'S' + marker.save() + notifySubmission(marker) + response_dct = get_base_response(area_name) + return HttpResponseRedirect(response_dct['extra_url'] + \ + 'submited/edit') + else: + # An unbound form + form = MarkerForm() + # get the « manualy » declared_fields. Ie: properties + declared_fields = form.declared_fields.keys() + response_dct = get_base_response(area_name) + response_dct.update({'actions':actions, + 'action_selected':('contribute', 'edit'), + 'error_message':'', + 'map_layer':settings.MAP_LAYER, + 'form':form, + 'dated':settings.DAYS_BEFORE_EVENT, + 'extra_head':form.media, + 'sub_categories':SubCategory.getAvailable(['M', 'B'], + area_name), + 'point_widget':PointChooserWidget().render('point', None, + area_name=area_name), + 'properties':declared_fields, + }) + # manualy populate the custom widget + if 'subcategory' in form.data and form.data['subcategory']: + response_dct['current_category'] = int(form.data['subcategory']) + return render_to_response('edit.html', response_dct) + +def uploadFile(request, category_id='', area_name=''): + response_dct = get_base_response(area_name) + Form = FileForm if not category_id else FullFileForm + category = None + if category_id: + try: + category = SubCategory.objects.get(pk=category_id) + response_dct['category'] = unicode(category) + except: + pass + # If the form has been submited + if request.method == 'POST': + form = Form(request.POST, request.FILES) + # All validation rules pass + if form.is_valid(): + raw_file = form.cleaned_data['raw_file'] + name = raw_file.name.split('.')[0] + file_type = raw_file.name.split('.')[-1][0].upper() + routefile = RouteFile(raw_file=raw_file, name=name, + file_type=file_type) + routefile.save() + if not category_id: + response_dct['gpx_id'] = routefile.pk + return render_to_response('upload_file.html', response_dct) + routefile.process() + if not routefile.route: + response_dct['errors'] = _(u"Bad file. Please check it with an " + u"external software.") + response_dct.update({'form':form}) + return render_to_response('upload_file.html', response_dct) + route = Route(name=form.cleaned_data['name'], route=routefile.route, + associated_file=routefile, status='S') + route.save() + route.categories.add(category) + route.save() + response_dct['thanks'] = True + form = Form() + else: + # An unbound form + form = Form() + response_dct.update({'form':form}) + return render_to_response('upload_file.html', response_dct) + +def processRouteFile(request, area_name='', file_id=None): + if file_id: + try: + route_file = RouteFile.objects.get(pk=file_id) + route_file.process() + route = route_file.route + if not route: + return HttpResponse(status=500) + return HttpResponse(simplejson.dumps({'wkt':route, + 'file_id':file_id}), + 'application/javascript', status=200) + except: + return HttpResponse(status=500) + else: + return HttpResponse(status=400) + +def editRoute(request, area_name=""): + """ + Route edition page + """ + # If the form has been submited + if request.method == 'POST': + form = RouteForm(request.POST, request.FILES) + # All validation rules pass + if form.is_valid(): + route = form.save() + # set the submited status + route.status = 'S' + route.save() + notifySubmission(route) + response_dct = get_base_response(area_name) + return HttpResponseRedirect(response_dct['extra_url'] + \ + 'submited/edit') + else: + # An unbound form + form = RouteForm() + # get the "manualy" declared_fields. Ie: properties + declared_fields = form.declared_fields.keys() + response_dct = get_base_response(area_name) + response_dct.update({'actions':actions, + 'action_selected':('contribute', 'edit_route'), + 'error_message':'', + 'map_layer':settings.MAP_LAYER, + 'form':form, + 'dated':settings.DAYS_BEFORE_EVENT, + 'extra_head':form.media, + 'sub_categories':SubCategory.getAvailable(['R', 'B'], + area_name), + 'route_widget':RouteChooserWidget().render('route', '', + area_name=area_name, + routefile_id='',), + 'properties':declared_fields + }) + # manualy populate the custom widget + if 'subcategory' in form.data and form.data['subcategory']: + response_dct['current_category'] = int(form.data['subcategory']) + return render_to_response('edit_route.html', response_dct) + +def welcome(request, display=None): + """ + Welcome string + """ + response_dct = {'display':display} + news = list(News.objects.filter(available=True).all()) + if settings.DAYS_BEFORE_EVENT: + q = checkDate(Q(status='A', start_date__isnull=False)) + news += list(Marker.objects.filter(q).all()) + news.sort(key=lambda x:x.date) + response_dct['news_lst'] = news + return loader.render_to_string('welcome.html', response_dct) + +def submited(request, area_name="", action=""): + """ + Successful submission page + """ + response_dct = get_base_response(area_name) + response_dct.update({'actions':actions, 'action_selected':action,}) + return render_to_response('submited.html', response_dct) + +def charte(request, area_name=""): + """ + Affichage de la charte + """ + response_dct = get_base_response(area_name) + response_dct.update({'actions':actions, 'action_selected':('charte',)}) + return render_to_response('charte.html', response_dct) + +def contactus(request, area_name=""): + """ + Contact page + """ + form = None + msg = '' + # If the form has been submited + if request.method == 'POST': + form = ContactForm(request.POST) + # All validation rules pass + if form.is_valid(): + response = notifyStaff(_(u"Comments/request on the map"), + form.cleaned_data['content'], form.cleaned_data['email']) + if response: + msg = _(u"Thank you for your contribution. It will be taken \ +into account. If you have left your email you may be contacted soon for more \ +details.") + else: + msg = _(u"Temporary error. Renew your message later.") + else: + form = ContactForm() + response_dct = get_base_response(area_name) + response_dct.update({'actions':actions, 'action_selected':('contact',), + 'contact_form':form, 'message':msg}) + return render_to_response('contactus.html', response_dct) + +def getDetail(request, area_name, marker_id): + ''' + Get the detail for a marker + ''' + try: + marker = Marker.objects.filter(id=int(marker_id), status__in=['A', 'S'])[0] + except (ValueError, IndexError): + return HttpResponse('no results') + response_dct = get_base_response() + response_dct['marker'] = marker + if request.method == 'GET': + if 'simple' in request.GET and request.GET['simple']: + response_dct['simple'] = True + parameters = u'current_feature=%s' % marker_id + parameters += u"&checked_categories=%s" % "_".join([str(m.id) \ + for m in marker.categories.all()]) + net_dct = getTinyfiedUrl(parameters, area_name) + share_networks = [] + for network in settings.SHARE_NETWORKS: + share_networks.append((network[0], network[1] % net_dct, network[2])) + response_dct['share_networks'] = share_networks + response_dct['dated'] = settings.DAYS_BEFORE_EVENT and marker.start_date + return render_to_response('detail.html', response_dct) + +def getDescriptionDetail(request, area_name, category_id): + ''' + Get the description for a category + ''' + try: + category = Category.objects.filter(id=int(category_id))[0] + except (ValueError, IndexError): + return HttpResponse('no results') + response_dct = get_base_response(area_name) + response_dct['category'] = category + return render_to_response('category_detail.html', response_dct) + +def checkDate(q): + """ + Filter a queryset to manage dates + """ + if not settings.DAYS_BEFORE_EVENT: + return q + today = datetime.date.today() + after = today + datetime.timedelta(settings.DAYS_BEFORE_EVENT) + + q = q & ( Q(start_date__isnull=True) + | Q(start_date__gte=today, start_date__lte=after) + | Q(start_date__lte=today, end_date__gte=today) + ) + return q + +def getGeoObjects(request, area_name, category_ids, status): + ''' + Get the JSON for markers and routes + ''' + if not status: + status = 'A' + status = status.split('_') + category_ids = category_ids.split('_') + try: + q = checkDate(Q(status__in=status, categories__in=category_ids)) + query = Route.objects.filter(q) + except: + return HttpResponse('no results') + query.order_by('categories') + routes = list(query) + jsons = [] + current_cat, colors, idx = None, None, 0 + for route in routes: + c_cat = route.categories.all()[0] + if not current_cat or current_cat != c_cat: + idx = 0 + current_cat = c_cat + colors = list(Color.objects.filter(color_theme = c_cat.color_theme)) + if colors: + jsons.append(route.getGeoJSON(color=colors[idx % len(colors)].code)) + else: + jsons.append(route.getGeoJSON(color='000')) + idx += 1 + try: + q = checkDate(Q(status__in=status, categories__in=category_ids)) + query = Marker.objects.filter(q) + except: + return HttpResponse('no results') + category_ids = [int(cat_id) for cat_id in category_ids] + jsons += [geo_object.getGeoJSON(category_ids) for geo_object in list(query)] + if not jsons: + return HttpResponse('no results') + data = '{"type": "FeatureCollection", "features":[%s]}' % ",".join(jsons) + return HttpResponse(data) + +def getAvailableCategories(request, area_name=None, area=None, status='A', + force=None): + ''' + Get categories for a designed area + ''' + if settings.DYNAMIC_CATEGORIES and not area: + return "" + response_dct = get_base_response('area_name') + if not settings.DYNAMIC_CATEGORIES: + subcategories = SubCategory.getAvailable() + response_dct['sub_categories'] = subcategories + return render_to_response('categories.html', response_dct) + default_message = "

%s

" % \ + _("No category available in this area.") + if not status: # there must be a status + status = 'A' + try: + status = status.split('_') + area = area.replace('M', '-').replace('D', '.') + area = SimpleArea([float(pt) for pt in area.split('_')]) + except: + # bad area format + return HttpResponse(default_message) + # if not force and area.isIn(SimpleArea(cookie.AREA):return + categories = area.getCategories(status) + if not categories: + return HttpResponse(default_message) + get_cat = lambda subcat: subcat.category + get_cat_order = lambda subcat: (subcat.category.order, subcat.category, + subcat.order) + categories = sorted(categories, key=get_cat_order) + subcategories = [(cat, list(subcats)) \ + for cat, subcats in groupby(categories, get_cat)] + response_dct['sub_categories'] = subcategories + return render_to_response('categories.html', response_dct) + +def getTinyfiedUrl(parameters, area_name=''): + ''' + Get the tinyfied version of parameters + ''' + data = {"urn": "", "url":"", "text":""} + try: + urn = TinyUrl.getUrnByParameters(parameters) + except: + return {} + response_dct = get_base_response(area_name) + url = settings.SERVER_URL + if url[-1] == '/': + url = url[:-1] + url += response_dct['extra_url'] + 'ty/' + urn + text = settings.PROJECT_NAME + if 'current_feature' in parameters: + for item in parameters.split('&'): + if 'current_feature' in item: + try: + text = unicode(Marker.objects.get(id=item.split('=')[1])) + except (IndexError, Marker.DoesNotExist): + pass + data["urn"] = urlquote(urn) + data["url"] = urlquote(url) + data["text"] = urlquote(text) + return data + +def redirectFromTinyURN(request, area_name='', tiny_urn=''): + """ + Redirect from a tiny Urn + """ + parameters = '?' + TinyUrl.getParametersByUrn(tiny_urn) + response_dct = get_base_response(area_name) + return HttpResponseRedirect(response_dct['extra_url'] + parameters) diff --git a/chimere/widgets.py b/chimere/widgets.py new file mode 100644 index 0000000..ec63ea3 --- /dev/null +++ b/chimere/widgets.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2008-2011 É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. + +""" +Extra widgets and fields +""" + +from django import forms +from django.conf import settings +from django.contrib.gis.db import models +from django.contrib.gis.geos import fromstr +from django.utils.safestring import mark_safe +from django.utils.translation import ugettext as _ + +URL_OSM_CSS = ["http://www.openlayers.org/api/theme/default/style.css"] +URL_OSM_JS = [settings.MEDIA_URL+"OpenLayers.js", + "http://www.openstreetmap.org/openlayers/OpenStreetMap.js"] + +def getMapJS(area_name=''): + '''Variable initialization for drawing the map + ''' + # projection, center and bounds definitions + js = u"var epsg_display_projection = new OpenLayers.Projection('EPSG:%d')\ +;\n" % settings.EPSG_DISPLAY_PROJECTION + js += u"var epsg_projection = new OpenLayers.Projection('EPSG:%d');\n" % \ + settings.EPSG_PROJECTION + js += u"var centerLonLat = new OpenLayers.LonLat(%f,\ +%f).transform(epsg_display_projection, epsg_projection);\n" % \ + settings.DEFAULT_CENTER + js += u"var media_path = '%s';\n" % settings.MEDIA_URL + js += u"var map_layer = %s;\n" % settings.MAP_LAYER + js += u"var restricted_extent;\n" + + if area_name: + js += u"var area_name='%s';\n" % area_name + if settings.RESTRICTED_EXTENT: + restricted_extent_str = [str(coord) \ + for coord in settings.RESTRICTED_EXTENT] + js += u"restricted_extent = new OpenLayers.Bounds(%s);\n" %\ + ", ".join(restricted_extent_str) + js = u""" +""" % js + return js + +class TextareaWidget(forms.Textarea): + """ + Manage the edition of a text using TinyMCE + """ + class Media: + js = ["%stiny_mce.js" % settings.TINYMCE_URL, + "%stextareas.js" % settings.MEDIA_URL,] + +class PointChooserWidget(forms.TextInput): + """ + Manage the edition of point on a map + """ + class Media: + css = { + "all": URL_OSM_CSS + ["%sforms.css" % settings.MEDIA_URL,] + } + js = URL_OSM_JS + ["%sedit_map.js" % settings.MEDIA_URL, + "%sbase.js" % settings.MEDIA_URL,] + + def render(self, name, value, attrs=None, area_name=''): + ''' + Render a map and latitude, longitude information field + ''' + val = '0' + value_x, value_y = 0, 0 + if value: + val = str(value) + if hasattr(value, 'x') and hasattr(value, 'y'): + value_x, value_y = value.x, value.y + elif isinstance(value, unicode) and value.startswith('POINT('): + try: + value_x, value_y = value.split('(')[1][:-1].split(' ') + value_x, value_y = float(value_x), float(value_y) + except: + value = None + else: + value = None + tpl = getMapJS(area_name) + tpl += u'\n' % settings.MEDIA_URL + tpl += u"""
+
+

\ +

+

+
+ +""" % (_("Latitude"), value_y, _("Longitude"), value_x, name, name, val) + tpl += " +
+""" + return mark_safe(tpl) + +class PointField(models.PointField): + ''' + Set the widget for the form field + ''' + def formfield(self, **keys): + defaults = {'widget': PointChooserWidget} + keys.update(defaults) + return super(PointField, self).formfield(**keys) + + def clean(self, value, instance=None): + if len(value) != 2 and self.required: + raise ValidationError(_("Invalid point")) + return value + +class RouteChooserWidget(forms.TextInput): + """ + Manage the edition of route on a map + """ + class Media: + css = { + "all": URL_OSM_CSS + ["%sforms.css" % settings.MEDIA_URL,] + } + js = ["%sedit_route_map.js" % settings.MEDIA_URL, + "%sbase.js" % settings.MEDIA_URL,] + URL_OSM_JS + + def render(self, name, value, attrs=None, area_name='', routefile_id=None): + ''' + Render a map and latitude, longitude information field + ''' + tpl = getMapJS(area_name) + help_create = '' + if not value: + help_create = """

%s

+

%s

+

%s

+

%s

+

%s

+

%s

""" % (_(u"Creation mode"), +_(u"To start drawing the route click on the toggle button: \"Draw\"."), +_(u"Then click on the map to begin the drawing."), +_(u"You can add points by clicking again."), +_(u"To finish the drawing double click. When the drawing is finished you can \ +edit it."), +_(u"While creating to undo a drawing click again on the toggle button \"Stop \ +drawing\".")) + help_modify = """

%s

+

%s

+

%s

+

%s

""" % (_(u"Modification mode"), +_(u"To move a point click on it and drag it to the desired position."), +_(u"To delete a point move the mouse cursor over it and press the \"d\" or \ +\"Del\" key."), +_(u"To add a point click in the middle of a segment and drag the new point to \ +the desired position")) + tpl += u'\n' % \ + settings.MEDIA_URL + if not value: + # upload a file + tpl += u""" +""" % _(u"Give a name and set category before uploading a file.") + tpl += u'' % ( + _(u"Upload a route file (GPX or KML)")) + tpl += u"""\n

%s

\n""" % _(u"or") + tpl += u"""
+%s
+
+
""" % (_(u"Start \"hand\" drawing")) + if value: + tpl += """ +
""" + else: + tpl += """ +
+
+ %s + %s
+
""" % (_(u"Move on the map"), _(u"Draw")) + tpl += ''' +
%s
''' % help_create + style = '' + if value: + style = " style='display:block'" + tpl += """ +
%s
+
+ + +""" % (style, help_modify, name, name, value, routefile_id) + tpl += " +""" + return mark_safe(tpl) + +class RouteField(models.LineStringField): + ''' + Set the widget for the form field + ''' + def formfield(self, **keys): + defaults = {'widget': RouteChooserWidget} + keys.update(defaults) + return super(RouteField, self).formfield(**keys) + +class AreaWidget(forms.TextInput): + """ + Manage the edition of an area on the map + """ + class Media: + css = { + "all": URL_OSM_CSS + ["%sforms.css" % settings.MEDIA_URL,] + } + js = URL_OSM_JS + ["%sedit_area.js" % settings.MEDIA_URL, + "%sbase.js" % settings.MEDIA_URL,] + + def render(self, name, value, attrs=None): + """ + Render a map + """ + upper_left_lat, upper_left_lon = 0, 0 + lower_right_lat, lower_right_lon = 0, 0 + if value: + if len(value) == 2: + upper_left = value[0] + lower_right = value[1] + if hasattr(upper_left, 'x') and hasattr(upper_left, 'y'): + upper_left_lon, upper_left_lat = upper_left.x, upper_left.y + if hasattr(lower_right, 'x') and hasattr(lower_right, 'y'): + lower_right_lon, lower_right_lat = lower_right.x, \ + lower_right.y + tpl = getMapJS() + tpl += u"""
+ + + + +""" % (upper_left_lat, upper_left_lon, lower_right_lat, lower_right_lon) + tpl += """ +
+""" + return mark_safe(tpl) + + def value_from_datadict(self, data, files, name): + """ + Return the appropriate values + """ + values = [] + for keys in (('upper_left_lon', 'upper_left_lat',), + ('lower_right_lon', 'lower_right_lat')): + value = [] + for key in keys: + val = data.get(key, None) + if not val: + return [] + value.append(val) + values.append(value) + return values + +class AreaField(forms.MultiValueField): + ''' + Set the widget for the form field + ''' + widget = AreaWidget + + def compress(self, data_list): + if not data_list: + return None + return data_list + +class MultiSelectWidget(forms.SelectMultiple): + class Media: + css = {'all': ( + settings.MEDIA_URL + 'jquery/bsmSelect/css/jquery.bsmselect.css', + settings.MEDIA_URL + 'jquery/css/jquery.bsmselect.custom.css', + ) + } + js = ( + settings.MEDIA_URL + 'jquery/bsmSelect/js/jquery.bsmselect.js', + settings.MEDIA_URL + 'jquery/bsmSelect/js/jquery.bsmselect.compatibility.js', + ) + + def render(self, name, value, attrs=None): + rendered = super(MultiSelectWidget, self).render(name, value, attrs) + return mark_safe(rendered + u'''
''' % {'name':name, 'title':_("Select...")}) + +class SelectMultipleField(models.ManyToManyField): + ''' + Set the widget for the category field + ''' + def formfield(self, **keys): + self.help_text = "" + defaults = {'widget': MultiSelectWidget} + keys.update(defaults) + return super(SelectMultipleField, self).formfield(**keys) + +from south.modelsinspector import add_introspection_rules +add_introspection_rules([], ["^chimere\.widgets\.PointField"]) +add_introspection_rules([], ["^chimere\.widgets\.SelectMultipleField"]) +add_introspection_rules([], ["^chimere\.widgets\.RouteField"]) diff --git a/chimere_rss/__init__.py b/chimere_rss/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/chimere_rss/__init__.py @@ -0,0 +1 @@ +# diff --git a/chimere_rss/feeds.py b/chimere_rss/feeds.py new file mode 100644 index 0000000..18a4259 --- /dev/null +++ b/chimere_rss/feeds.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010 Pierre Clarenc , +# Samuel Renard , +# É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. + +from django.utils.translation import ugettext as _ +from django.contrib.syndication.feeds import Feed +from django.contrib.syndication.feeds import FeedDoesNotExist +from chimere.main.models import Category, SubCategory, Marker, Area +from django.core.exceptions import ObjectDoesNotExist +from django.contrib.gis.geos import * + +from chimere import settings + +class BaseFeed(Feed): + """ + Base feed for Chimere objects + """ + def item_link(self, item): + ''' Return POI permalink ''' + coord = item.point + cat = 0 + if item.categories.all() and item.categories.all()[0]: + cat = item.categories.all()[0].pk + return settings.BASE_URL + '?zoom=16&lat=%d&lon=%d¤t_feature=%d&\ +checked_categories=%d' % (coord.y, coord.x, item.id, cat) + + def item_pubdate(self, item): + """ + Date of the Marker when it has been available + """ + return item.available_date + + def description(self, obj): + return "" + +class LatestPOIsByCategory(BaseFeed): + ''' + Last Points of interests by category in Feeds + ''' + title_template = "rss_title.html" + description_template = "rss_descr.html" + + def get_object(self, bits): + """ + Get extra url, after rss/category/ id of category + """ + if len(bits) != 1: + raise ObjectDoesNotExist + return Category.objects.get(id__exact=bits[0]) + + def title(self, obj): + """ + Define the title of the feed + """ + return u"%s - %s" % (settings.PROJECT_NAME, obj.name) + + def link(self, obj): + """ + Define the link of the feed. + """ + if not obj: + raise FeedDoesNotExist + return settings.BASE_URL + 'rss/category/' + str(obj.id) + + def items(self, obj): + """ + Requests to marker where its category match the category is requested + and its status is available + This returns a list of the 15 last markers/POIs ordering by date + """ + q = Marker.objects.filter(status__exact='A', + categories__category__id__exact=obj.id, + available_date__isnull=False).order_by('-available_date')[:15] + return q + +class LatestPOIsBySubCategory(BaseFeed): + ''' + Last Points of interests by SubCategory in Feeds + ''' + title_template = "rss_title.html" + description_template = "rss_descr.html" + + def get_object(self, bits): + if len(bits) != 1: + raise ObjectDoesNotExist + return SubCategory.objects.get(id__exact=bits[0]) + + def title(self, obj): + return u"%s - %s - %s" % (settings.PROJECT_NAME, obj.category.name, + obj.name) + + def link(self, obj): + if not obj: + raise FeedDoesNotExist + return settings.BASE_URL + 'rss/subcategory/' + str(obj.id) + + def items(self, obj): + q = Marker.objects.filter(categories__id__exact=obj.id, + available_date__isnull=False, status__exact='A').order_by( + '-available_date')[:15] + return q + +class LatestPOIs(BaseFeed): + ''' + Last Points of interests + ''' + title_template = "rss_title.html" + description_template = "rss_descr.html" + + def title(self): + return settings.PROJECT_NAME + u" - " + _(u"Last points of interest") + + def link(self): + return settings.BASE_URL + 'rss/categories/' + + def description(self): + return _("Latest points of interest from ") + settings.PROJECT_NAME + + def items(self): + q = Marker.objects.filter(status__exact='A', + available_date__isnull=False).order_by('-available_date')[:15] + return q + +class LatestPOIsByZone(BaseFeed): + ''' + Last Points of interests by zone by coordinates + ''' + title_template = "rss_title.html" + description_template = "rss_descr.html" + upper_left_lat = 0 + upper_left_lon = 0 + lower_right_lat = 0 + lower_right_lon = 0 + + def get_object(self, bits): + """ + Get the extra url. Parameters are the coordinates of the zone (the + upper left and lower right points) + """ + if len(bits) != 1: + raise ObjectDoesNotExist + # Then define the upper right and lower left points + coordinates = str(bits[0]).split('_') + upper_left_lat = float(coordinates[0]) + upper_left_lon = float(coordinates[1]) + lower_right_lat = float(coordinates[2]) + lower_right_lon = float(coordinates[3]) + upper_right_lat = upper_left_lat + upper_right_lon = lower_right_lon + lower_left_lat = lower_right_lat + lower_left_lon = upper_left_lon + # Define a Polygon with the 4 points of the zone. + areaBox = Polygon(((upper_left_lon, upper_left_lat), + (upper_right_lon, upper_right_lat), + (lower_right_lon, lower_right_lat), + (lower_left_lon, lower_left_lat), + (upper_left_lon, upper_left_lat)), + srid=settings.EPSG_DISPLAY_PROJECTION) + return areaBox + + def title(self, obj): + return settings.PROJECT_NAME + u" - " +\ + _(u"Last points of interest by area") + + def link(self, obj): + """ + Define the link of the feed. It's the same url as we get in the method + get_object + """ + if not obj: + raise FeedDoesNotExist + return settings.BASE_URL + 'rss/area/' \ + + str(self.upper_left_lat) + '_' + str(self.upper_left_lon) + \ + '_' + str(self.lower_right_lat) + '_' + str(self.lower_right_lon) + + def items(self, obj): + """ + Request to return Markers WHERE there points are containes in the zone + which is requested. + This returns a list of the 15 last markers/POIs ordering by date + """ + q = Marker.objects.filter(point__contained=obj, status__exact='A', + available_date__isnull=False).order_by('-available_date')[:15] + return q + +class LatestPOIsByZoneID(BaseFeed): + ''' + Last Points of interests by zone by id + ''' + title_template = "rss_title.html" + description_template = "rss_descr.html" + + def get_object(self, bits): + if len(bits) != 1: + raise ObjectDoesNotExist + return Area.objects.get(id__exact=bits[0]) + + def title(self, obj): + return settings.PROJECT_NAME + u" - " + \ + _(u"Last points of interest") + u" - " + obj.name + + def link(self, obj): + if not obj: + raise FeedDoesNotExist + return settings.BASE_URL + 'rss/areaid/' + str(obj.id) + + def items(self, obj): + sql = 'select * from "main_marker" where ' + obj.getIncludeSql() + sql += ' and "main_marker".available_date is not null' + sql += ' and "main_marker".status=\'A\'' + sql += ' order by "main_marker".available_date desc limit 15' + q = Marker.objects.raw(sql) + return q diff --git a/chimere_rss/templates/rss.html b/chimere_rss/templates/rss.html new file mode 100644 index 0000000..0c895ed --- /dev/null +++ b/chimere_rss/templates/rss.html @@ -0,0 +1,73 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block sidebar %} +{% endblock %} + +{% block content %} +
+
+{% trans "Subscribe to RSS feed" %} + +

{{ error_message }}

+ +
+{%if not category_rss_feed %} +
+ + +
+{% endif %} + +{%ifequal category_rss_feed "category" %} +

{% trans "New points of interest by category" %}

+
+ + +
+{% endifequal %} + +{%ifequal category_rss_feed "area" %} +

{% trans "New points of interest by area" %}

+{% if area_id %} +
+ + +
+ +
+ +
+{% endif %} +
+ +
+ {{form.area}} +
+
+

+{% endifequal %} + +
+ +
+
+{% endblock %} diff --git a/chimere_rss/templates/rss_descr.html b/chimere_rss/templates/rss_descr.html new file mode 100644 index 0000000..4f75ac6 --- /dev/null +++ b/chimere_rss/templates/rss_descr.html @@ -0,0 +1,8 @@ +{% load i18n %} +{% load sanitize %} +
+{% if obj.picture %}{{obj.name}}{%endif%} +
{% for property in obj.getProperties %} +

{{ property.value|sanitize:"p b i br hr strong em span:style a:href:target ul li ol h1 h2 h3 h4"|safe }}

+{% endfor %}
+
diff --git a/chimere_rss/templates/rss_title.html b/chimere_rss/templates/rss_title.html new file mode 100644 index 0000000..5b379e7 --- /dev/null +++ b/chimere_rss/templates/rss_title.html @@ -0,0 +1,2 @@ +{% load i18n %} +{{ obj.name }} diff --git a/chimere_rss/urls.py b/chimere_rss/urls.py new file mode 100644 index 0000000..e5e9a24 --- /dev/null +++ b/chimere_rss/urls.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010 Pierre Clarenc , +# Samuel Renard , +# É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. + + +from django.conf.urls.defaults import * + +from chimere.rss.feeds import LatestPOIsByCategory, LatestPOIsBySubCategory, \ + LatestPOIs, LatestPOIsByZone, LatestPOIsByZoneID +from chimere.urls import EXTRA_NO_AREA as EXTRA + +feeds = { + 'category': LatestPOIsByCategory, + 'subcategory': LatestPOIsBySubCategory, + 'global': LatestPOIs, + 'area': LatestPOIsByZone, + 'areaid': LatestPOIsByZoneID +} + +urlpatterns = patterns('', + (EXTRA + r'rss/$', 'chimere.rss.views.rss'), + (EXTRA + r'rss/(?P.*)/$', + 'django.contrib.syndication.views.feed', {'feed_dict': feeds}), +) + diff --git a/chimere_rss/views.py b/chimere_rss/views.py new file mode 100644 index 0000000..1d6381d --- /dev/null +++ b/chimere_rss/views.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010 Pierre Clarenc , +# Samuel Renard , +# É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. + +""" +Views of the project +""" + +from django.shortcuts import render_to_response +from django.http import HttpResponseRedirect +from django.utils.translation import ugettext as _ + +from chimere import settings +from chimere.main.views import get_base_response +from chimere.main.actions import actions +from chimere.main.models import SubCategory,Area +from chimere.main.forms import AreaForm +from chimere.main.widgets import AreaWidget + +def rss(request, area_name=''): + ''' + Redirect to RSS subscription page + ''' + response_dct = get_base_response() + response_dct.update({'actions':actions, 'action_selected':('rss',), + 'category_rss_feed':'',}) + # If the form has been submited + if request.method == "POST": + # User has defined the kind of POI he is interested in : POI in a area + # (GET method is used for the link with RSS icon in the browser) + if 'rss_category' in request.POST: + #User wants to follow all the new POI + if request.POST['rss_category'] == 'global': + feeds_link = '/' + settings.EXTRA_URL + 'rss/global/' + return HttpResponseRedirect(feeds_link) + # User wants to follow all the new POI by category or subcategory + elif request.POST['rss_category'] == 'poi': + response_dct['category_rss_feed'] = 'category' + response_dct['sub_categories'] = SubCategory.getAvailable() + return render_to_response('rss.html', response_dct) + # User wants to follow all the new POI situated in a defined area + elif request.POST['rss_category'] == 'area': + # An unbound form + form = AreaForm() + area_widget = AreaWidget().render('area', None) + response_dct.update({'map_layer':settings.MAP_LAYER, + 'extra_head':form.media, + 'form':form, + 'category_rss_feed':'area', + 'area_id':Area.getAvailable(), + 'area_widget':area_widget + }) + return render_to_response('rss.html', response_dct) + # Error when submitting the form + else: + error = _("Incorrect choice in the list") + response_dct.update({'error_message':error, + 'category_rss_feed':'', + 'sub_categories':SubCategory.getAvailable()}) + return render_to_response('rss.html', response_dct) + + # User has specified the category or subcategory he wants to follow => + # we redirect him towards the related rss feed + if 'subcategory' in request.POST and request.POST['subcategory'] != '': + idCat = request.POST['subcategory'] + if idCat.find("cat_") != -1 : + list_Cat = idCat.split('_') + feeds_link = '/' + settings.EXTRA_URL + 'rss/category/' + feeds_link += list_Cat[1] + return HttpResponseRedirect(feeds_link) + + else: + feeds_link = '/' + settings.EXTRA_URL + 'rss/subcategory/' + \ + idCat + return HttpResponseRedirect(feeds_link) + + # User has specified the ID of the area he wants to follow + if 'id_area' in request.POST and request.POST['id_area'] != '': + feeds_link = '/' + settings.EXTRA_URL + 'rss/areaid/' \ + + request.POST['id_area'] + return HttpResponseRedirect(feeds_link) + + # User has specified the area he wants to follow => we redirect him + # towards the related rss feed (using upper left and lower right + # coordinates) + elif 'upper_left_lat' in request.POST and \ + request.POST['upper_left_lat'] != '' and \ + 'upper_left_lon' in request.POST and \ + request.POST['upper_left_lon'] != '' and \ + 'lower_right_lon' in request.POST and \ + request.POST['lower_right_lon'] != '' and \ + 'lower_right_lat' in request.POST and \ + request.POST['lower_right_lat'] != '' : + feeds_link = '/' + settings.EXTRA_URL + 'rss/area/' + \ +request.POST['upper_left_lat'] + '_' + request.POST['upper_left_lon'] + '_' + \ +request.POST['lower_right_lat'] + '_' + request.POST['lower_right_lon'] + return HttpResponseRedirect(feeds_link) + + + # GET method is used for linking with the RSS icon in the browser when user + # wants to choose a category to follow + elif request.method == "GET" and 'rss_category' in request.GET: + if request.GET['rss_category'] == 'global': + feeds_link = '/' + settings.EXTRA_URL + 'rss/global/' + return HttpResponseRedirect(feeds_link) + if request.GET['rss_category'] == 'poi': + response_dct['category_rss_feed'] = 'category' + response_dct['sub_categories'] = SubCategory.getAvailable(['M','B']) + return render_to_response('rss.html', response_dct) + if request.GET['rss_category'] == 'area': + # An unbound form + form = AreaForm() + response_dct.update({'map_layer':settings.MAP_LAYER, + 'extra_head':form.media, + 'form':form, + 'category_rss_feed':'area', + 'area_id':Area.getAvailable(), + 'area_widget':AreaWidget().render('area', None)}) + return render_to_response('rss.html', response_dct) + + # User access to the RSS tab + else: + return render_to_response('rss.html', response_dct) diff --git a/example_project/__init__.py b/example_project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/example_project/manage.py b/example_project/manage.py new file mode 100755 index 0000000..300eab0 --- /dev/null +++ b/example_project/manage.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +import os, sys +from django.core.management import execute_manager +sys.path.insert(0, os.path.abspath('./..')) + +try: + import settings # Assumed to be in the same directory. +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.exit(1) + +if __name__ == "__main__": + execute_manager(settings) diff --git a/example_project/settings.py.example b/example_project/settings.py.example new file mode 100644 index 0000000..5f993ab --- /dev/null +++ b/example_project/settings.py.example @@ -0,0 +1,159 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os + +# Django settings for chimere project. +PROJECT_NAME = u'Chimère' +ROOT_PATH = os.path.realpath(os.path.dirname(__file__)) + "/" + +EXTRA_URL = 'chimere/' +BASE_URL = SERVER_URL + EXTRA_URL +EMAIL_HOST = 'localhost' + +TINYMCE_URL = 'http://localhost/tinymce/' +JQUERY_URL = "/chimere/static/jquery/jquery-1.4.4.min.js" +GPSBABEL = '/usr/bin/gpsbabel' +GPSBABEL_OPTIONS = 'simplify,crosstrack,error=0.005k' # simplify with an + # error of 5 meters +#GPSBABEL_OPTIONS = 'simplify,count=100' + +## chimere specific ## +# center of the map +DEFAULT_CENTER = (-1.679444, 48.114722) +# projection used by the main map +# most public map providers use spherical mercator : 900913 +EPSG_PROJECTION = 900913 +# projection displayed to the end user by openlayers +# chimere use the same projection to save its data in the database +EPSG_DISPLAY_PROJECTION = 4326 +# to restrict the map to a defined bounding box set it here +# (left, bottom, right, top) +RESTRICTED_EXTENT = None +# dynamic load of categories on the main map +DYNAMIC_CATEGORIES = False +# display of shortcuts for areas +DISPLAY_AREAS = True +# specific css for areas +CSS_AREAS = True +# number of day before an event to display +# if equal to 0: disable event management +# if you change this value from 0 to a value in a production environnement +# don't forget to run the upgrade.py script to create appropriate fields in +# the database +DAYS_BEFORE_EVENT = 30 + +# default id category to check on the map +DEFAULT_CATEGORIES = [1] + +# JS definition of the main map cf. OpenLayers documentation for more details +#MAP_LAYER = '''new OpenLayers.Layer.OSM.CycleMap("Cycle map", { +#displayOutsideMaxExtent: true, wrapDateLine: true})''' # OSM cyclemap +MAP_LAYER = "new OpenLayers.Layer.OSM.Mapnik('Mapnik')" # OSM mapnik map + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'NAME': 'ratatouille', + 'ENGINE': 'django.contrib.gis.db.backends.postgis', + 'HOST': 'localhost', + 'PORT': '5432', + 'USER': 'ratatouille', + 'PASSWORD': 'wiki', + }, +} + +# Local time zone for this installation. Choices can be found here: +# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE +# although not all variations may be possible on all operating systems. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'Europe/Paris' + +# Language code for this installation. All choices can be found here: +# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes +# http://blogs.law.harvard.edu/tech/stories/storyReader$15 +LANGUAGE_CODE = 'fr-fr' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True +USE_L10N = True + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = ROOT_PATH + 'static/' + +# URL that handles the media served from MEDIA_ROOT. +# Example: "http://media.lawrence.com" +MEDIA_URL = '/' + EXTRA_URL + 'static/' + +# share with +SHARE_NETWORKS = ( +("Email", 'mailto:?subject=%(text)s&body=%(url)s', + MEDIA_URL + 'icons/email.png'), +("Facebook", 'http://www.facebook.com/sharer.php?t=%(text)s&u=%(url)s', + MEDIA_URL + 'icons/facebook.png'), +("Twitter", 'http://twitter.com/home?status=%(text)s %(url)s', + MEDIA_URL + 'icons/twitter.png'), +("Identi.ca", 'http://identi.ca/index.php?action=newnotice&status_textarea=%(text)s %(url)s', + MEDIA_URL + 'icons/identica.png'), +) + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/' + EXTRA_URL + 'media/' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = 'achanger_!ToChange!' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.load_template_source', + 'django.template.loaders.app_directories.load_template_source', +# 'django.template.loaders.eggs.load_template_source', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.middleware.doc.XViewMiddleware', +) + +ROOT_URLCONF = 'chimere.urls' + +TEMPLATE_DIRS = [ + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. + ROOT_PATH + 'templates', +] + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.admin', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'south', + 'chimere', + # activate it if you want to use migration scripts + #'chimere.scripts', + # activate it if you want to use RSS feeds + 'chimere_rss' +) + +if 'chimere_rss' in INSTALLED_APPS: + TEMPLATE_DIRS.append(ROOT_PATH + 'chimere_rss/templates') diff --git a/example_project/urls.py b/example_project/urls.py new file mode 100644 index 0000000..8644a5d --- /dev/null +++ b/example_project/urls.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2012 É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. + +from django.conf import settings +from django.conf.urls.defaults import * + +from django.contrib import admin +from django.contrib.staticfiles.urls import staticfiles_urlpatterns + +admin.autodiscover() +urlpatterns = patterns('django.views.static', + (r'^%s(?P.*)' % settings.MEDIA_URL[1:], 'serve', + {'document_root': settings.MEDIA_ROOT}), +) + +urlpatterns += staticfiles_urlpatterns() + +urlpatterns += patterns('', + (r'^admin/doc/', include('django.contrib.admindocs.urls')), + (r'^admin/', include(admin.site.urls)), + url(r'^chimere/', include('chimere.urls', namespace="chimere")), +) + +if 'rss' in settings.INSTALLED_APPS: + urlpatterns += patterns('', + (r'^chimere/rss/', include('rss.urls')),) + -- cgit v1.2.3