class RestrictedAreaEdge(Topology): topo_object = models.OneToOneField(Topology, parent_link=True, db_column='evenement') restricted_area = models.ForeignKey(RestrictedArea, verbose_name=_(u"Restricted area"), db_column='zone') # Override default manager objects = Topology.get_manager_cls(models.GeoManager)() class Meta: db_table = 'f_t_zonage' verbose_name = _(u"Restricted area edge") verbose_name_plural = _(u"Restricted area edges") def __unicode__(self): return _(u"Restricted area edge") + u": %s" % self.restricted_area @classmethod def path_area_edges(cls, path): return cls.objects.select_related('restricted_area')\ .select_related('restricted_area__area_type')\ .filter(aggregations__path=path).distinct('pk') @classmethod def topology_area_edges(cls, topology): return cls.overlapping(topology).select_related('restricted_area')\ .select_related('restricted_area__area_type')
class CityEdge(Topology): topo_object = models.OneToOneField(Topology, parent_link=True, db_column='evenement') city = models.ForeignKey(City, verbose_name=_(u"City"), db_column='commune') # Override default manager objects = Topology.get_manager_cls(models.GeoManager)() class Meta: db_table = 'f_t_commune' verbose_name = _(u"City edge") verbose_name_plural = _(u"City edges") def __unicode__(self): return _("City edge") + u": %s" % self.city @classmethod def path_city_edges(cls, path): return cls.objects.select_related('city').filter( aggregations__path=path).distinct('pk') @classmethod def topology_city_edges(cls, topology): return cls.overlapping(topology).select_related('city')
class DistrictEdge(Topology): topo_object = models.OneToOneField(Topology, parent_link=True, db_column='evenement') district = models.ForeignKey(District, verbose_name=_(u"District"), db_column='secteur') # Override default manager objects = Topology.get_manager_cls(models.GeoManager)() class Meta: db_table = 'f_t_secteur' verbose_name = _(u"District edge") verbose_name_plural = _(u"District edges") def __unicode__(self): return _(u"District edge") + u": %s" % self.district @classmethod def path_district_edges(cls, path): return cls.objects.select_related('district').filter( aggregations__path=path).distinct('pk') @classmethod def topology_district_edges(cls, topology): return cls.overlapping(topology).select_related('district')
class POI(PicturesMixin, MapEntityMixin, Topology): topo_object = models.OneToOneField(Topology, parent_link=True, db_column='evenement') name = models.CharField(verbose_name=_(u"Name"), max_length=128, db_column='nom', help_text=_(u"Official name")) description = models.TextField(verbose_name=_(u"Description"), db_column='description', help_text=_(u"History, details, ...")) type = models.ForeignKey('POIType', related_name='pois', verbose_name=_(u"Type"), db_column='type') class Meta: db_table = 'o_t_poi' verbose_name = _(u"POI") verbose_name_plural = _(u"POI") # Override default manager objects = Topology.get_manager_cls(POIManager)() def __unicode__(self): return u"%s (%s)" % (self.name, self.type) @property def type_display(self): return unicode(self.type) @property def name_display(self): return u'<a data-pk="%s" href="%s" >%s</a>' % ( self.pk, self.get_detail_url(), self.name) @property def name_csv_display(self): return unicode(self.name) @property def serializable_type(self): return { 'label': self.type.label, 'pictogram': self.type.serializable_pictogram } @classmethod def path_pois(cls, path): return cls.objects.filter(aggregations__path=path).distinct('pk') @classmethod def topology_pois(cls, topology): return cls.overlapping(topology)
class LandEdge(MapEntityMixin, Topology): topo_object = models.OneToOneField(Topology, parent_link=True, db_column='evenement') land_type = models.ForeignKey(LandType, verbose_name=_(u"Land type"), db_column='type') owner = models.TextField(verbose_name=_(u"Owner"), db_column='proprietaire', blank=True) agreement = models.BooleanField(verbose_name=_(u"Agreement"), db_column='convention', default=False) eid = models.CharField(verbose_name=_(u"External id"), max_length=1024, blank=True, null=True, db_column='id_externe') # Override default manager objects = Topology.get_manager_cls(models.GeoManager)() class Meta: db_table = 'f_t_foncier' verbose_name = _(u"Land edge") verbose_name_plural = _(u"Land edges") def __unicode__(self): return _(u"Land edge") + u": %s" % self.land_type @property def color_index(self): return self.land_type_id @property def name(self): return self.land_type_csv_display @property def land_type_display(self): return u'<a data-pk="%s" href="%s" >%s</a>' % ( self.pk, self.get_detail_url(), self.land_type) @property def land_type_csv_display(self): return unicode(self.land_type) @classmethod def path_lands(cls, path): return cls.objects.existing().select_related('land_type').filter( aggregations__path=path).distinct('pk') @classmethod def topology_lands(cls, topology): return cls.overlapping(topology).select_related('land_type')
class CompetenceEdge(MapEntityMixin, Topology): topo_object = models.OneToOneField(Topology, parent_link=True, db_column='evenement') organization = models.ForeignKey(Organism, verbose_name=_(u"Organism"), db_column='organisme') eid = models.CharField(verbose_name=_(u"External id"), max_length=1024, blank=True, null=True, db_column='id_externe') # Override default manager objects = Topology.get_manager_cls(models.GeoManager)() class Meta: db_table = 'f_t_competence' verbose_name = _(u"Competence edge") verbose_name_plural = _(u"Competence edges") def __unicode__(self): return _(u"Competence edge") + u": %s" % self.organization @property def color_index(self): return self.organization_id @property def name(self): return self.organization_csv_display @property def organization_display(self): return u'<a data-pk="%s" href="%s" >%s</a>' % ( self.pk, self.get_detail_url(), self.organization) @property def organization_csv_display(self): return unicode(self.organization) @classmethod def path_competences(cls, path): return cls.objects.existing().select_related('organization').filter( aggregations__path=path).distinct('pk') @classmethod def topology_competences(cls, topology): return cls.overlapping(Topology.objects.get( pk=topology.pk)).select_related('organization')
class PhysicalEdge(MapEntityMixin, Topology): topo_object = models.OneToOneField(Topology, parent_link=True, db_column='evenement') physical_type = models.ForeignKey(PhysicalType, verbose_name=_(u"Physical type"), db_column='type') # Override default manager objects = Topology.get_manager_cls(models.GeoManager)() class Meta: db_table = 'f_t_nature' verbose_name = _(u"Physical edge") verbose_name_plural = _(u"Physical edges") def __unicode__(self): return _(u"Physical edge") + u": %s" % self.physical_type @property def color_index(self): return self.physical_type_id @property def name(self): return self.physical_type_csv_display @property def physical_type_display(self): return self.display @property def physical_type_csv_display(self): return unicode(self.physical_type) @property def display(self): return u'<a data-pk="%s" href="%s" >%s</a>' % ( self.pk, self.get_detail_url(), self.physical_type) @classmethod def path_physicals(cls, path): return cls.objects.select_related('physical_type').filter( aggregations__path=path).distinct('pk') @classmethod def topology_physicals(cls, topology): return cls.overlapping(topology).select_related('physical_type')
class SignageManagementEdge(MapEntityMixin, Topology): topo_object = models.OneToOneField(Topology, parent_link=True, db_column='evenement') organization = models.ForeignKey(Organism, verbose_name=_(u"Organism"), db_column='organisme') # Override default manager objects = Topology.get_manager_cls(models.GeoManager)() class Meta: db_table = 'f_t_gestion_signaletique' verbose_name = _(u"Signage management edge") verbose_name_plural = _(u"Signage management edges") def __unicode__(self): return _(u"Signage management edge") + u": %s" % self.organization @property def color_index(self): return self.organization_id @property def name(self): return self.organization_csv_display @property def organization_display(self): return self.display @property def organization_csv_display(self): return unicode(self.organization) @property def display(self): return u'<a data-pk="%s" href="%s" >%s</a>' % ( self.pk, self.get_detail_url(), self.organization) @classmethod def path_signages(cls, path): return cls.objects.select_related('organization').filter( aggregations__path=path).distinct('pk') @classmethod def topology_signages(cls, topology): return cls.overlapping(topology).select_related('organization')
class Trek(PicturesMixin, MapEntityMixin, Topology): topo_object = models.OneToOneField(Topology, parent_link=True, db_column='evenement') name = models.CharField(verbose_name=_(u"Name"), max_length=128, help_text=_(u"Public name (Change carefully)"), db_column='nom') departure = models.CharField(verbose_name=_(u"Departure"), max_length=128, blank=True, help_text=_(u"Departure description"), db_column='depart') arrival = models.CharField(verbose_name=_(u"Arrival"), max_length=128, blank=True, help_text=_(u"Arrival description"), db_column='arrivee') published = models.BooleanField(verbose_name=_(u"Published"), help_text=_(u"Online"), db_column='public') description_teaser = models.TextField( verbose_name=_(u"Description teaser"), blank=True, help_text=_(u"A brief summary (map pop-ups)"), db_column='chapeau') description = models.TextField(verbose_name=_(u"Description"), blank=True, db_column='description', help_text=_(u"Complete description")) ambiance = models.TextField(verbose_name=_(u"Ambiance"), blank=True, db_column='ambiance', help_text=_(u"Main attraction and interest")) access = models.TextField(verbose_name=_(u"Access"), blank=True, db_column='acces', help_text=_(u"Best way to go")) disabled_infrastructure = models.TextField( verbose_name=_(u"Disabled infrastructure"), db_column='handicap', blank=True, help_text=_(u"Any specific infrastructure")) duration = models.FloatField(verbose_name=_(u"Duration"), default=0, blank=True, null=True, db_column='duree', help_text=_(u"In hours")) is_park_centered = models.BooleanField( verbose_name=_(u"Is in the midst of the park"), db_column='coeur', help_text=_(u"Crosses center of park")) advised_parking = models.CharField(verbose_name=_(u"Advised parking"), max_length=128, blank=True, db_column='parking', help_text=_(u"Where to park")) parking_location = models.PointField(verbose_name=_(u"Parking location"), db_column='geom_parking', srid=settings.SRID, spatial_index=False, blank=True, null=True) public_transport = models.TextField( verbose_name=_(u"Public transport"), blank=True, db_column='transport', help_text=_(u"Train, bus (see web links)")) advice = models.TextField(verbose_name=_(u"Advice"), blank=True, db_column='recommandation', help_text=_(u"Risks, danger, best period, ...")) themes = models.ManyToManyField('Theme', related_name="treks", db_table="o_r_itineraire_theme", blank=True, null=True, verbose_name=_(u"Themes")) networks = models.ManyToManyField('TrekNetwork', related_name="treks", db_table="o_r_itineraire_reseau", blank=True, null=True, verbose_name=_(u"Networks")) usages = models.ManyToManyField('Usage', related_name="treks", db_table="o_r_itineraire_usage", blank=True, null=True, verbose_name=_(u"Usages")) route = models.ForeignKey('Route', related_name='treks', blank=True, null=True, verbose_name=_(u"Route"), db_column='parcours') difficulty = models.ForeignKey('DifficultyLevel', related_name='treks', blank=True, null=True, verbose_name=_(u"Difficulty"), db_column='difficulte') web_links = models.ManyToManyField('WebLink', related_name="treks", db_table="o_r_itineraire_web", blank=True, null=True, verbose_name=_(u"Web links")) related_treks = models.ManyToManyField( 'self', through='TrekRelationship', verbose_name=_(u"Related treks"), symmetrical=False, related_name='related_treks+') # Hide reverse attribute information_desk = models.ForeignKey('InformationDesk', related_name='treks', blank=True, null=True, verbose_name=_(u"Information Desk"), db_column='renseignement') objects = Topology.get_manager_cls(models.GeoManager)() class Meta: db_table = 'o_t_itineraire' verbose_name = _(u"Trek") verbose_name_plural = _(u"Treks") @property def slug(self): return slugify(self.name) @models.permalink def get_document_public_url(self): return ('trekking:trek_document_public', [str(self.pk)]) @models.permalink def get_document_public_poi_url(self): return ('trekking:trek_document_public_poi', [str(self.pk)]) @property def related(self): return self.related_treks.exclude(deleted=True).exclude( pk=self.pk).distinct() @property def relationships(self): # Does not matter if a or b return TrekRelationship.objects.filter(trek_a=self) @property def poi_types(self): pks = set(self.pois.values_list('type', flat=True)) return POIType.objects.filter(pk__in=pks) def prepare_map_image(self, rooturl): """ We override the default behaviour of map image preparation : if the trek has a attached picture file with *title* ``mapimage``, we use it as a screenshot. TODO: remove this when screenshots are bullet-proof ? """ attached = None for picture in [a for a in self.attachments.all() if a.is_image]: if picture.title == 'mapimage': attached = picture.attachment_file break if attached is None: super(Trek, self).prepare_map_image(rooturl) else: # Copy it along other screenshots src = os.path.join(settings.MEDIA_ROOT, attached.name) dst = self.get_map_image_path() shutil.copyfile(src, dst) def get_attachment_print(self): """ Look in attachment if there is document to be used as print version """ overriden = self.attachments.filter(title="docprint").get() # Must have OpenOffice document mimetype if overriden.mimetype != [ 'application', 'vnd.oasis.opendocument.text' ]: raise overriden.DoesNotExist() return os.path.join(settings.MEDIA_ROOT, overriden.attachment_file.name) @property def serializable_relationships(self): return [{ 'has_common_departure': rel.has_common_departure, 'has_common_edge': rel.has_common_edge, 'is_circuit_step': rel.is_circuit_step, 'trek': { 'pk': rel.trek_b.pk, 'slug': rel.trek_b.slug, 'name': rel.trek_b.name, 'url': reverse('trekking:trek_json_detail', args=(rel.trek_b.pk, )), }, 'published': rel.trek_b.published } for rel in self.relationships] @property def serializable_cities(self): return [{'code': city.code, 'name': city.name} for city in self.cities] @property def serializable_networks(self): return [{ 'id': network.id, 'name': network.network } for network in self.networks.all()] @property def serializable_difficulty(self): if not self.difficulty: return None return {'id': self.difficulty.pk, 'label': self.difficulty.difficulty} @property def serializable_information_desk(self): if not self.information_desk: return None return { 'id': self.information_desk.pk, 'name': self.information_desk.name, 'description': self.information_desk.description } @property def serializable_themes(self): return [{ 'id': t.pk, 'pictogram': os.path.join(settings.MEDIA_URL, t.pictogram.name), 'label': t.label } for t in self.themes.all()] @property def serializable_usages(self): return [{ 'id': u.pk, 'pictogram': os.path.join(settings.MEDIA_URL, u.pictogram.name), 'label': u.usage } for u in self.usages.all()] @property def serializable_districts(self): return [{'id': d.pk, 'name': d.name} for d in self.districts] @property def serializable_route(self): if not self.route: return None return {'id': self.route.pk, 'label': self.route.route} @property def serializable_web_links(self): return [{ 'id': w.pk, 'name': w.name, 'category': w.serializable_category, 'url': w.url } for w in self.web_links.all()] @property def serializable_parking_location(self): if not self.parking_location: return None return self.parking_location.transform(settings.API_SRID, clone=True).coords @property def name_display(self): s = u'<a data-pk="%s" href="%s" >%s</a>' % ( self.pk, self.get_detail_url(), self.name) if self.published: s = u'<span class="badge badge-success" title="%s">☆</span> ' % _( "Published") + s return s @property def name_csv_display(self): return unicode(self.name) @property def length_kilometer(self): return "%.1f" % (self.length / 1000.0) @property def networks_display(self): return ', '.join([unicode(n) for n in self.networks.all()]) @property def districts_display(self): return ', '.join([unicode(d) for d in self.districts]) @property def themes_display(self): return ', '.join([unicode(n) for n in self.themes.all()]) @property def usages_display(self): return ', '.join([unicode(n) for n in self.usages.all()]) def kml(self): """ Exports trek into KML format, add geometry as linestring and POI as place marks """ kml = simplekml.Kml() # Main itinerary geom = self.geom.transform(4326, clone=True) # KML uses WGS84 line = kml.newlinestring(name=self.name, description=plain_text(self.description), coords=geom.coords) line.style.linestyle.color = simplekml.Color.red # Red line.style.linestyle.width = 4 # pixels # Place marks for poi in self.pois: place = poi.geom_as_point() place.transform(settings.API_SRID) kml.newpoint(name=poi.name, description=plain_text(poi.description), coords=[place.coords]) return kml._genkml() def is_complete(self): """It should also have a description, etc. """ mandatory = ['departure', 'arrival', 'description_teaser'] for f in mandatory: if not getattr(self, f): return False return True def has_geom_valid(self): """A trek should be a LineString, even if it's a loop. """ return (self.geom is not None and self.geom.geom_type.lower() == 'linestring') def is_publishable(self): return self.is_complete() and self.has_geom_valid() @property def duration_pretty(self): return trekking_tags.duration(self.duration) def __unicode__(self): return u"%s (%s - %s)" % (self.name, self.departure, self.arrival) @classmethod def path_treks(cls, path): return cls.objects.existing().filter( aggregations__path=path).distinct('pk') @classmethod def topology_treks(cls, topology): return cls.overlapping(topology)
class Service(StructureRelated, MapEntityMixin, Topology): topo_object = models.OneToOneField(Topology, parent_link=True, db_column='evenement') type = models.ForeignKey('ServiceType', related_name='services', verbose_name=_(u"Type"), db_column='type') eid = models.CharField(verbose_name=_(u"External id"), max_length=128, blank=True, null=True, db_column='id_externe') class Meta: db_table = 'o_t_service' verbose_name = _(u"Service") verbose_name_plural = _(u"Services") # Override default manager objects = Topology.get_manager_cls(ServiceManager)() def __unicode__(self): return unicode(self.type) @property def name(self): return self.type.name @property def name_display(self): s = u'<a data-pk="%s" href="%s" title="%s">%s</a>' % (self.pk, self.get_detail_url(), self.name, self.name) if self.type.published: s = u'<span class="badge badge-success" title="%s">☆</span> ' % _("Published") + s elif self.type.review: s = u'<span class="badge badge-warning" title="%s">☆</span> ' % _("Waiting for publication") + s return s @classproperty def name_verbose_name(cls): return _("Type") @property def type_display(self): return unicode(self.type) @property def serializable_type(self): return {'label': self.type.label, 'pictogram': self.type.get_pictogram_url()} @classmethod def path_services(cls, path): return cls.objects.existing().filter(aggregations__path=path).distinct('pk') @classmethod def topology_services(cls, topology): if settings.TREKKING_TOPOLOGY_ENABLED: qs = cls.overlapping(topology) else: area = topology.geom.buffer(settings.TREK_POI_INTERSECTION_MARGIN) qs = cls.objects.existing().filter(geom__intersects=area) if isinstance(topology, Trek): qs = qs.filter(type__practices=topology.practice) return qs @classmethod def published_topology_services(cls, topology): return cls.topology_services(topology).filter(type__published=True) def distance(self, to_cls): return settings.TOURISM_INTERSECTION_MARGIN
class POI(PicturesMixin, MapEntityMixin, Topology): topo_object = models.OneToOneField(Topology, parent_link=True, db_column='evenement') name = models.CharField(verbose_name=_(u"Name"), max_length=128, db_column='nom', help_text=_(u"Official name")) description = models.TextField(verbose_name=_(u"Description"), db_column='description', help_text=_(u"History, details, ...")) type = models.ForeignKey('POIType', related_name='pois', verbose_name=_(u"Type"), db_column='type') class Meta: db_table = 'o_t_poi' verbose_name = _(u"POI") verbose_name_plural = _(u"POI") # Override default manager objects = Topology.get_manager_cls(POIManager)() def __unicode__(self): return u"%s (%s)" % (self.name, self.type) def save(self, *args, **kwargs): super(POI, self).save(*args, **kwargs) # Invalidate treks map for trek in self.treks.all(): try: os.remove(trek.get_map_image_path()) except OSError: pass @property def type_display(self): return unicode(self.type) @property def name_display(self): return u'<a data-pk="%s" href="%s" title="%s">%s</a>' % ( self.pk, self.get_detail_url(), self.name, self.name) @property def name_csv_display(self): return unicode(self.name) @property def serializable_type(self): return { 'label': self.type.label, 'pictogram': self.type.serializable_pictogram } @classmethod def path_pois(cls, path): return cls.objects.filter(aggregations__path=path).distinct('pk') @classmethod def topology_pois(cls, topology): if settings.TREKKING_TOPOLOGY_ENABLED: qs = cls.overlapping(topology) else: area = topology.geom.buffer(settings.TREK_POI_INTERSECTION_MARGIN) qs = cls.objects.filter(geom__intersects=area) return qs
class Trek(StructureRelated, PicturesMixin, PublishableMixin, MapEntityMixin, Topology): topo_object = models.OneToOneField(Topology, parent_link=True, db_column='evenement') departure = models.CharField(verbose_name=_(u"Departure"), max_length=128, blank=True, help_text=_(u"Departure description"), db_column='depart') arrival = models.CharField(verbose_name=_(u"Arrival"), max_length=128, blank=True, help_text=_(u"Arrival description"), db_column='arrivee') description_teaser = models.TextField(verbose_name=_(u"Description teaser"), blank=True, help_text=_(u"A brief summary (map pop-ups)"), db_column='chapeau') description = models.TextField(verbose_name=_(u"Description"), blank=True, db_column='description', help_text=_(u"Complete description")) ambiance = models.TextField(verbose_name=_(u"Ambiance"), blank=True, db_column='ambiance', help_text=_(u"Main attraction and interest")) access = models.TextField(verbose_name=_(u"Access"), blank=True, db_column='acces', help_text=_(u"Best way to go")) disabled_infrastructure = models.TextField(verbose_name=_(u"Disabled infrastructure"), db_column='handicap', blank=True, help_text=_(u"Any specific infrastructure")) duration = models.FloatField(verbose_name=_(u"Duration"), default=0, blank=True, db_column='duree', help_text=_(u"In hours (1.5 = 1 h 30, 24 = 1 day, 48 = 2 days)"), validators=[MinValueValidator(0)]) is_park_centered = models.BooleanField(verbose_name=_(u"Is in the midst of the park"), db_column='coeur', help_text=_(u"Crosses center of park")) advised_parking = models.CharField(verbose_name=_(u"Advised parking"), max_length=128, blank=True, db_column='parking', help_text=_(u"Where to park")) parking_location = models.PointField(verbose_name=_(u"Parking location"), db_column='geom_parking', srid=settings.SRID, spatial_index=False, blank=True, null=True) public_transport = models.TextField(verbose_name=_(u"Public transport"), blank=True, db_column='transport', help_text=_(u"Train, bus (see web links)")) advice = models.TextField(verbose_name=_(u"Advice"), blank=True, db_column='recommandation', help_text=_(u"Risks, danger, best period, ...")) themes = models.ManyToManyField(Theme, related_name="treks", db_table="o_r_itineraire_theme", blank=True, null=True, verbose_name=_(u"Themes"), help_text=_(u"Main theme(s)")) networks = models.ManyToManyField('TrekNetwork', related_name="treks", db_table="o_r_itineraire_reseau", blank=True, null=True, verbose_name=_(u"Networks"), help_text=_(u"Hiking networks")) practice = models.ForeignKey('Practice', related_name="treks", blank=True, null=True, verbose_name=_(u"Practice"), db_column='pratique') accessibilities = models.ManyToManyField('Accessibility', related_name="treks", db_table="o_r_itineraire_accessibilite", blank=True, null=True, verbose_name=_(u"Accessibility")) route = models.ForeignKey('Route', related_name='treks', blank=True, null=True, verbose_name=_(u"Route"), db_column='parcours') difficulty = models.ForeignKey('DifficultyLevel', related_name='treks', blank=True, null=True, verbose_name=_(u"Difficulty"), db_column='difficulte') web_links = models.ManyToManyField('WebLink', related_name="treks", db_table="o_r_itineraire_web", blank=True, null=True, verbose_name=_(u"Web links"), help_text=_(u"External resources")) related_treks = models.ManyToManyField('self', through='TrekRelationship', verbose_name=_(u"Related treks"), symmetrical=False, help_text=_(u"Connections between treks"), related_name='related_treks+') # Hide reverse attribute information_desks = models.ManyToManyField(tourism_models.InformationDesk, related_name='treks', db_table="o_r_itineraire_renseignement", blank=True, null=True, verbose_name=_(u"Information desks"), help_text=_(u"Where to obtain information")) points_reference = models.MultiPointField(verbose_name=_(u"Points of reference"), db_column='geom_points_reference', srid=settings.SRID, spatial_index=False, blank=True, null=True) source = models.ManyToManyField('common.RecordSource', blank=True, related_name='treks', verbose_name=_("Source"), db_table='o_r_itineraire_source') portal = models.ManyToManyField('common.TargetPortal', blank=True, related_name='treks', verbose_name=_("Portal"), db_table='o_r_itineraire_portal') eid = models.CharField(verbose_name=_(u"External id"), max_length=128, blank=True, null=True, db_column='id_externe') eid2 = models.CharField(verbose_name=_(u"Second external id"), max_length=128, blank=True, null=True, db_column='id_externe2') objects = Topology.get_manager_cls(models.GeoManager)() category_id_prefix = 'T' capture_map_image_waitfor = '.poi_enum_loaded.services_loaded.info_desks_loaded.ref_points_loaded' class Meta: db_table = 'o_t_itineraire' verbose_name = _(u"Trek") verbose_name_plural = _(u"Treks") ordering = ['name'] def __unicode__(self): return self.name @models.permalink def get_map_image_url(self): return ('trekking:trek_map_image', [], {'pk': str(self.pk), 'lang': get_language()}) def get_map_image_path(self): basefolder = os.path.join(settings.MEDIA_ROOT, 'maps') if not os.path.exists(basefolder): os.makedirs(basefolder) return os.path.join(basefolder, '%s-%s-%s.png' % (self._meta.module_name, self.pk, get_language())) @models.permalink def get_document_public_url(self): """ Override ``geotrek.common.mixins.PublishableMixin`` """ return ('trekking:trek_document_public', [], {'lang': get_language(), 'pk': self.pk, 'slug': self.slug}) @property def related(self): return self.related_treks.exclude(deleted=True).exclude(pk=self.pk).distinct() @classproperty def related_verbose_name(cls): return _("Related treks") @property def relationships(self): # Does not matter if a or b return TrekRelationship.objects.filter(trek_a=self) @property def published_relationships(self): return self.relationships.filter(trek_b__published=True) @property def poi_types(self): if settings.TREKKING_TOPOLOGY_ENABLED: # Can't use values_list and must add 'ordering' because of bug: # https://code.djangoproject.com/ticket/14930 values = self.pois.values('ordering', 'type') else: values = self.pois.values('type') pks = [value['type'] for value in values] return POIType.objects.filter(pk__in=set(pks)) @property def length_kilometer(self): return "%.1f" % (self.length / 1000.0) @property def networks_display(self): return ', '.join([unicode(n) for n in self.networks.all()]) @property def districts_display(self): return ', '.join([unicode(d) for d in self.districts]) @property def themes_display(self): return ', '.join([unicode(n) for n in self.themes.all()]) @property def city_departure(self): cities = self.cities return unicode(cities[0]) if len(cities) > 0 else '' def kml(self): """ Exports trek into KML format, add geometry as linestring and POI as place marks """ kml = simplekml.Kml() # Main itinerary geom3d = self.geom_3d.transform(4326, clone=True) # KML uses WGS84 line = kml.newlinestring(name=self.name, description=plain_text(self.description), coords=geom3d.coords) line.style.linestyle.color = simplekml.Color.red # Red line.style.linestyle.width = 4 # pixels # Place marks for poi in self.pois: place = poi.geom_3d.transform(settings.API_SRID, clone=True) kml.newpoint(name=poi.name, description=plain_text(poi.description), coords=[place.coords]) return kml.kml() def has_geom_valid(self): """A trek should be a LineString, even if it's a loop. """ return super(Trek, self).has_geom_valid() and self.geom.geom_type.lower() == 'linestring' @property def duration_pretty(self): return trekking_tags.duration(self.duration) @classproperty def duration_pretty_verbose_name(cls): return _("Formated duration") @classmethod def path_treks(cls, path): treks = cls.objects.existing().filter(aggregations__path=path) # The following part prevents conflict with default trek ordering # ProgrammingError: SELECT DISTINCT ON expressions must match initial ORDER BY expressions return treks.order_by('topo_object').distinct('topo_object') @classmethod def topology_treks(cls, topology): if settings.TREKKING_TOPOLOGY_ENABLED: qs = cls.overlapping(topology) else: area = topology.geom.buffer(settings.TREK_POI_INTERSECTION_MARGIN) qs = cls.objects.existing().filter(geom__intersects=area) return qs @classmethod def published_topology_treks(cls, topology): return cls.topology_treks(topology).filter(published=True) # Rando v1 compat @property def usages(self): return [self.practice] if self.practice else [] @classmethod def get_create_label(cls): return _(u"Add a new trek") @property def parents(self): return Trek.objects.filter(trek_children__child=self, deleted=False) @property def parents_id(self): parents = self.trek_parents.values_list('parent__id', flat=True) return list(parents) @property def children(self): return Trek.objects.filter(trek_parents__parent=self, deleted=False).order_by('trek_parents__order') @property def children_id(self): """ Get children IDs """ children = self.trek_children.order_by('order')\ .values_list('child__id', flat=True) return list(children) def previous_id_for(self, parent): children_id = parent.children_id index = children_id.index(self.id) if index == 0: return None return children_id[index - 1] def next_id_for(self, parent): children_id = parent.children_id index = children_id.index(self.id) if index == len(children_id) - 1: return None return children_id[index + 1] @property def previous_id(self): """ Dict of parent -> previous child """ return {parent.id: self.previous_id_for(parent) for parent in self.parents.filter(published=True, deleted=False)} @property def next_id(self): """ Dict of parent -> next child """ return {parent.id: self.next_id_for(parent) for parent in self.parents.filter(published=True, deleted=False)} def clean(self): """ Custom model validation """ if self.pk in self.trek_children.values_list('child__id', flat=True): raise ValidationError(_(u"Cannot use itself as child trek.")) @property def prefixed_category_id(self): if settings.SPLIT_TREKS_CATEGORIES_BY_PRACTICE and self.practice: return '{prefix}{id}'.format(prefix=self.category_id_prefix, id=self.practice.id) else: return self.category_id_prefix def distance(self, to_cls): if self.practice and self.practice.distance is not None: return self.practice.distance else: return settings.TOURISM_INTERSECTION_MARGIN def is_public(self): for parent in self.parents: if parent.any_published: return True return self.any_published @property def picture_print(self): picture = super(Trek, self).picture_print if picture: return picture for poi in self.published_pois: picture = poi.picture_print if picture: return picture def save(self, *args, **kwargs): if self.pk is not None and kwargs.get('update_fields', None) is None: field_names = set() for field in self._meta.concrete_fields: if not field.primary_key and not hasattr(field, 'through'): field_names.add(field.attname) old_trek = Trek.objects.get(pk=self.pk) if self.geom is not None and old_trek.geom.equals_exact(self.geom, tolerance=0.00001): field_names.remove('geom') if self.geom_3d is not None and old_trek.geom_3d.equals_exact(self.geom_3d, tolerance=0.00001): field_names.remove('geom_3d') return super(Trek, self).save(update_fields=field_names, *args, **kwargs) super(Trek, self).save(*args, **kwargs)
class Dive(AddPropertyMixin, PublishableMixin, MapEntityMixin, StructureRelated, TimeStampedModelMixin, PicturesMixin, NoDeleteMixin): description_teaser = models.TextField(verbose_name=_("Description teaser"), blank=True, help_text=_("A brief summary"), db_column='chapeau') description = models.TextField(verbose_name=_("Description"), blank=True, db_column='description', help_text=_("Complete description")) owner = models.CharField(verbose_name=_("Owner"), max_length=256, blank=True, db_column='proprietaire') practice = models.ForeignKey(Practice, related_name="dives", blank=True, null=True, verbose_name=_("Practice"), db_column='pratique') departure = models.CharField(verbose_name=_("Departure area"), max_length=128, blank=True, db_column='depart') disabled_sport = models.TextField(verbose_name=_("Disabled sport accessibility"), db_column='handicap', blank=True) facilities = models.TextField(verbose_name=_("Facilities"), db_column='equipements', blank=True) difficulty = models.ForeignKey(Difficulty, related_name='dives', blank=True, null=True, verbose_name=_("Difficulty level"), db_column='difficulte') levels = models.ManyToManyField(Level, related_name='dives', blank=True, verbose_name=_("Technical levels"), db_table='g_r_plongee_niveau') depth = models.PositiveIntegerField(verbose_name=_("Maximum depth"), db_column='profondeur', blank=True, null=True, help_text=_("meters")) advice = models.TextField(verbose_name=_("Advice"), blank=True, db_column='recommandation', help_text=_("Risks, danger, best period, ...")) themes = models.ManyToManyField(Theme, related_name="dives", db_table="g_r_plongee_theme", blank=True, verbose_name=_("Themes"), help_text=_("Main theme(s)")) geom = models.GeometryField(verbose_name=_("Location"), srid=settings.SRID) source = models.ManyToManyField('common.RecordSource', blank=True, related_name='dives', verbose_name=_("Source"), db_table='g_r_plongee_source') portal = models.ManyToManyField('common.TargetPortal', blank=True, related_name='dives', verbose_name=_("Portal"), db_table='g_r_plongee_portal') eid = models.CharField(verbose_name=_("External id"), max_length=1024, blank=True, null=True, db_column='id_externe') objects = Topology.get_manager_cls(models.GeoManager)() category_id_prefix = 'D' class Meta: db_table = 'g_t_plongee' verbose_name = _("Dive") verbose_name_plural = _("Dives") def __str__(self): return self.name @property def rando_url(self): if settings.SPLIT_DIVES_CATEGORIES_BY_PRACTICE and self.practice: category_slug = self.practice.slug else: category_slug = _('dive') return '{}/{}/'.format(category_slug, self.slug) @property def display_geom(self): return format_coordinates(self.geom) def distance(self, to_cls): return settings.DIVING_INTERSECTION_MARGIN @property def prefixed_category_id(self): if settings.SPLIT_DIVES_CATEGORIES_BY_PRACTICE and self.practice: return '{prefix}{id}'.format(prefix=self.category_id_prefix, id=self.practice.id) else: return self.category_id_prefix def get_map_image_url(self): return reverse('diving:dive_map_image', args=[str(self.pk), get_language()]) @classmethod def get_create_label(cls): return _("Add a new dive")
class POI(StructureRelated, PicturesMixin, PublishableMixin, MapEntityMixin, Topology): topo_object = models.OneToOneField(Topology, parent_link=True, db_column='evenement') description = models.TextField(verbose_name=_("Description"), db_column='description', blank=True, help_text=_("History, details, ...")) type = models.ForeignKey('POIType', related_name='pois', verbose_name=_("Type"), db_column='type') eid = models.CharField(verbose_name=_("External id"), max_length=1024, blank=True, null=True, db_column='id_externe') class Meta: db_table = 'o_t_poi' verbose_name = _("POI") verbose_name_plural = _("POI") # Override default manager objects = Topology.get_manager_cls(POIManager)() # Do no check structure when selecting POIs to exclude check_structure_in_forms = False def __str__(self): return "%s (%s)" % (self.name, self.type) def save(self, *args, **kwargs): super(POI, self).save(*args, **kwargs) # Invalidate treks map for trek in self.treks.all(): try: os.remove(trek.get_map_image_path()) except OSError: pass @property def type_display(self): return str(self.type) @property def serializable_type(self): return { 'label': self.type.label, 'pictogram': self.type.get_pictogram_url() } @classmethod def path_pois(cls, path): return cls.objects.existing().filter( aggregations__path=path).distinct('pk') @classmethod def topology_pois(cls, topology): return cls.exclude_pois(cls.topology_all_pois(topology), topology) @classmethod def topology_all_pois(cls, topology): if settings.TREKKING_TOPOLOGY_ENABLED: qs = cls.overlapping(topology) else: object_geom = topology.geom.transform( settings.SRID, clone=True).buffer(settings.TREK_POI_INTERSECTION_MARGIN) qs = cls.objects.existing().filter(geom__intersects=object_geom) if topology.geom.geom_type == 'LineString': qs = qs.annotate(locate=LineLocatePoint( Transform( Value(topology.geom.ewkt, output_field=models.GeometryField()), settings.SRID), Transform(F('geom'), settings.SRID))) qs = qs.order_by('locate') return qs @classmethod def published_topology_pois(cls, topology): return cls.topology_pois(topology).filter(published=True) def distance(self, to_cls): return settings.TOURISM_INTERSECTION_MARGIN @classmethod def exclude_pois(cls, qs, topology): try: return qs.exclude(pk__in=topology.trek.pois_excluded.values_list( 'pk', flat=True)) except Trek.DoesNotExist: return qs @property def extent(self): return self.geom.transform(settings.API_SRID, clone=True).extent if self.geom else None
class Project(MapEntityMixin, TimeStampedModel, StructureRelated, NoDeleteMixin): name = models.CharField(verbose_name=_(u"Name"), max_length=128, db_column='nom') begin_year = models.IntegerField(verbose_name=_(u"Begin year"), db_column='annee_debut') end_year = models.IntegerField(verbose_name=_(u"End year"), db_column='annee_fin') constraint = models.TextField(verbose_name=_(u"Constraint"), blank=True, db_column='contraintes', help_text=_(u"Specific conditions, ...")) cost = models.FloatField(verbose_name=_(u"Cost"), default=0, db_column='cout', help_text=_(u"€")) comments = models.TextField(verbose_name=_(u"Comments"), blank=True, db_column='commentaires', help_text=_(u"Remarks and notes")) type = models.ForeignKey('ProjectType', null=True, blank=True, verbose_name=_(u"Type"), db_column='type') domain = models.ForeignKey('ProjectDomain', null=True, blank=True, verbose_name=_(u"Domain"), db_column='domaine') contractors = models.ManyToManyField('Contractor', related_name="projects", db_table="m_r_chantier_prestataire", verbose_name=_(u"Contractors")) project_owner = models.ForeignKey(Organism, related_name='own', verbose_name=_(u"Project owner"), db_column='maitre_oeuvre') project_manager = models.ForeignKey(Organism, related_name='manage', verbose_name=_(u"Project manager"), db_column='maitre_ouvrage') founders = models.ManyToManyField(Organism, through='Funding', verbose_name=_(u"Founders")) objects = Topology.get_manager_cls()() class Meta: db_table = 'm_t_chantier' verbose_name = _(u"Project") verbose_name_plural = _(u"Projects") ordering = ['-begin_year', 'name'] def __init__(self, *args, **kwargs): super(Project, self).__init__(*args, **kwargs) self._geom = None @property def paths(self): s = [] for i in self.interventions.existing(): s += i.paths return Path.objects.filter(pk__in=[p.pk for p in set(s)]) @property def trails(self): s = [] for i in self.interventions.existing(): for p in i.paths.all(): if p.trail: s.append(p.trail) return Trail.objects.filter(pk__in=[t.pk for t in set(s)]) @property def signages(self): s = [] for i in self.interventions.existing(): s += i.signages return list(set(s)) @property def infrastructures(self): s = [] for i in self.interventions.existing(): s += i.infrastructures return list(set(s)) @classproperty def geomfield(cls): from django.contrib.gis.geos import LineString # Fake field, TODO: still better than overkill code in views, but can do neater. c = GeometryCollection([LineString((0, 0), (1, 1))], srid=settings.SRID) c.name = 'geom' return c @property def geom(self): """ Merge all interventions geometry into a collection """ if self._geom is None: interventions = Intervention.objects.existing().filter( project=self) geoms = [i.geom for i in interventions if i.geom is not None] if geoms: self._geom = GeometryCollection(*geoms, srid=settings.SRID) return self._geom @geom.setter def geom(self, value): self._geom = value @property def name_display(self): return u'<a data-pk="%s" href="%s" >%s</a>' % ( self.pk, self.get_detail_url(), self.name) @property def name_csv_display(self): return unicode(self.name) @property def period(self): return "%s - %s" % (self.begin_year, self.end_year) @property def period_display(self): return self.period @classproperty def period_verbose_name(cls): return _("Period") def __unicode__(self): deleted_text = u"[" + _(u"Deleted") + u"]" if self.deleted else "" return u"%s (%s-%s) %s" % (self.name, self.begin_year, self.end_year, deleted_text) @classmethod def path_projects(cls, path): return cls.objects.filter( interventions__in=path.interventions).distinct() @classmethod def trail_projects(cls, trail): return cls.objects.filter( interventions__in=trail.interventions).distinct() @classmethod def topology_projects(cls, topology): return cls.objects.filter( interventions__in=topology.interventions).distinct() def edges_by_attr(self, interventionattr): pks = [] for i in self.interventions.all(): pks += getattr(i, interventionattr).values_list('pk', flat=True) return Topology.objects.filter(pk__in=pks)
class Intervention(MapEntityMixin, AltimetryMixin, TimeStampedModel, StructureRelated, NoDeleteMixin): in_maintenance = models.BooleanField( verbose_name=_(u"Recurrent intervention"), db_column='maintenance', help_text=_(u"Recurrent")) name = models.CharField(verbose_name=_(u"Name"), max_length=128, db_column='nom', help_text=_(u"Brief summary")) date = models.DateField(default=datetime.now, verbose_name=_(u"Date"), db_column='date', help_text=_(u"When ?")) comments = models.TextField(blank=True, verbose_name=_(u"Comments"), db_column='commentaire', help_text=_(u"Remarks and notes")) ## Technical information ## width = models.FloatField(default=0.0, verbose_name=_(u"Width"), db_column='largeur') height = models.FloatField(default=0.0, verbose_name=_(u"Height"), db_column='hauteur') area = models.FloatField(editable=False, default=0, verbose_name=_(u"Area"), db_column='surface') ## Costs ## material_cost = models.FloatField(default=0.0, verbose_name=_(u"Material cost"), db_column='cout_materiel') heliport_cost = models.FloatField(default=0.0, verbose_name=_(u"Heliport cost"), db_column='cout_heliport') subcontract_cost = models.FloatField(default=0.0, verbose_name=_(u"Subcontract cost"), db_column='cout_soustraitant') """ Topology can be of type Infrastructure or of own type Intervention """ topology = models.ForeignKey( Topology, null=True, # TODO: why null ? related_name="interventions_set", verbose_name=_(u"Interventions")) # AltimetyMixin for denormalized fields from related topology, updated via trigger. stake = models.ForeignKey('core.Stake', null=True, related_name='interventions', verbose_name=_("Stake"), db_column='enjeu') status = models.ForeignKey('InterventionStatus', verbose_name=_("Status"), db_column='status') type = models.ForeignKey('InterventionType', null=True, blank=True, verbose_name=_(u"Type"), db_column='type') disorders = models.ManyToManyField('InterventionDisorder', related_name="interventions", db_table="m_r_intervention_desordre", verbose_name=_(u"Disorders")) jobs = models.ManyToManyField('InterventionJob', through='ManDay', verbose_name=_(u"Jobs")) project = models.ForeignKey('Project', null=True, blank=True, related_name="interventions", verbose_name=_(u"Project"), db_column='chantier') # Special manager objects = Topology.get_manager_cls()() class Meta: db_table = 'm_t_intervention' verbose_name = _(u"Intervention") verbose_name_plural = _(u"Interventions") def __init__(self, *args, **kwargs): super(Intervention, self).__init__(*args, **kwargs) self._geom = None def set_infrastructure(self, baseinfra): self.topology = baseinfra if not self.on_infrastructure: raise ValueError("Expecting an infrastructure or signage") def default_stake(self): stake = None if self.topology: for path in self.topology.paths.all(): if path.stake > stake: stake = path.stake return stake def reload(self, fromdb=None): if self.pk: fromdb = self.__class__.objects.get(pk=self.pk) self.area = fromdb.area AltimetryMixin.reload(self, fromdb) TimeStampedModel.reload(self, fromdb) NoDeleteMixin.reload(self, fromdb) return self def save(self, *args, **kwargs): if self.stake is None: self.stake = self.default_stake() super(Intervention, self).save(*args, **kwargs) self.reload() @property def on_infrastructure(self): return self.is_infrastructure or self.is_signage @property def infrastructure(self): """ Equivalent of topology attribute, but casted to related type (Infrastructure or Signage) """ if self.on_infrastructure: if self.is_signage: return self.signages[0] if self.is_infrastructure: return self.infrastructures[0] return None @classproperty def infrastructure_verbose_name(cls): return _("On") @property def infrastructure_display(self): if self.on_infrastructure: return '<img src="%simages/%s-16.png" title="%s">' % ( settings.STATIC_URL, self.topology.kind.lower(), unicode(_(self.topology.kind))) return '' @property def infrastructure_csv_display(self): if self.on_infrastructure: return unicode(self.infrastructure) return '' @property def is_infrastructure(self): if self.topology: return self.topology.kind == Infrastructure.KIND return False @property def is_signage(self): if self.topology: return self.topology.kind == Signage.KIND return False @property def in_project(self): return self.project is not None @property def paths(self): if self.topology: return self.topology.paths.all() return [] @property def signages(self): if self.is_signage: return [Signage.objects.existing().get(pk=self.topology.pk)] return [] @property def infrastructures(self): if self.is_infrastructure: return [Infrastructure.objects.existing().get(pk=self.topology.pk)] return [] @property def total_manday(self): total = 0.0 for md in self.manday_set.all(): total += float(md.nb_days) return total @property def total_cost(self): total = 0.0 for md in self.manday_set.all(): total += md.cost return total @classproperty def geomfield(cls): return Topology._meta.get_field('geom') @property def geom(self): if self._geom is None: if self.topology: self._geom = self.topology.geom return self._geom @geom.setter def geom(self, value): self._geom = value @property def name_display(self): return u'<a data-pk="%s" href="%s" >%s</a>' % ( self.pk, self.get_detail_url(), self.name) @property def name_csv_display(self): return unicode(self.name) def __unicode__(self): return u"%s (%s)" % (self.name, self.date) @classmethod def path_interventions(cls, path): return cls.objects.existing().filter(topology__aggregations__path=path) @classmethod def trail_interventions(cls, trail): """ Interventions of a trail is the union of interventions on all its paths """ return cls.objects.existing().filter( topology__aggregations__path__trail=trail) @classmethod def topology_interventions(cls, topology): topos = Topology.overlapping(topology).values_list('pk', flat=True) return cls.objects.existing().filter(topology__in=topos).distinct('pk')
class POI(StructureRelated, PicturesMixin, PublishableMixin, MapEntityMixin, Topology): topo_object = models.OneToOneField(Topology, parent_link=True, db_column='evenement') description = models.TextField(verbose_name=_(u"Description"), db_column='description', help_text=_(u"History, details, ...")) type = models.ForeignKey('POIType', related_name='pois', verbose_name=_(u"Type"), db_column='type') eid = models.CharField(verbose_name=_(u"External id"), max_length=128, blank=True, null=True, db_column='id_externe') class Meta: db_table = 'o_t_poi' verbose_name = _(u"POI") verbose_name_plural = _(u"POI") # Override default manager objects = Topology.get_manager_cls(POIManager)() def __unicode__(self): return u"%s (%s)" % (self.name, self.type) @models.permalink def get_document_public_url(self): """ Override ``geotrek.common.mixins.PublishableMixin`` """ return ('trekking:poi_document_public', [], {'lang': get_language(), 'pk': self.pk, 'slug': self.slug}) def save(self, *args, **kwargs): super(POI, self).save(*args, **kwargs) # Invalidate treks map for trek in self.treks.all(): try: os.remove(trek.get_map_image_path()) except OSError: pass @property def type_display(self): return unicode(self.type) @property def serializable_type(self): return {'label': self.type.label, 'pictogram': self.type.get_pictogram_url()} @classmethod def path_pois(cls, path): return cls.objects.existing().filter(aggregations__path=path).distinct('pk') @classmethod def topology_pois(cls, topology): if settings.TREKKING_TOPOLOGY_ENABLED: qs = cls.overlapping(topology) else: area = topology.geom.buffer(settings.TREK_POI_INTERSECTION_MARGIN) qs = cls.objects.existing().filter(geom__intersects=area) return qs @classmethod def published_topology_pois(cls, topology): return cls.topology_pois(topology).filter(published=True) def distance(self, to_cls): return settings.TOURISM_INTERSECTION_MARGIN
class Trek(PicturesMixin, MapEntityMixin, Topology): topo_object = models.OneToOneField(Topology, parent_link=True, db_column='evenement') name = models.CharField(verbose_name=_(u"Name"), max_length=128, help_text=_(u"Public name (Change carefully)"), db_column='nom') departure = models.CharField(verbose_name=_(u"Departure"), max_length=128, blank=True, help_text=_(u"Departure description"), db_column='depart') arrival = models.CharField(verbose_name=_(u"Arrival"), max_length=128, blank=True, help_text=_(u"Arrival description"), db_column='arrivee') published = models.BooleanField(verbose_name=_(u"Published"), default=False, help_text=_(u"Online"), db_column='public') publication_date = models.DateField(verbose_name=_(u"Publication date"), null=True, blank=True, editable=False, db_column='date_publication') description_teaser = models.TextField( verbose_name=_(u"Description teaser"), blank=True, help_text=_(u"A brief summary (map pop-ups)"), db_column='chapeau') description = models.TextField(verbose_name=_(u"Description"), blank=True, db_column='description', help_text=_(u"Complete description")) ambiance = models.TextField(verbose_name=_(u"Ambiance"), blank=True, db_column='ambiance', help_text=_(u"Main attraction and interest")) access = models.TextField(verbose_name=_(u"Access"), blank=True, db_column='acces', help_text=_(u"Best way to go")) disabled_infrastructure = models.TextField( verbose_name=_(u"Disabled infrastructure"), db_column='handicap', blank=True, help_text=_(u"Any specific infrastructure")) duration = models.FloatField(verbose_name=_(u"Duration"), default=0, blank=True, db_column='duree', help_text=_(u"In hours")) is_park_centered = models.BooleanField( verbose_name=_(u"Is in the midst of the park"), db_column='coeur', help_text=_(u"Crosses center of park")) advised_parking = models.CharField(verbose_name=_(u"Advised parking"), max_length=128, blank=True, db_column='parking', help_text=_(u"Where to park")) parking_location = models.PointField(verbose_name=_(u"Parking location"), db_column='geom_parking', srid=settings.SRID, spatial_index=False, blank=True, null=True) public_transport = models.TextField( verbose_name=_(u"Public transport"), blank=True, db_column='transport', help_text=_(u"Train, bus (see web links)")) advice = models.TextField(verbose_name=_(u"Advice"), blank=True, db_column='recommandation', help_text=_(u"Risks, danger, best period, ...")) themes = models.ManyToManyField('Theme', related_name="treks", db_table="o_r_itineraire_theme", blank=True, null=True, verbose_name=_(u"Themes"), help_text=_(u"Main theme(s)")) networks = models.ManyToManyField('TrekNetwork', related_name="treks", db_table="o_r_itineraire_reseau", blank=True, null=True, verbose_name=_(u"Networks"), help_text=_(u"Hiking networks")) usages = models.ManyToManyField('Usage', related_name="treks", db_table="o_r_itineraire_usage", blank=True, null=True, verbose_name=_(u"Usages"), help_text=_(u"Practicability")) route = models.ForeignKey('Route', related_name='treks', blank=True, null=True, verbose_name=_(u"Route"), db_column='parcours') difficulty = models.ForeignKey('DifficultyLevel', related_name='treks', blank=True, null=True, verbose_name=_(u"Difficulty"), db_column='difficulte') web_links = models.ManyToManyField('WebLink', related_name="treks", db_table="o_r_itineraire_web", blank=True, null=True, verbose_name=_(u"Web links"), help_text=_(u"External resources")) related_treks = models.ManyToManyField( 'self', through='TrekRelationship', verbose_name=_(u"Related treks"), symmetrical=False, help_text=_(u"Connections between treks"), related_name='related_treks+') # Hide reverse attribute information_desks = models.ManyToManyField( 'InformationDesk', db_table="o_r_itineraire_renseignement", blank=True, null=True, verbose_name=_(u"Information desks"), help_text=_(u"Where to obtain information")) points_reference = models.MultiPointField( verbose_name=_(u"Points of reference"), db_column='geom_points_reference', srid=settings.SRID, spatial_index=False, blank=True, null=True) objects = Topology.get_manager_cls(models.GeoManager)() class Meta: db_table = 'o_t_itineraire' verbose_name = _(u"Trek") verbose_name_plural = _(u"Treks") ordering = ['name'] def __unicode__(self): return self.name def save(self, *args, **kwargs): if self.publication_date is None and self.any_published: self.publication_date = datetime.date.today() if self.publication_date is not None and not self.any_published: self.publication_date = None super(Trek, self).save(*args, **kwargs) @property def slug(self): return slugify(self.name) @models.permalink def get_document_public_url(self): return ('trekking:trek_document_public', [str(self.pk)]) @property @models.permalink def elevation_area_url(self): return ('trekking:trek_elevation_area', [str(self.pk)]) @property def any_published(self): """Returns True if the trek is published in at least one of the language """ if not settings.TREK_PUBLISHED_BY_LANG: return self.published for l in settings.MAPENTITY_CONFIG['TRANSLATED_LANGUAGES']: if getattr(self, 'published_%s' % l[0], False): return True return False @property def published_status(self): status = [] for l in settings.MAPENTITY_CONFIG['TRANSLATED_LANGUAGES']: if settings.TREK_PUBLISHED_BY_LANG: published = getattr(self, 'published_%s' % l[0], None) or False else: published = self.published status.append({ 'lang': l[0], 'language': l[1], 'status': published }) return status @property def related(self): return self.related_treks.exclude(deleted=True).exclude( pk=self.pk).distinct() @property def relationships(self): # Does not matter if a or b return TrekRelationship.objects.filter(trek_a=self) @property def poi_types(self): if settings.TREKKING_TOPOLOGY_ENABLED: # Can't use values_list and must add 'ordering' because of bug: # https://code.djangoproject.com/ticket/14930 values = self.pois.values('ordering', 'type') else: values = self.pois.values('type') pks = [value['type'] for value in values] return POIType.objects.filter(pk__in=set(pks)) def prepare_map_image(self, rooturl): """ We override the default behaviour of map image preparation : if the trek has a attached picture file with *title* ``mapimage``, we use it as a screenshot. TODO: remove this when screenshots are bullet-proof ? """ attached = None for picture in [a for a in self.attachments.all() if a.is_image]: if picture.title == 'mapimage': attached = picture.attachment_file break if attached is None: super(Trek, self).prepare_map_image(rooturl) else: # Copy it along other screenshots src = os.path.join(settings.MEDIA_ROOT, attached.name) dst = self.get_map_image_path() shutil.copyfile(src, dst) def get_geom_aspect_ratio(self): """ Force trek aspect ratio to fit height and width of image in public document. """ s = settings.TREK_EXPORT_MAP_IMAGE_SIZE return float(s[0]) / s[1] def get_attachment_print(self): """ Look in attachment if there is document to be used as print version """ overriden = self.attachments.filter(title="docprint").get() # Must have OpenOffice document mimetype if overriden.mimetype != [ 'application', 'vnd.oasis.opendocument.text' ]: raise overriden.DoesNotExist() return os.path.join(settings.MEDIA_ROOT, overriden.attachment_file.name) @property def serializable_relationships(self): return [{ 'has_common_departure': rel.has_common_departure, 'has_common_edge': rel.has_common_edge, 'is_circuit_step': rel.is_circuit_step, 'trek': { 'pk': rel.trek_b.pk, 'slug': rel.trek_b.slug, 'name': rel.trek_b.name, 'url': reverse('trekking:trek_json_detail', args=(rel.trek_b.pk, )), }, 'published': rel.trek_b.published } for rel in self.relationships] @property def serializable_cities(self): return [{'code': city.code, 'name': city.name} for city in self.cities] @property def serializable_networks(self): return [{ 'id': network.id, 'pictogram': network.serializable_pictogram, 'name': network.network } for network in self.networks.all()] @property def serializable_difficulty(self): if not self.difficulty: return None return { 'id': self.difficulty.pk, 'pictogram': self.difficulty.serializable_pictogram, 'label': self.difficulty.difficulty } @property def serializable_themes(self): return [{ 'id': t.pk, 'pictogram': t.serializable_pictogram, 'label': t.label } for t in self.themes.all()] @property def serializable_usages(self): return [{ 'id': u.pk, 'pictogram': u.serializable_pictogram, 'label': u.usage } for u in self.usages.all()] @property def serializable_districts(self): return [{'id': d.pk, 'name': d.name} for d in self.districts] @property def serializable_route(self): if not self.route: return None return { 'id': self.route.pk, 'pictogram': self.route.serializable_pictogram, 'label': self.route.route } @property def serializable_web_links(self): return [{ 'id': w.pk, 'name': w.name, 'category': w.serializable_category, 'url': w.url } for w in self.web_links.all()] @property def information_desk(self): """Retrocompatibily method for Geotrek-rando. """ try: return self.information_desks.first() except (ValueError, InformationDesk.DoesNotExist): return None @property def serializable_information_desk(self): return self.information_desk.__json__( ) if self.information_desk else None @property def serializable_information_desks(self): return [d.__json__() for d in self.information_desks.all()] @property def serializable_parking_location(self): if not self.parking_location: return None return self.parking_location.transform(settings.API_SRID, clone=True).coords @property def serializable_points_reference(self): if not self.points_reference: return None geojson = self.points_reference.transform(settings.API_SRID, clone=True).geojson return json.loads(geojson) @property def name_display(self): s = u'<a data-pk="%s" href="%s" title="%s">%s</a>' % ( self.pk, self.get_detail_url(), self.name, self.name) if self.published: s = u'<span class="badge badge-success" title="%s">☆</span> ' % _( "Published") + s return s @property def name_csv_display(self): return unicode(self.name) @property def length_kilometer(self): return "%.1f" % (self.length / 1000.0) @property def networks_display(self): return ', '.join([unicode(n) for n in self.networks.all()]) @property def districts_display(self): return ', '.join([unicode(d) for d in self.districts]) @property def themes_display(self): return ', '.join([unicode(n) for n in self.themes.all()]) @property def usages_display(self): return ', '.join([unicode(n) for n in self.usages.all()]) @property def city_departure(self): cities = self.cities return unicode(cities[0]) if len(cities) > 0 else '' def kml(self): """ Exports trek into KML format, add geometry as linestring and POI as place marks """ kml = simplekml.Kml() # Main itinerary geom3d = self.geom_3d.transform(4326, clone=True) # KML uses WGS84 line = kml.newlinestring(name=self.name, description=plain_text(self.description), coords=geom3d.coords) line.style.linestyle.color = simplekml.Color.red # Red line.style.linestyle.width = 4 # pixels # Place marks for poi in self.pois: place = poi.geom_3d.transform(settings.API_SRID, clone=True) kml.newpoint(name=poi.name, description=plain_text(poi.description), coords=[place.coords]) return kml._genkml() def is_complete(self): """It should also have a description, etc. """ mandatory = settings.TREK_COMPLETENESS_FIELDS for f in mandatory: if not getattr(self, f): return False return True def has_geom_valid(self): """A trek should be a LineString, even if it's a loop. """ return (self.geom is not None and self.geom.geom_type.lower() == 'linestring') def is_publishable(self): return self.is_complete() and self.has_geom_valid() @property def duration_pretty(self): return trekking_tags.duration(self.duration) @classmethod def path_treks(cls, path): treks = cls.objects.existing().filter(aggregations__path=path) # The following part prevents conflict with default trek ordering # ProgrammingError: SELECT DISTINCT ON expressions must match initial ORDER BY expressions return treks.order_by('topo_object').distinct('topo_object') @classmethod def topology_treks(cls, topology): if settings.TREKKING_TOPOLOGY_ENABLED: qs = cls.overlapping(topology) else: area = topology.geom.buffer(settings.TREK_POI_INTERSECTION_MARGIN) qs = cls.objects.filter(geom__intersects=area) return qs