class TouristicContent(AddPropertyMixin, PublishableMixin, MapEntityMixin, StructureRelated, TimeStampedModelMixin, PicturesMixin, NoDeleteMixin): """ A generic touristic content (accomodation, museum, etc.) in the park """ description_teaser = models.TextField( verbose_name=_(u"Description teaser"), blank=True, help_text=_(u"A brief summary"), db_column='chapeau') description = models.TextField(verbose_name=_(u"Description"), blank=True, db_column='description', help_text=_(u"Complete description")) themes = models.ManyToManyField(Theme, related_name="touristiccontents", db_table="t_r_contenu_touristique_theme", blank=True, verbose_name=_(u"Themes"), help_text=_(u"Main theme(s)")) geom = models.GeometryField(verbose_name=_(u"Location"), srid=settings.SRID) category = models.ForeignKey(TouristicContentCategory, related_name='contents', verbose_name=_(u"Category"), db_column='categorie') contact = models.TextField(verbose_name=_(u"Contact"), blank=True, db_column='contact', help_text=_(u"Address, phone, etc.")) email = models.EmailField(verbose_name=_(u"Email"), max_length=256, db_column='email', blank=True, null=True) website = models.URLField(verbose_name=_(u"Website"), max_length=256, db_column='website', blank=True, null=True) practical_info = models.TextField(verbose_name=_(u"Practical info"), blank=True, db_column='infos_pratiques', help_text=_(u"Anything worth to know")) type1 = models.ManyToManyField(TouristicContentType, related_name='contents1', verbose_name=_(u"Type 1"), db_table="t_r_contenu_touristique_type1", blank=True) type2 = models.ManyToManyField(TouristicContentType, related_name='contents2', verbose_name=_(u"Type 2"), db_table="t_r_contenu_touristique_type2", blank=True) source = models.ManyToManyField('common.RecordSource', blank=True, related_name='touristiccontents', verbose_name=_("Source"), db_table='t_r_contenu_touristique_source') portal = models.ManyToManyField('common.TargetPortal', blank=True, related_name='touristiccontents', verbose_name=_("Portal"), db_table='t_r_contenu_touristique_portal') eid = models.CharField(verbose_name=_(u"External id"), max_length=1024, blank=True, null=True, db_column='id_externe') reservation_system = models.ForeignKey( ReservationSystem, verbose_name=_(u"Reservation system"), blank=True, null=True) reservation_id = models.CharField(verbose_name=_(u"Reservation ID"), max_length=1024, blank=True, db_column='id_reservation') approved = models.BooleanField(verbose_name=_(u"Approved"), default=False, db_column='labellise') objects = NoDeleteMixin.get_manager_cls(models.GeoManager)() class Meta: db_table = 't_t_contenu_touristique' verbose_name = _(u"Touristic content") verbose_name_plural = _(u"Touristic contents") def __unicode__(self): return self.name @property def districts_display(self): return ', '.join([unicode(d) for d in self.districts]) @property def type1_label(self): return self.category.type1_label @property def type2_label(self): return self.category.type2_label @property def type1_display(self): return ', '.join([unicode(n) for n in self.type1.all()]) @property def type2_display(self): return ', '.join([unicode(n) for n in self.type2.all()]) @property def prefixed_category_id(self): return self.category.prefixed_id def distance(self, to_cls): return settings.TOURISM_INTERSECTION_MARGIN @property def type(self): """Fake type to simulate POI for mobile app v1""" return self.category @property def min_elevation(self): return 0 @property def max_elevation(self): return 0 @property def portal_display(self): return ', '.join([unicode(portal) for portal in self.portal.all()]) @property def source_display(self): return ','.join([unicode(source) for source in self.source.all()]) @property def themes_display(self): return ','.join([unicode(source) for source in self.themes.all()]) @property def extent(self): return self.geom.buffer(10).transform(settings.API_SRID, clone=True).extent @property def rando_url(self): category_slug = _(u'touristic-content') return '{}/{}/'.format(category_slug, self.slug) @property def meta_description(self): return plain_text(self.description_teaser or self.description)[:500]
class DocumentLinksMixin(models.Model): documents = models.ManyToManyField(Document, blank=True) class Meta: abstract = True
class SiteVisit(AbstractAdditionalData): """Site visit model.""" location_site = models.ForeignKey(LocationSite, on_delete=models.CASCADE, default=None) site_visit_date = models.DateField(default=timezone.now) time = models.DateTimeField(null=True, blank=True) owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, related_name='%(class)s_owner') assessor = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, related_name='%(class)s_assessor') water_level = models.CharField(max_length=100, choices=[(status.name, status.value[WATER_LEVEL_NAME]) for status in WaterLevel], blank=True, null=True) channel_type = models.CharField(max_length=200, choices=[(status.name, status.value[CHANNEL_TYPE_NAME]) for status in ChannelType], blank=True, null=True) water_turbidity = models.CharField(max_length=100, choices=[(status.name, status.value) for status in WaterTurbidity], blank=True, null=True) canopy_cover = models.CharField(max_length=100, choices=[(status.name, status.value) for status in CanopyCover], blank=True, null=True) average_velocity = models.IntegerField(null=True, blank=True) average_depth = models.IntegerField(null=True, blank=True) discharge = models.IntegerField(null=True, blank=True) sass_version = models.IntegerField(null=True, blank=True) sass_biotope_fraction = models.ManyToManyField('sass.SassBiotopeFraction', null=True, blank=True) other_biota = models.TextField(null=True, blank=True) comments_or_observations = models.TextField(null=True, blank=True) data_source = models.ForeignKey('bims.DataSource', null=True, blank=True) def __unicode__(self): return self.location_site.name
class Site(ZoningPropertiesMixin, AddPropertyMixin, PublishableMixin, MapEntityMixin, StructureRelated, TimeStampedModelMixin, MPTTModel): ORIENTATION_CHOICES = ( ('N', _("↑ N")), ('NE', _("↗ NE")), ('E', _("→ E")), ('SE', _("↘ SE")), ('S', _("↓ S")), ('SW', _("↙ SW")), ('W', _("← W")), ('NW', _("↖ NW")), ) geom = models.GeometryCollectionField(verbose_name=_("Location"), srid=settings.SRID) parent = TreeForeignKey('Site', related_name="children", on_delete=models.PROTECT, verbose_name=_("Parent"), null=True, blank=True) practice = models.ForeignKey('Practice', related_name="sites", on_delete=models.PROTECT, verbose_name=_("Practice"), null=True, blank=True) description = models.TextField(verbose_name=_("Description"), blank=True, help_text=_("Complete description")) description_teaser = models.TextField( verbose_name=_("Description teaser"), blank=True, help_text=_("A brief summary (map pop-ups)")) ambiance = models.TextField(verbose_name=_("Ambiance"), blank=True, help_text=_("Main attraction and interest")) advice = models.TextField(verbose_name=_("Advice"), blank=True, help_text=_("Risks, danger, best period, ...")) ratings_min = models.ManyToManyField(Rating, related_name='sites_min', blank=True) ratings_max = models.ManyToManyField(Rating, related_name='sites_max', blank=True) period = models.CharField(verbose_name=_("Period"), max_length=1024, blank=True) orientation = MultiSelectField(verbose_name=_("Orientation"), blank=True, max_length=20, choices=ORIENTATION_CHOICES) wind = MultiSelectField(verbose_name=_("Wind"), blank=True, max_length=20, choices=ORIENTATION_CHOICES) labels = models.ManyToManyField('common.Label', related_name='sites', blank=True, verbose_name=_("Labels")) themes = models.ManyToManyField('common.Theme', related_name="sites", blank=True, verbose_name=_("Themes"), help_text=_("Main theme(s)")) information_desks = models.ManyToManyField( 'tourism.InformationDesk', related_name='sites', blank=True, verbose_name=_("Information desks"), help_text=_("Where to obtain information")) portal = models.ManyToManyField('common.TargetPortal', blank=True, related_name='sites', verbose_name=_("Portal")) source = models.ManyToManyField('common.RecordSource', blank=True, related_name='sites', verbose_name=_("Source")) web_links = models.ManyToManyField('trekking.WebLink', related_name="sites", blank=True, verbose_name=_("Web links"), help_text=_("External resources")) type = models.ForeignKey(SiteType, related_name="sites", on_delete=models.PROTECT, verbose_name=_("Type"), null=True, blank=True) eid = models.CharField(verbose_name=_("External id"), max_length=1024, blank=True, null=True) class Meta: verbose_name = _("Outdoor site") verbose_name_plural = _("Outdoor sites") ordering = ('name', ) class MPTTMeta: order_insertion_by = ['name'] def __str__(self): return self.name @property def name_display(self): return "- " * self.level + super().name_display def distance(self, to_cls): """Distance to associate this site to another class""" return None @classmethod def get_create_label(cls): return _("Add a new outdoor site") @property def published_children(self): if not settings.PUBLISHED_BY_LANG: return self.children.filter(published=True) q = Q() for lang in settings.MODELTRANSLATION_LANGUAGES: q |= Q(**{'published_{}'.format(lang): True}) return self.children.filter(q) @property def super_practices(self): "Return practices of itself and its descendants" practices_id = self.get_descendants(include_self=True) \ .exclude(practice=None) \ .values_list('practice_id', flat=True) return Practice.objects.filter( id__in=practices_id) # Sorted and unique @property def super_practices_display(self): practices = self.super_practices if not practices: return "" verbose = [ str(practice) if practice == self.practice else "<i>{}</i>".format( escape(practice)) for practice in practices ] return ", ".join(verbose) super_practices_verbose_name = _('Practices') @property def super_sectors(self): "Return sectors of itself and its descendants" sectors_id = self.get_descendants(include_self=True) \ .exclude(practice=None) \ .values_list('practice__sector_id', flat=True) return Sector.objects.filter(id__in=sectors_id) # Sorted and unique @property def super_orientation(self): "Return orientation of itself and its descendants" orientation = set( sum( self.get_descendants(include_self=True).values_list( 'orientation', flat=True), [])) return [o for o, _o in self.ORIENTATION_CHOICES if o in orientation] # Sorting @property def super_wind(self): "Return wind of itself and its descendants" wind = set( sum( self.get_descendants(include_self=True).values_list('wind', flat=True), [])) return [o for o, _o in self.ORIENTATION_CHOICES if o in wind] # Sorting
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"), default=False) 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, 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, 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, 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, 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, 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") 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.model_name, self.pk, get_language())) def get_map_image_extent(self, srid=settings.API_SRID): extent = list(super(Trek, self).get_map_image_extent(srid)) if self.parking_location: self.parking_location.transform(srid) extent[0] = min(extent[0], self.parking_location.x) extent[1] = min(extent[1], self.parking_location.y) extent[2] = max(extent[2], self.parking_location.x) extent[3] = max(extent[3], self.parking_location.y) if self.points_reference: self.points_reference.transform(srid) prextent = self.points_reference.extent extent[0] = min(extent[0], prextent[0]) extent[1] = min(extent[1], prextent[1]) extent[2] = max(extent[2], prextent[2]) extent[3] = max(extent[3], prextent[3]) for poi in self.published_pois: poi.geom.transform(srid) extent[0] = min(extent[0], poi.geom.x) extent[1] = min(extent[1], poi.geom.y) extent[2] = max(extent[2], poi.geom.x) extent[3] = max(extent[3], poi.geom.y) return extent @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 information_desks_display(self): return ', '.join([unicode(n) for n in self.information_desks.all()]) @property def accessibilities_display(self): return ', '.join([unicode(n) for n in self.accessibilities.all()]) @property def web_links_display(self): return ', '.join([unicode(n) for n in self.web_links.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.published_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 children def previous_id_for(self, parent): children_id = list(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 = list(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) @property def portal_display(self): return ', '.join([unicode(portal) for portal in self.portal.all()]) @property def source_display(self): return ','.join([unicode(source) for source in self.source.all()]) @property def extent(self): return self.geom.transform( settings.API_SRID, clone=True).extent if self.geom.extent else None @property def rando_url(self): if settings.SPLIT_TREKS_CATEGORIES_BY_PRACTICE and self.practice: category_slug = self.practice.slug else: category_slug = _('trek') return '{}/{}/'.format(category_slug, self.slug) @property def meta_description(self): return plain_text(self.ambiance or self.description_teaser or self.description)[:500]
class TouristicContent(AddPropertyMixin, PublishableMixin, MapEntityMixin, StructureRelated, TimeStampedModelMixin, PicturesMixin, NoDeleteMixin): """ A generic touristic content (accomodation, museum, etc.) in the park """ description_teaser = models.TextField( verbose_name=_(u"Description teaser"), blank=True, help_text=_(u"A brief summary"), db_column='chapeau') description = models.TextField(verbose_name=_(u"Description"), blank=True, db_column='description', help_text=_(u"Complete description")) themes = models.ManyToManyField(Theme, related_name="touristiccontents", db_table="t_r_contenu_touristique_theme", blank=True, null=True, verbose_name=_(u"Themes"), help_text=_(u"Main theme(s)")) geom = models.GeometryField(verbose_name=_(u"Location"), srid=settings.SRID) category = models.ForeignKey(TouristicContentCategory, related_name='contents', verbose_name=_(u"Category"), db_column='categorie') contact = models.TextField(verbose_name=_(u"Contact"), blank=True, db_column='contact', help_text=_(u"Address, phone, etc.")) email = models.EmailField(verbose_name=_(u"Email"), max_length=256, db_column='email', blank=True, null=True) website = models.URLField(verbose_name=_(u"Website"), max_length=256, db_column='website', blank=True, null=True) practical_info = models.TextField(verbose_name=_(u"Practical info"), blank=True, db_column='infos_pratiques', help_text=_(u"Anything worth to know")) type1 = models.ManyToManyField(TouristicContentType, related_name='contents1', verbose_name=_(u"Type 1"), db_table="t_r_contenu_touristique_type1", blank=True) type2 = models.ManyToManyField(TouristicContentType, related_name='contents2', verbose_name=_(u"Type 2"), db_table="t_r_contenu_touristique_type2", blank=True) source = models.ManyToManyField('common.RecordSource', null=True, blank=True, related_name='touristiccontents', verbose_name=_("Source"), db_table='t_r_contenu_touristique_source') eid = models.CharField(verbose_name=_(u"External id"), max_length=128, blank=True, db_column='id_externe') reservation_id = models.CharField(verbose_name=_(u"Reservation id"), max_length=128, blank=True, db_column='id_reservation') approved = models.BooleanField(verbose_name=_(u"Approved"), default=False, db_column='labellise') objects = NoDeleteMixin.get_manager_cls(models.GeoManager)() class Meta: db_table = 't_t_contenu_touristique' verbose_name = _(u"Touristic content") verbose_name_plural = _(u"Touristic contents") def __unicode__(self): return self.name @models.permalink def get_document_public_url(self): """ Override ``geotrek.common.mixins.PublishableMixin`` """ return ('tourism:touristiccontent_document_public', [], { 'lang': get_language(), 'pk': self.pk, 'slug': self.slug }) @property def districts_display(self): return ', '.join([unicode(d) for d in self.districts]) @property def type1_label(self): return self.category.type1_label @property def type2_label(self): return self.category.type2_label @property def types1_display(self): return ', '.join([unicode(n) for n in self.type1.all()]) @property def types2_display(self): return ', '.join([unicode(n) for n in self.type2.all()]) @property def prefixed_category_id(self): return self.category.prefixed_id def distance(self, to_cls): return settings.TOURISM_INTERSECTION_MARGIN @property def type(self): """Fake type to simulate POI for mobile app v1""" return self.category @property def min_elevation(self): return 0 @property def max_elevation(self): return 0
class Device(BaseAccessLevel): """ Device Model Represents a network device eg: an outdoor point-to-point wifi device, a BGP router, a server, and so on """ name = models.CharField(_('name'), max_length=50) node = models.ForeignKey('nodes.Node', verbose_name=_('node')) type = models.CharField(_('type'), max_length=50, choices=DEVICE_TYPES_CHOICES) status = models.SmallIntegerField(_('status'), max_length=2, choices=DEVICE_STATUS_CHOICES, default=DEVICE_STATUS.get('unknown')) # geographic data location = models.PointField( _('location'), blank=True, null=True, help_text=_("""specify device coordinates (if different from node); defaults to node coordinates if node is a point, otherwise if node is a geometry it will default to che centroid of the geometry""" )) elev = models.FloatField(_('elevation'), blank=True, null=True) # device specific routing_protocols = models.ManyToManyField('net.RoutingProtocol', blank=True) os = models.CharField(_('operating system'), max_length=128, blank=True, null=True) os_version = models.CharField(_('operating system version'), max_length=128, blank=True, null=True) first_seen = models.DateTimeField(_('first time seen on'), blank=True, null=True, default=None) last_seen = models.DateTimeField(_('last time seen on'), blank=True, null=True, default=None) # text description = models.CharField(_('description'), max_length=255, blank=True, null=True) notes = models.TextField(_('notes'), blank=True, null=True) # extra data data = DictionaryField( _('extra data'), null=True, blank=True, help_text=_('store extra attributes in JSON string')) shortcuts = ReferencesField(null=True, blank=True) objects = DeviceManager() # list indicating if any other module has extended this model extended_by = [] class Meta: app_label = 'net' def __unicode__(self): return '%s' % self.name def save(self, *args, **kwargs): """ Custom save method does the following: * automatically inherit node coordinates and elevation * save shortcuts if HSTORE is enabled """ custom_checks = kwargs.pop('custom_checks', True) super(Device, self).save(*args, **kwargs) if custom_checks is False: return changed = False if not self.location: self.location = self.node.point changed = True if not self.elev and self.node.elev: self.elev = self.node.elev changed = True original_user = self.shortcuts.get('user') if self.node.user: self.shortcuts['user'] = self.node.user if original_user != self.shortcuts.get('user'): changed = True if 'nodeshot.core.layers' in settings.INSTALLED_APPS: original_layer = self.shortcuts.get('layer') self.shortcuts['layer'] = self.node.layer if original_layer != self.shortcuts.get('layer'): changed = True if changed: self.save(custom_checks=False) @property def owner(self): if 'user' not in self.shortcuts: if self.node or self.node_id: self.save() else: raise Exception('Instance does not have a node set yet') return self.shortcuts['user'] @property def layer(self): if 'nodeshot.core.layers' not in settings.INSTALLED_APPS: return False if 'layer' not in self.shortcuts: if self.node or self.node_id: self.save() else: raise Exception('Instance does not have a node set yet') return self.shortcuts['layer'] if 'grappelli' in settings.INSTALLED_APPS: @staticmethod def autocomplete_search_fields(): return ('name__icontains', )
class FoiLaw(models.Model): name = models.CharField(_("Name"), max_length=255) slug = models.SlugField(_("Slug"), max_length=255) description = models.TextField(_("Description"), blank=True) long_description = models.TextField(_("Website Text"), blank=True) created = models.DateField(_("Creation Date"), blank=True, null=True) updated = models.DateField(_("Updated Date"), blank=True, null=True) request_note = models.TextField(_("request note"), blank=True) meta = models.BooleanField(_("Meta Law"), default=False) law_type = models.CharField(_('law type'), max_length=255, blank=True) combined = models.ManyToManyField( 'FoiLaw', verbose_name=_("Combined Laws"), blank=True ) letter_start = models.TextField(_("Start of Letter"), blank=True) letter_end = models.TextField(_("End of Letter"), blank=True) jurisdiction = models.ForeignKey( Jurisdiction, verbose_name=_('Jurisdiction'), null=True, on_delete=models.SET_NULL, blank=True) priority = models.SmallIntegerField(_("Priority"), default=3) url = models.CharField(_("URL"), max_length=255, blank=True) max_response_time = models.IntegerField(_("Maximal Response Time"), null=True, blank=True, default=30) max_response_time_unit = models.CharField(_("Unit of Response Time"), blank=True, max_length=32, default='day', choices=(('day', _('Day(s)')), ('working_day', _('Working Day(s)')), ('month_de', _('Month(s) (DE)')), )) refusal_reasons = models.TextField( _("Possible Refusal Reasons, one per line, e.g §X.Y: Privacy Concerns"), blank=True) mediator = models.ForeignKey('PublicBody', verbose_name=_("Mediator"), null=True, blank=True, default=None, on_delete=models.SET_NULL, related_name="mediating_laws") email_only = models.BooleanField(_('E-Mail only'), default=False) requires_signature = models.BooleanField(_('Requires signature'), default=False) site = models.ForeignKey(Site, verbose_name=_("Site"), null=True, on_delete=models.SET_NULL, default=settings.SITE_ID) class Meta: verbose_name = _("Freedom of Information Law") verbose_name_plural = _("Freedom of Information Laws") def __str__(self): return "%s (%s)" % (self.name, self.jurisdiction) def get_absolute_url(self): return reverse('publicbody-foilaw-show', kwargs={'slug': self.slug}) def get_absolute_domain_url(self): return "%s%s" % (settings.SITE_URL, self.get_absolute_url()) @property def request_note_html(self): return markdown(self.request_note) @property def description_html(self): return markdown(self.description) @property def address_required(self): return not self.email_only def get_refusal_reason_choices(self): not_applicable = [('n/a', _("No law can be applied"))] if self.meta: return (not_applicable + [ (l[0], "%s: %s" % (law.name, l[1])) for law in self.combined.all() for l in law.get_refusal_reason_choices()[1:]]) else: return (not_applicable + [ (x, Truncator(x).words(12)) for x in self.refusal_reasons.splitlines()]) def as_data(self, request=None): from .api_views import FoiLawSerializer if request is None: ctx = get_fake_api_context() else: ctx = { 'request': request } return FoiLawSerializer(self, context=ctx).data def calculate_due_date(self, date=None, value=None): if date is None: date = timezone.now() if value is None: value = self.max_response_time if self.max_response_time_unit == "month_de": return calculate_month_range_de(date, value) elif self.max_response_time_unit == "day": return date + timedelta(days=value) elif self.max_response_time_unit == "working_day": return calculate_workingday_range(date, value)
class PublicBody(models.Model): name = models.CharField(_("Name"), max_length=255) other_names = models.TextField(_("Other names"), default="", blank=True) slug = models.SlugField(_("Slug"), max_length=255) description = models.TextField(_("Description"), blank=True) url = models.URLField(_("URL"), null=True, blank=True, max_length=500) parent = models.ForeignKey('PublicBody', null=True, blank=True, default=None, on_delete=models.SET_NULL, related_name="children") root = models.ForeignKey('PublicBody', null=True, blank=True, default=None, on_delete=models.SET_NULL, related_name="descendants") depth = models.SmallIntegerField(default=0) classification = models.ForeignKey(Classification, null=True, blank=True, on_delete=models.SET_NULL) email = models.EmailField(_("Email"), blank=True, default='') fax = models.CharField(max_length=50, blank=True) contact = models.TextField(_("Contact"), blank=True) address = models.TextField(_("Address"), blank=True) website_dump = models.TextField(_("Website Dump"), null=True, blank=True) request_note = models.TextField(_("request note"), blank=True) file_index = models.CharField(_("file index"), max_length=1024, blank=True) org_chart = models.CharField(_("organisational chart"), max_length=1024, blank=True) _created_by = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_("Created by"), blank=True, null=True, related_name='public_body_creators', on_delete=models.SET_NULL) _updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_("Updated by"), blank=True, null=True, related_name='public_body_updaters', on_delete=models.SET_NULL) created_at = models.DateTimeField(_("Created at"), default=timezone.now) updated_at = models.DateTimeField(_("Updated at"), default=timezone.now) confirmed = models.BooleanField(_("confirmed"), default=True) number_of_requests = models.IntegerField(_("Number of requests"), default=0) site = models.ForeignKey(Site, verbose_name=_("Site"), null=True, on_delete=models.SET_NULL, default=settings.SITE_ID) wikidata_item = models.CharField(max_length=50, blank=True) jurisdiction = models.ForeignKey(Jurisdiction, verbose_name=_('Jurisdiction'), blank=True, null=True, on_delete=models.SET_NULL) geo = models.PointField(null=True, blank=True, geography=True) regions = models.ManyToManyField(GeoRegion, blank=True) laws = models.ManyToManyField(FoiLaw, verbose_name=_("Freedom of Information Laws")) tags = TaggableManager(through=TaggedPublicBody, blank=True) categories = TaggableManager( through=CategorizedPublicBody, verbose_name=_("categories"), blank=True ) non_filtered_objects = models.Manager() objects = PublicBodyManager() published = objects class Meta: ordering = ('name',) verbose_name = _("Public Body") verbose_name_plural = _("Public Bodies") serializable_fields = ('id', 'name', 'slug', 'request_note_html', 'description', 'url', 'email', 'contact', 'address', 'domain', 'number_of_requests') def __str__(self): return self.name @property def created_by(self): return self._created_by @property def updated_by(self): return self._updated_by @property def domain(self): if self.url and self.url.count('/') > 1: return self.url.split("/")[2] return None @property def all_names(self): names = [self.name, self.other_names] if self.jurisdiction: names.extend([self.jurisdiction.name, self.jurisdiction.slug]) return ' '.join(names) @property def request_note_html(self): return markdown(self.request_note) @property def tag_list(self): return edit_string_for_tags(self.tags.all()) @property def default_law(self): # FIXME: Materialize this? return self.get_applicable_law() def get_applicable_law(self, law_type=None): return get_applicable_law(pb=self, law_type=law_type) def get_absolute_url(self): return reverse('publicbody-show', kwargs={"slug": self.slug}) def get_absolute_short_url(self): return reverse('publicbody-publicbody_shortlink', kwargs={ 'obj_id': self.pk }) def get_absolute_domain_url(self): return "%s%s" % (settings.SITE_URL, self.get_absolute_url()) def get_absolute_domain_short_url(self): return "%s%s" % (settings.SITE_URL, self.get_absolute_short_url()) def get_mediator(self): law = self.default_law if law is None: return None return law.mediator def get_label(self): return mark_safe( '%(name)s - <a href="%(url)s" target="_blank" ' 'class="info-link">%(detail)s</a>' % { "name": escape(self.name), "url": self.get_absolute_url(), "detail": _("More Info") } ) def as_data(self, request=None): from .api_views import PublicBodyListSerializer if request is None: ctx = get_fake_api_context() else: ctx = { 'request': request } return PublicBodyListSerializer(self, context=ctx).data @property def children_count(self): return len(PublicBody.objects.filter(parent=self)) @classmethod def export_csv(cls, queryset): fields = ( "id", "name", "email", "fax", "contact", "address", "url", ('classification', lambda x: x.classification.name if x.classification else None), "jurisdiction__slug", ("categories", lambda x: edit_string_for_tags(x.categories.all())), "other_names", "website_dump", "description", "request_note", "parent__id", ('regions', lambda obj: ','.join(str(x.id) for x in obj.regions.all())) ) return export_csv(queryset, fields)
class Invoice(BaseModel): """ Issue Date, Last Payment Date, Invoice ID, PO Number, Client, Subject, Invoice Amount, Paid Amount, Balance, Subtotal, Discount, Tax, Tax2, Currency, Currency Symbol, Document Type """ subject = models.CharField(max_length=300, blank=True, null=True) issue_date = models.DateField("Issue Date", blank=True, default=timezone.now, null=True) due_date = models.DateField("Due", blank=True, null=True) last_payment_date = models.DateField(blank=True, null=True) start_date = models.DateField("Start Date", blank=True, default=timezone.now, null=True) end_date = models.DateField("End Date", blank=True, default=timezone.now, null=True) po_number = models.CharField("PO Number", max_length=300, blank=True, null=True) sa_number = models.CharField("Subcontractor Agreement Number", max_length=300, blank=True, null=True) client = models.ForeignKey( Client, blank=True, null=True, on_delete=models.CASCADE, limit_choices_to={'active': True}, ) amount = models.DecimalField("Invoice Amount", blank=True, null=True, max_digits=12, decimal_places=2) paid_amount = models.DecimalField(blank=True, null=True, max_digits=12, decimal_places=2) balance = models.DecimalField(blank=True, null=True, max_digits=12, decimal_places=2) subtotal = models.DecimalField(blank=True, null=True, max_digits=12, decimal_places=2) discount = models.IntegerField(blank=True, null=True) tax = models.IntegerField(blank=True, null=True) tax2 = models.IntegerField(blank=True, null=True) project = models.ForeignKey( "Project", blank=True, null=True, on_delete=models.CASCADE, limit_choices_to={'active': True}, ) currency = models.CharField(default="United States Dollar - USD", max_length=300, blank=True, null=True) currency_symbol = models.CharField(default="$", max_length=300, blank=True, null=True) note = models.ManyToManyField( 'Note', blank=True, limit_choices_to={'active': True}, ) def __str__(self): if self.subject: return self.subject else: return 'invoice-%s' % self.pk # https://stackoverflow.com/a/6062320/185820 class Meta: ordering = ["subject"]
class Project(BaseModel): """ Client, Project, Project Code, Start Date, End Date, Project Notes, Total Hours, Billable Hours, Billable Amount, Budget, Budget Spent, Budget Remaining, Total Costs, Team Costs, Expenses """ client = models.ForeignKey( Client, blank=True, null=True, on_delete=models.CASCADE, limit_choices_to={'active': True}, ) name = models.CharField("Project Name", max_length=300, blank=True, null=True) task = models.ForeignKey( "Task", blank=True, null=True, on_delete=models.CASCADE, limit_choices_to={'active': True}, ) team = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, limit_choices_to={'profile__active': True}) start_date = models.DateField(blank=True, null=True) end_date = models.DateField(blank=True, null=True) code = models.IntegerField("Project Code", blank=True, null=True) notes = models.TextField(blank=True, null=True) total_hours = models.FloatField(blank=True, null=True) billable_hours = models.FloatField(blank=True, null=True) amount = models.DecimalField(blank=True, null=True, max_digits=12, decimal_places=2) budget = models.DecimalField(blank=True, null=True, max_digits=12, decimal_places=2) budget_spent = models.DecimalField(blank=True, null=True, max_digits=12, decimal_places=2) budget_remaining = models.DecimalField(blank=True, null=True, max_digits=12, decimal_places=2) total_costs = models.DecimalField(blank=True, null=True, max_digits=12, decimal_places=2) team_costs = models.DecimalField(blank=True, null=True, max_digits=12, decimal_places=2) cost = models.DecimalField(blank=True, null=True, max_digits=12, decimal_places=2) expenses = models.DecimalField(blank=True, null=True, max_digits=12, decimal_places=2) def __str__(self): if self.name: return self.name else: return '-'.join([self._meta.verbose_name, str(self.pk)]) # https://stackoverflow.com/a/6062320/185820 class Meta: ordering = ["name"]
class LandManager(models.Model): class Meta: verbose_name = "Land Manager" verbose_name_plural = "Land Managers" user = models.OneToOneField(User, related_name="landmanager", on_delete=models.CASCADE) management_agency = models.ForeignKey("ManagementAgency", null=True, blank=True, on_delete=models.CASCADE) full_access = models.BooleanField( default=False, help_text="Give this user access to all Archaeological Sites.", ) apply_agency_filter = models.BooleanField( default=False, help_text="Give this user access to all Archaeological Sites managed"\ "by their Agency (as defined above).", blank=True, null=True, ) apply_area_filter = models.BooleanField( default=False, help_text="Give this user access to all Archaeological Sites within "\ "any of the specified areas or groups of areas below.", blank=True, null=True, ) individual_areas = models.ManyToManyField("ManagementArea", blank=True) grouped_areas = models.ManyToManyField("ManagementAreaGroup", blank=True) def __str__(self): return self.user.username @property def all_areas(self): areas = self.individual_areas.all() for ga in self.grouped_areas.all(): areas = areas.union(ga.areas.all()) return areas @property def areas_as_multipolygon(self): poly_agg = list() for area in self.all_areas: # each area is a MultiPolygon so iterate the Polygons within it for poly in area.geom: poly_agg.append(poly) full_multi = MultiPolygon(poly_agg, srid=4326) return full_multi @property def filter_rules(self): rules = {"access_level": "", "": ""} def set_allowed_resources(self): """very confusingly, this method must be called from admin.LandManagerAdmin.save_related(). This is because self.save() and post_save here do not yet have the updated versions of the ManyToManyFields (individual_areas and grouped_areas)""" from hms.utils import update_hms_permissions_table update_hms_permissions_table(user=self.user)
class Resource(ModifiableModel, AutoIdentifiedModel): AUTHENTICATION_TYPES = ( ('none', _('None')), ('weak', _('Weak')), ('strong', _('Strong')) ) ACCESS_CODE_TYPE_NONE = 'none' ACCESS_CODE_TYPE_PIN4 = 'pin4' ACCESS_CODE_TYPE_PIN6 = 'pin6' ACCESS_CODE_TYPES = ( (ACCESS_CODE_TYPE_NONE, _('None')), (ACCESS_CODE_TYPE_PIN4, _('4-digit PIN code')), (ACCESS_CODE_TYPE_PIN6, _('6-digit PIN code')), ) id = models.CharField(primary_key=True, max_length=100) public = models.BooleanField(default=True, verbose_name=_('Public')) unit = models.ForeignKey('Unit', verbose_name=_('Unit'), db_index=True, null=True, blank=True, related_name="resources", on_delete=models.PROTECT) type = models.ForeignKey(ResourceType, verbose_name=_('Resource type'), db_index=True, on_delete=models.PROTECT) purposes = models.ManyToManyField(Purpose, verbose_name=_('Purposes')) name = models.CharField(verbose_name=_('Name'), max_length=200) description = models.TextField(verbose_name=_('Description'), null=True, blank=True) need_manual_confirmation = models.BooleanField(verbose_name=_('Need manual confirmation'), default=False) authentication = models.CharField(blank=False, verbose_name=_('Authentication'), max_length=20, choices=AUTHENTICATION_TYPES) people_capacity = models.PositiveIntegerField(verbose_name=_('People capacity'), null=True, blank=True) area = models.PositiveIntegerField(verbose_name=_('Area (m2)'), null=True, blank=True) # if not set, location is inherited from unit location = models.PointField(verbose_name=_('Location'), null=True, blank=True, srid=settings.DEFAULT_SRID) min_period = models.DurationField(verbose_name=_('Minimum reservation time'), default=datetime.timedelta(minutes=30)) max_period = models.DurationField(verbose_name=_('Maximum reservation time'), null=True, blank=True) slot_size = models.DurationField(verbose_name=_('Slot size for reservation time'), default=datetime.timedelta(minutes=30)) equipment = EquipmentField(Equipment, through='ResourceEquipment', verbose_name=_('Equipment')) max_reservations_per_user = models.PositiveIntegerField(verbose_name=_('Maximum number of active reservations per user'), null=True, blank=True) reservable = models.BooleanField(verbose_name=_('Reservable'), default=False) reservation_info = models.TextField(verbose_name=_('Reservation info'), null=True, blank=True) responsible_contact_info = models.TextField(verbose_name=_('Responsible contact info'), blank=True) generic_terms = models.ForeignKey(TermsOfUse, verbose_name=_('Generic terms'), null=True, blank=True, on_delete=models.SET_NULL) specific_terms = models.TextField(verbose_name=_('Specific terms'), blank=True) reservation_requested_notification_extra = models.TextField(verbose_name=_( 'Extra content to "reservation requested" notification'), blank=True) reservation_confirmed_notification_extra = models.TextField(verbose_name=_( 'Extra content to "reservation confirmed" notification'), blank=True) min_price_per_hour = models.DecimalField(verbose_name=_('Min price per hour'), max_digits=8, decimal_places=2, blank=True, null=True, validators=[MinValueValidator(Decimal('0.00'))]) max_price_per_hour = models.DecimalField(verbose_name=_('Max price per hour'), max_digits=8, decimal_places=2, blank=True, null=True, validators=[MinValueValidator(Decimal('0.00'))]) access_code_type = models.CharField(verbose_name=_('Access code type'), max_length=20, choices=ACCESS_CODE_TYPES, default=ACCESS_CODE_TYPE_NONE) # Access codes can be generated either by the general Respa code or # the Kulkunen app. Kulkunen will set the `generate_access_codes` # attribute by itself if special access code considerations are # needed. generate_access_codes = models.BooleanField( verbose_name=_('Generate access codes'), default=True, editable=False, help_text=_('Should access codes generated by the general system') ) reservable_max_days_in_advance = models.PositiveSmallIntegerField(verbose_name=_('Reservable max. days in advance'), null=True, blank=True) reservable_min_days_in_advance = models.PositiveSmallIntegerField(verbose_name=_('Reservable min. days in advance'), null=True, blank=True) reservation_metadata_set = models.ForeignKey( 'resources.ReservationMetadataSet', verbose_name=_('Reservation metadata set'), null=True, blank=True, on_delete=models.SET_NULL ) external_reservation_url = models.URLField( verbose_name=_('External reservation URL'), help_text=_('A link to an external reservation system if this resource is managed elsewhere'), null=True, blank=True) reservation_extra_questions = models.TextField(verbose_name=_('Reservation extra questions'), blank=True) objects = ResourceQuerySet.as_manager() class Meta: verbose_name = _("resource") verbose_name_plural = _("resources") ordering = ('unit', 'name',) def __str__(self): return "%s (%s)/%s" % (get_translated(self, 'name'), self.id, self.unit) @cached_property def main_image(self): resource_image = next( (image for image in self.images.all() if image.type == 'main'), None) return resource_image.image if resource_image else None def validate_reservation_period(self, reservation, user, data=None): """ Check that given reservation if valid for given user. Reservation may be provided as Reservation or as a data dict. When providing the data dict from a serializer, reservation argument must be present to indicate the reservation being edited, or None if we are creating a new reservation. If the reservation is not valid raises a ValidationError. Staff members have no restrictions at least for now. Normal users cannot make multi day reservations or reservations outside opening hours. :type reservation: Reservation :type user: User :type data: dict[str, Object] """ # no restrictions for staff if self.is_admin(user): return tz = self.unit.get_tz() # check if data from serializer is present: if data: begin = data['begin'] end = data['end'] else: # if data is not provided, the reservation object has the desired data: begin = reservation.begin end = reservation.end if begin.tzinfo: begin = begin.astimezone(tz) else: begin = tz.localize(begin) if end.tzinfo: end = end.astimezone(tz) else: end = tz.localize(end) if begin.date() != end.date(): raise ValidationError(_("You cannot make a multi day reservation")) opening_hours = self.get_opening_hours(begin.date(), end.date()) days = opening_hours.get(begin.date(), None) if days is None or not any(day['opens'] and begin >= day['opens'] and end <= day['closes'] for day in days): if not self._has_perm(user, 'can_ignore_opening_hours'): raise ValidationError(_("You must start and end the reservation during opening hours")) if self.max_period and (end - begin) > self.max_period: raise ValidationError(_("The maximum reservation length is %(max_period)s") % {'max_period': humanize_duration(self.max_period)}) def validate_max_reservations_per_user(self, user): """ Check maximum number of active reservations per user per resource. If the user has too many reservations raises ValidationError. Staff members have no reservation limits. :type user: User """ if self.is_admin(user): return max_count = self.max_reservations_per_user if max_count is not None: reservation_count = self.reservations.filter(user=user).active().count() if reservation_count >= max_count: raise ValidationError(_("Maximum number of active reservations for this resource exceeded.")) def check_reservation_collision(self, begin, end, reservation): overlapping = self.reservations.filter(end__gt=begin, begin__lt=end).active() if reservation: overlapping = overlapping.exclude(pk=reservation.pk) return overlapping.exists() def get_available_hours(self, start=None, end=None, duration=None, reservation=None, during_closing=False): """ Returns hours that the resource is not reserved for a given date range If include_closed=True, will also return hours when the resource is closed, if it is not reserved. This is so that admins can book resources during closing hours. Returns the available hours as a list of dicts. The optional reservation argument is for disregarding a given reservation during checking, if we wish to move an existing reservation. The optional duration argument specifies minimum length for periods to be returned. :rtype: list[dict[str, datetime.datetime]] :type start: datetime.datetime :type end: datetime.datetime :type duration: datetime.timedelta :type reservation: Reservation :type during_closing: bool """ today = arrow.get(timezone.now()) if start is None: start = today.floor('day').naive if end is None: end = today.replace(days=+1).floor('day').naive if not start.tzinfo and not end.tzinfo: """ Only try to localize naive dates """ tz = timezone.get_current_timezone() start = tz.localize(start) end = tz.localize(end) if not during_closing: """ Check open hours only """ open_hours = self.get_opening_hours(start, end) hours_list = [] for date, open_during_date in open_hours.items(): for period in open_during_date: if period['opens']: # if the start or end straddle opening hours opens = period['opens'] if period['opens'] > start else start closes = period['closes'] if period['closes'] < end else end # include_closed to prevent recursion, opening hours need not be rechecked hours_list.extend(self.get_available_hours(start=opens, end=closes, duration=duration, reservation=reservation, during_closing=True)) return hours_list reservations = self.reservations.filter( end__gte=start, begin__lte=end).order_by('begin') hours_list = [({'starts': start})] first_checked = False for res in reservations: # skip the reservation that is being edited if res == reservation: continue # check if the reservation spans the beginning if not first_checked: first_checked = True if res.begin < start: if res.end > end: return [] hours_list[0]['starts'] = res.end # proceed to the next reservation continue if duration: if res.begin - hours_list[-1]['starts'] < duration: # the free period is too short, discard this period hours_list[-1]['starts'] = res.end continue hours_list[-1]['ends'] = timezone.localtime(res.begin) # check if the reservation spans the end if res.end > end: return hours_list hours_list.append({'starts': timezone.localtime(res.end)}) # after the last reservation, we must check if the remaining free period is too short if duration: if end - hours_list[-1]['starts'] < duration: hours_list.pop() return hours_list # otherwise add the remaining free period hours_list[-1]['ends'] = end return hours_list def get_opening_hours(self, begin=None, end=None, opening_hours_cache=None): """ :rtype : dict[str, datetime.datetime] :type begin: datetime.date :type end: datetime.date """ tz = pytz.timezone(self.unit.time_zone) begin, end = determine_hours_time_range(begin, end, tz) if opening_hours_cache is None: hours_objs = self.opening_hours.filter(open_between__overlap=(begin, end, '[)')) else: hours_objs = opening_hours_cache opening_hours = dict() for h in hours_objs: opens = h.open_between.lower.astimezone(tz) closes = h.open_between.upper.astimezone(tz) date = opens.date() hours_item = OrderedDict(opens=opens, closes=closes) date_item = opening_hours.setdefault(date, []) date_item.append(hours_item) # Set the dates when the resource is closed. date = begin.date() end = end.date() while date < end: if date not in opening_hours: opening_hours[date] = [OrderedDict(opens=None, closes=None)] date += datetime.timedelta(days=1) return opening_hours def update_opening_hours(self): hours = self.opening_hours.order_by('open_between') existing_hours = {} for h in hours: assert h.open_between.lower not in existing_hours existing_hours[h.open_between.lower] = h.open_between.upper unit_periods = list(self.unit.periods.all()) resource_periods = list(self.periods.all()) # Periods set for the resource always carry a higher priority. If # nothing is defined for the resource for a given day, use the # periods configured for the unit. for period in unit_periods: period.priority = 0 for period in resource_periods: period.priority = 1 earliest_date = None latest_date = None all_periods = unit_periods + resource_periods for period in all_periods: if earliest_date is None or period.start < earliest_date: earliest_date = period.start if latest_date is None or period.end > latest_date: latest_date = period.end # Assume we delete everything, but remove items from the delete # list if the hours are identical. to_delete = existing_hours to_add = {} if all_periods: hours = get_opening_hours(self.unit.time_zone, all_periods, earliest_date, latest_date) for hours_items in hours.values(): for h in hours_items: if not h['opens'] or not h['closes']: continue if h['opens'] in to_delete and h['closes'] == to_delete[h['opens']]: del to_delete[h['opens']] continue to_add[h['opens']] = h['closes'] if to_delete: ret = ResourceDailyOpeningHours.objects.filter( open_between__in=[(opens, closes, '[)') for opens, closes in to_delete.items()], resource=self ).delete() assert ret[0] == len(to_delete) add_objs = [ ResourceDailyOpeningHours(resource=self, open_between=(opens, closes, '[)')) for opens, closes in to_add.items() ] if add_objs: ResourceDailyOpeningHours.objects.bulk_create(add_objs) def is_admin(self, user): """ Check if the given user is an administrator of this resource. :type user: users.models.User :rtype: bool """ # UserFilterBackend and ReservationFilterSet in resources.api.reservation assume the same behaviour, # so if this is changed those need to be changed as well. if not self.unit: return is_general_admin(user) return self.unit.is_admin(user) def is_manager(self, user): """ Check if the given user is a manager of this resource. :type user: users.models.User :rtype: bool """ if not self.unit: return is_general_admin(user) return self.unit.is_manager(user) def _has_perm(self, user, perm, allow_admin=True): if not is_authenticated_user(user): return False # Admins are almighty. if self.is_admin(user) and allow_admin: return True if hasattr(self, '_permission_checker'): checker = self._permission_checker else: checker = ObjectPermissionChecker(user) # Permissions can be given per-unit if checker.has_perm('unit:%s' % perm, self.unit): return True # ... or through Resource Groups resource_group_perms = [checker.has_perm('group:%s' % perm, rg) for rg in self.groups.all()] return any(resource_group_perms) def get_users_with_perm(self, perm): users = {u for u in get_users_with_perms(self.unit) if u.has_perm('unit:%s' % perm, self.unit)} for rg in self.groups.all(): users |= {u for u in get_users_with_perms(rg) if u.has_perm('group:%s' % perm, rg)} return users def can_make_reservations(self, user): return self.reservable or self._has_perm(user, 'can_make_reservations') def can_modify_reservations(self, user): return self._has_perm(user, 'can_modify_reservations') def can_ignore_opening_hours(self, user): return self._has_perm(user, 'can_ignore_opening_hours') def can_view_reservation_extra_fields(self, user): return self._has_perm(user, 'can_view_reservation_extra_fields') def can_access_reservation_comments(self, user): return self._has_perm(user, 'can_access_reservation_comments') def can_view_catering_orders(self, user): return self._has_perm(user, 'can_view_reservation_catering_orders') def can_modify_catering_orders(self, user): return self._has_perm(user, 'can_modify_reservation_catering_orders') def can_view_product_orders(self, user): return self._has_perm(user, 'can_view_reservation_product_orders', allow_admin=False) def can_modify_paid_reservations(self, user): return self._has_perm(user, 'can_modify_paid_reservations', allow_admin=False) def can_approve_reservations(self, user): return self._has_perm(user, 'can_approve_reservation', allow_admin=False) def can_view_access_codes(self, user): return self._has_perm(user, 'can_view_reservation_access_code') def is_access_code_enabled(self): return self.access_code_type != Resource.ACCESS_CODE_TYPE_NONE def get_reservable_max_days_in_advance(self): return self.reservable_max_days_in_advance or self.unit.reservable_max_days_in_advance def get_reservable_before(self): return create_datetime_days_from_now(self.get_reservable_max_days_in_advance()) def get_reservable_min_days_in_advance(self): return self.reservable_min_days_in_advance or self.unit.reservable_min_days_in_advance def get_reservable_after(self): return create_datetime_days_from_now(self.get_reservable_min_days_in_advance()) def has_rent(self): return self.products.current().rents().exists() def get_supported_reservation_extra_field_names(self, cache=None): if not self.reservation_metadata_set_id: return [] if cache: metadata_set = cache[self.reservation_metadata_set_id] else: metadata_set = self.reservation_metadata_set return [x.field_name for x in metadata_set.supported_fields.all()] def get_required_reservation_extra_field_names(self, cache=None): if not self.reservation_metadata_set: return [] if cache: metadata_set = cache[self.reservation_metadata_set_id] else: metadata_set = self.reservation_metadata_set return [x.field_name for x in metadata_set.required_fields.all()] def clean(self): if self.min_price_per_hour is not None and self.max_price_per_hour is not None: if self.min_price_per_hour > self.max_price_per_hour: raise ValidationError( {'min_price_per_hour': _('This value cannot be greater than max price per hour')} ) if self.min_period % self.slot_size != datetime.timedelta(0): raise ValidationError({'min_period': _('This value must be a multiple of slot_size')}) if self.need_manual_confirmation and self.products.current().exists(): raise ValidationError( {'need_manual_confirmation': _('This cannot be enabled because the resource has product(s).')} )
class Guidebook(models.Model): unique_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) user = models.ForeignKey(UserModel, on_delete=models.CASCADE) name = models.CharField(max_length=100) description = models.TextField(null=True) category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True) cover_image = models.ImageField(upload_to=image_directory_path, null=True) tag = models.ManyToManyField(Tag) is_published = models.BooleanField(default=False) is_approved = models.BooleanField(default=True) created_at = models.DateTimeField(default=datetime.now, blank=True) updated_at = models.DateTimeField(default=datetime.now, blank=True) def get_absolute_url(self): return reverse('guidebook.guidebook_detail', kwargs={'unique_id': str(self.unique_id)}) def getScenes(self): scenes = Scene.objects.filter(guidebook=self).order_by('sort') return scenes def getScenePositions(self): scenes = Scene.objects.filter(guidebook=self) positions = [] if scenes and scenes.count() > 0: for scene in scenes: position = [scene.lat, scene.lng] positions.append(position) return positions def getFirstScene(self): scenes = Scene.objects.filter(guidebook=self).order_by('sort') if scenes and scenes.count() > 0: firstScene = scenes[0] return firstScene else: return '' def getSceneCount(self): scenes = Scene.objects.filter(guidebook=self) return scenes.count() def getLikeCount(self): liked_guidebook = GuidebookLike.objects.filter(guidebook=self) if not liked_guidebook: return 0 else: return liked_guidebook.count() def getShortDescription(self): description = self.description if len(description) > 100: return description[0:100] + '...' else: return description def getTagStr(self): tags = [] if self.tag is None: return '' for tag in self.tag.all(): if tag and tag.is_actived: tags.append(tag.name) if len(tags) > 0: return ', '.join(tags) else: return '' def getTags(self): tags = [] if self.tag is None: return [] for tag in self.tag.all(): if tag and tag.is_actived: tags.append(tag.name) return tags def getCoverImage(self): scenes = Scene.objects.filter(guidebook=self) if scenes.count() > 0: return scenes[0].image_key else: return None
class AOI(GeoQBase, Assignment): """ Low-level organizational object. Now (6/1/14) referred to as a 'Workcell' """ STATUS_VALUES = STATUS_VALUES_LIST STATUS_CHOICES = [(choice, choice) for choice in STATUS_VALUES] PRIORITIES = [(n, n) for n in range(1, 6)] analyst = models.ForeignKey(User, blank=True, null=True, help_text="User assigned to work the workcell.") job = models.ForeignKey(Job, related_name="aois") reviewers = models.ManyToManyField(User, blank=True, null=True, related_name="aoi_reviewers", help_text='Users that actually reviewed this work.') objects = AOIManager() polygon = models.MultiPolygonField() priority = models.SmallIntegerField(choices=PRIORITIES, max_length=1, default=5) status = models.CharField(max_length=15, choices=STATUS_CHOICES, default='Unassigned') class Meta: verbose_name = 'Area of Interest' verbose_name_plural = 'Areas of Interest' permissions = ( ('assign_workcells', 'Assign Workcells'), ('certify_workcells', 'Certify Workcells'), ) def __unicode__(self): aoi_obj = '%s - AOI %s' % (self.name, self.id) return aoi_obj @property def log(self): return Comment.objects.filter(aoi=self).order_by('created_at') @property def assignee_name(self): if self.assignee_id is None: return 'Unknown' else: if self.assignee_type_id == AssigneeType.USER: return User.objects.get(id=self.assignee_id).username else: return Group.objects.get(id=self.assignee_id).name #def save(self): # if analyst or reviewer updated, then create policy to give them permission to edit this object..... # -- Afterwards -- check how this will work with the views. def get_absolute_url(self): if self.job.editable_layer_id is None: return reverse('aoi-work', args=[self.id]) else: return reverse('aoi-mapedit', args=[self.id]) def geoJSON(self): """ Returns geoJSON of the feature. """ if self.id is None: self.id = 1 geojson = SortedDict() geojson["type"] = "Feature" geojson["properties"] = dict( id=self.id, status=self.status, analyst=(self.analyst.username if self.analyst is not None else 'None'), assignee=self.assignee_name, priority=self.priority, delete_url=reverse('aoi-deleter', args=[self.id])) geojson["geometry"] = json.loads(self.polygon.json) geojson["properties"]["absolute_url"] = self.get_absolute_url() return clean_dumps(geojson) def logJSON(self): return [ob.to_dict() for ob in self.log] def properties_json(self): """ Returns json of the feature properties. """ if self.id is None: self.id = 1 properties_main = self.properties or {} properties_built = dict( status=self.status, analyst=(self.analyst.username if self.analyst is not None else 'Unassigned'), priority=self.priority) prop_json = dict(properties_built.items() + properties_main.items()) return clean_dumps(prop_json) def map_detail(self): """ Get map coordinates for MapEdit """ center = self.polygon.centroid return "15/%f/%f" % (center.y, center.x) def grid_geoJSON(self): """ Return geoJSON of workcells for export """ if self.id is None: self.id = 1 geojson = SortedDict() geojson["type"] = "Feature" geojson["properties"] = dict( id=self.id, priority=self.priority, status=self.status) geojson["geometry"] = json.loads(self.polygon.json) return clean_dumps(geojson) def user_can_complete(self, user): """ Returns whether the user can update the AOI as complete. """ return user == self.analyst or user in self.job.reviewers.all()
class BaseContent(BaseClass): """ Merengue managed content types use relational database inheritance to have a non abstract base managed model to be able to selecting all objects with only one SQL. If you want to create a new content type you should inherits from this model. """ __metaclass__ = BaseContentMeta contact_info = models.ForeignKey(ContactInfo, verbose_name=_('contact info'), null=True, blank=True, editable=False, on_delete=models.SET_NULL) contact_info.delete_cascade = False related_items = models.ManyToManyField('BaseContent', verbose_name=_('related items'), null=True, blank=True, editable=False) creation_date = models.DateTimeField(verbose_name=_('creation date'), blank=True, null=True, auto_now_add=True) modification_date = models.DateTimeField(verbose_name=_('modification date'), blank=True, null=True, auto_now=True) # this is handled in admin forms user_modification_date = models.DateTimeField(verbose_name=_('modification date'), blank=True, null=True, editable=False) cached_plain_text = models.TextField(verbose_name=_('cached plain text'), null=True, blank=True, editable=False) last_editor = models.ForeignKey(User, null=True, blank=True, editable=False, related_name='last_edited_content', verbose_name=_('last editor'), on_delete=models.SET_NULL) # permission global adquire_global_permissions = models.BooleanField(_('Adquire global permissions'), default=True) # tagging info tags = TagField(verbose_name=_('Tags'), help_text=_('Tags will be splitted by commas.')) # meta info meta_desc = models.TextField(verbose_name=_('meta description'), null=True, blank=True) commentable = models.CharField(_('comments'), max_length=20, default=settings.CONTENT_COMMENTABLE_BY_DEFAULT, choices=COMMENTABLE_CHOICES, editable=True, help_text=_('Is that content commentable')) # multimedia resources multimedia = models.ManyToManyField(BaseMultimedia, blank=True, verbose_name=_('multimedia'), through='MultimediaRelation') # cached class name from this content # this should has null=False, blank=False but in practice this # is not possible due to the way the class_name is computed # (the object need to get its child class and this is not # available until the parent register is saved) class_name = models.CharField(verbose_name=_('class name'), max_length=100, db_index=True, editable=False, null=True) # ranking system rank = models.FloatField(verbose_name=_('rank value'), default=100.0, db_index=True, editable=False, blank=False) # access control owners = models.ManyToManyField(User, verbose_name=_('owners'), null=True, blank=True, related_name='contents_owned') participants = models.ManyToManyField(User, verbose_name=_('participants'), null=True, blank=True, related_name='contents_participated') position = models.PositiveIntegerField(verbose_name=_('position'), null=True, editable=False) # structural contents no_changeable = models.BooleanField(default=False, editable=False) no_deletable = models.BooleanField(default=False, editable=False) # block control. cached value for controlling if content has special blocks attached has_related_blocks = models.BooleanField(default=False, editable=False, db_index=True) # structural fields no_changeable_fields = JSONField(null=True, blank=True, editable=False) objects = BaseContentManager() store_plain = ('name', 'description', ) def __init__(self, *args, **kwargs): super(BaseContent, self).__init__(*args, **kwargs) self._original_status = self.status def _generate_cached_plain_text(self): if self.id: instance = self.get_real_instance() else: instance = self to_store = instance.store_plain translated_fields = get_all_translatable_fields(instance.__class__) for lang in settings.LANGUAGES: setattr(self, 'cached_plain_text_%s' % lang[0], '') for field_name in to_store: for lang_code, lang_name in settings.LANGUAGES: cached = getattr(self, 'cached_plain_text_%s' % lang_code, '') if field_name in translated_fields: cached += self._convert_to_plain(getattr(self, '%s_%s' % (field_name, lang_code), '')) else: cached += self._convert_to_plain(getattr(self, field_name, '')) if cached: cached += " " setattr(self, 'cached_plain_text_%s' % lang_code, cached) def _convert_to_plain(self, value): if value: value = force_unicode(value) text = re.sub('<br[^>]*>', u'\n', value) text = unescape_entities(text) text = strip_tags(text) text = text.strip() text = unicodedata.normalize('NFKD', text.lower()).encode('ascii', 'ignore') return text return '' def _plain_text(self): if not self.cached_plain_text: self._generate_cached_plain_text() self.save() return self.cached_plain_text plain_text = property(_plain_text) class Meta: verbose_name = _('base content') verbose_name_plural = _('base contents') translate = ('cached_plain_text', ) abstract = False #content_view_template = 'content_view.html' # default definition by BaseContentMeta metaclass ordering = ('position', get_fallback_fieldname('name'), ) check_slug_uniqueness = True def admin_link_markup(self): return '<a href="%s">%s</a>' % (self.get_real_instance().get_admin_absolute_url(), self.name) admin_link_markup.allow_tags = True admin_link_markup.short_description = _('Name') def save(self, update_rank=False, **kwargs): """ Do extra logic like setting ordering, update ranking if needed remove some tags """ # new objects should be added in last place if not self.id: try: ordered = self.__class__.objects.filter(position__isnull=False).order_by('-position') last = ordered[0] self.position = last.position + 1 except IndexError: pass self._generate_cached_plain_text() super(BaseContent, self).save(**kwargs) object_update_again = False if update_rank: self.rank = self.calculate_rank() object_update_again = True tags_field = self._meta.get_field('tags') tags_field._save(instance=self) # updating the tags may leave some without related items, so we'll delete them for tag in Tag.objects.filter(items__isnull=True): if hasattr(tag, 'itag'): # itag deletion gets rid of the original tag object tag.itag.delete() else: tag.delete() # Save thumbnail of main_image in model inherited main_image_field = self._meta.get_field('main_image') try: main_image_field._rename_resize_image(instance=self) except OSError: pass # this may fail if the image file does not exist if object_update_again: non_pks = [f for f in self._meta.local_fields if not f.primary_key] if non_pks: # we force an update since we already did an insert super(BaseContent, self).save(force_update=True) def validate_unique(self, exclude=None): """ Check the slug uniqueness """ errors = {} try: super(BaseContent, self).validate_unique(exclude) except ValidationError, validation_errors: errors = validation_errors.update_error_dict(errors) if self._meta.check_slug_uniqueness: # validate that slug is unique in the model content_with_same_slug = self.__class__.objects.filter(slug=self.slug).exclude(pk=self.pk).exists() if content_with_same_slug: errors.setdefault('slug', []).append(ugettext(u'Please set other slug. This slug has been assigned')) if errors: raise ValidationError(errors)
class Project(GeoQBase): """ Top-level organizational object. """ PROJECT_TYPES = [ ("Hurricane/Cyclone", "Hurricane/Cyclone"), ("Tornado", "Tornado"), ("Earthquake", "Earthquake"), ("Extreme Weather", "Extreme Weather"), ("Fire", "Fire"), ("Flood", "Flood"), ("Tsunami", "Tsunami"), ("Volcano", "Volcano"), ("Pandemic", "Pandemic"), ("Exercise", "Exercise"), ("Special Event", "Special Event"), ("Training", "Training"), ] project_type = models.CharField(max_length=50, choices=PROJECT_TYPES) private = models.BooleanField(default=False, help_text="Check this to make this project 'Private' and available only to users assigned to it.") project_admins = models.ManyToManyField( User, blank=True, null=True, related_name="project_admins", help_text='User that has admin rights to project.') contributors = models.ManyToManyField( User, blank=True, null=True, related_name="contributors", help_text='User that will be able to take on jobs.') class Meta: permissions = ( ('open_project', 'Open Project'), ('close_project', 'Close Project'), ('archive_project', 'Archive Project'), ) ordering = ('-created_at',) @property def jobs(self): return Job.objects.filter(project=self) @property def job_count(self): return self.jobs.count() @property def user_count(self): return User.objects.filter(analysts__project__id=self.id).distinct().count() @property def aois(self): return AOI.objects.filter(job__project__id=self.id) @property def aoi_count(self): return self.aois.count() @property def aois_envelope(self): return MultiPolygon([n.aois_envelope() for n in self.jobs if n.aois.count()]) @property def aois_envelope_by_job(self): jobs = [] for job in self.jobs: if job.aois.count(): job_envelope = job.aois_envelope() envelope_string = job_envelope.json if envelope_string: job_poly = json.loads(envelope_string) job_poly['properties'] = {"job_id": str(job.id), "link": str(job.get_absolute_url()), "name": str(job.name)} jobs.append(job_poly) return clean_dumps(jobs, ensure_ascii=True) def get_absolute_url(self): return reverse('project-detail', args=[self.id]) def get_update_url(self): return reverse('project-update', args=[self.id])
class Polygon(models.Model): """ Polygon represent geo object, and could refer to collection of lower polygons """ # Polygon as polygon polygon_id = models.CharField(max_length=50, primary_key=True) organizations = models.ManyToManyField(Organization, blank=True) shape = models.PolygonField(null=True, blank=True) centroid = models.PointField(null=True, blank=True) address = models.CharField(max_length=800, null=True, blank=True) layer = models.ForeignKey('self', blank=True, null=True) # Polygon as layer country = 0 region = 1 area = 2 district = 3 building = 4 LEVEL = ((country, _("Root polygon")), (region, _("Regions of country")), (area, _("Subregions or big sities")), (district, _("Towns or districts of city")), (building, _("Houses"))) level = models.IntegerField(choices=LEVEL, default=building) is_verified = models.BooleanField(default=True) updated = models.DateTimeField(auto_now=True) objects = models.GeoManager() # moderation_filter @property def total_claims(self): claims = 0 if self.level == self.building: # claims += sum([x.claims for x in self.organizations.all()]) # claims = self.organizations.filter(claim__moderation__in=Moderator.allowed_statuses()).count() claims = get_claims_for_poly(self.polygon_id) else: # cached = cache.get('claims_for::%s' % self.polygon_id) cached = None if cached is not None: claims = cached else: # childs = self.polygon_set.all() # for child in childs: # claims += child.total_claims try: claims = get_sum_for_layers([self.polygon_id], self.level)[self.polygon_id] except KeyError: claims = 0 # cache.set('claims_for::%s' % self.polygon_id, claims, 300) return claims @staticmethod def color_spot(value, max_value): if max_value: percent = value * 100 / max_value else: percent = 0 if percent <= 20: return 'green' elif percent <= 70: return 'yellow' else: return 'red' def first_organization(self): orgs = self.organizations.all() if orgs: return orgs[0] else: return None def __str__(self): return 'Polygon ' + str(self.polygon_id)
class TouristicEvent(AddPropertyMixin, PublishableMixin, MapEntityMixin, StructureRelated, PicturesMixin, TimeStampedModelMixin, NoDeleteMixin): """ A touristic event (conference, workshop, etc.) in the park """ description_teaser = models.TextField( verbose_name=_(u"Description teaser"), blank=True, help_text=_(u"A brief summary"), db_column='chapeau') description = models.TextField(verbose_name=_(u"Description"), blank=True, db_column='description', help_text=_(u"Complete description")) themes = models.ManyToManyField(Theme, related_name="touristic_events", db_table="t_r_evenement_touristique_theme", blank=True, null=True, verbose_name=_(u"Themes"), help_text=_(u"Main theme(s)")) geom = models.PointField(verbose_name=_(u"Location"), srid=settings.SRID) begin_date = models.DateField(blank=True, null=True, verbose_name=_(u"Begin date"), db_column='date_debut') end_date = models.DateField(blank=True, null=True, verbose_name=_(u"End date"), db_column='date_fin') duration = models.CharField(verbose_name=_(u"Duration"), max_length=64, blank=True, db_column='duree', help_text=_(u"3 days, season, ...")) meeting_point = models.CharField(verbose_name=_(u"Meeting point"), max_length=256, blank=True, db_column='point_rdv', help_text=_(u"Where exactly ?")) meeting_time = models.TimeField(verbose_name=_(u"Meeting time"), blank=True, null=True, db_column='heure_rdv', help_text=_(u"11:00, 23:30")) contact = models.TextField(verbose_name=_(u"Contact"), blank=True, db_column='contact') email = models.EmailField(verbose_name=_(u"Email"), max_length=256, db_column='email', blank=True, null=True) website = models.URLField(verbose_name=_(u"Website"), max_length=256, db_column='website', blank=True, null=True) organizer = models.CharField(verbose_name=_(u"Organizer"), max_length=256, blank=True, db_column='organisateur') speaker = models.CharField(verbose_name=_(u"Speaker"), max_length=256, blank=True, db_column='intervenant') type = models.ForeignKey(TouristicEventType, verbose_name=_(u"Type"), blank=True, null=True, db_column='type') accessibility = models.CharField(verbose_name=_(u"Accessibility"), max_length=256, blank=True, db_column='accessibilite') participant_number = models.CharField( verbose_name=_(u"Number of participants"), max_length=256, blank=True, db_column='nb_places') booking = models.TextField(verbose_name=_(u"Booking"), blank=True, db_column='reservation') target_audience = models.CharField(verbose_name=_(u"Target audience"), max_length=128, blank=True, null=True, db_column='public_vise') practical_info = models.TextField( verbose_name=_(u"Practical info"), blank=True, db_column='infos_pratiques', help_text=_(u"Recommandations / To plan / Advices")) source = models.ManyToManyField( 'common.RecordSource', null=True, blank=True, related_name='touristicevents', verbose_name=_("Source"), db_table='t_r_evenement_touristique_source') eid = models.CharField(verbose_name=_(u"External id"), max_length=128, blank=True, db_column='id_externe') approved = models.BooleanField(verbose_name=_(u"Approved"), default=False, db_column='labellise') objects = NoDeleteMixin.get_manager_cls(models.GeoManager)() category_id_prefix = 'E' class Meta: db_table = 't_t_evenement_touristique' verbose_name = _(u"Touristic event") verbose_name_plural = _(u"Touristic events") ordering = ['-begin_date'] def __unicode__(self): return self.name @models.permalink def get_document_public_url(self): """ Override ``geotrek.common.mixins.PublishableMixin`` """ return ('tourism:touristicevent_document_public', [], { 'lang': get_language(), 'pk': self.pk, 'slug': self.slug }) @property def type1(self): return [self.type] if self.type else [] @property def type2(self): return [] @property def districts_display(self): return ', '.join([unicode(d) for d in self.districts]) @property def dates_display(self): if not self.begin_date and not self.end_date: return u"" elif not self.end_date: return _(u"starting from {begin}").format( begin=date_format(self.begin_date, 'SHORT_DATE_FORMAT')) elif not self.begin_date: return _(u"up to {end}").format( end=date_format(self.end_date, 'SHORT_DATE_FORMAT')) elif self.begin_date == self.end_date: return date_format(self.begin_date, 'SHORT_DATE_FORMAT') else: return _(u"from {begin} to {end}").format( begin=date_format(self.begin_date, 'SHORT_DATE_FORMAT'), end=date_format(self.end_date, 'SHORT_DATE_FORMAT')) @property def prefixed_category_id(self): return self.category_id_prefix def distance(self, to_cls): return settings.TOURISM_INTERSECTION_MARGIN
class RouteSegment(models.Model): start = models.ForeignKey('stops.Stop', related_name='segments_starting') end = models.ForeignKey('stops.Stop', related_name='segments_ending') route = models.ForeignKey(Route, related_name="segments") stops = models.ManyToManyField('stops.Stop', through='SegmentStop')
class Place(Entity): chain = models.ForeignKey(Chain, blank=True, null=True, on_delete=models.CASCADE) location = models.PointField() features = models.ManyToManyField("Feature")
class UnidadeProducao(Propriedade): QUALIDADE_AGUA = ( ('Boa', 'Boa'), ('Regular', 'Regular'), ('Ruim', 'Ruim'), ) beneficiario = ChainedForeignKey(Beneficiario, chained_field="municipio", chained_model_field="municipio", verbose_name=u'Beneficiário') participacao = models.DecimalField(u'Participação %', max_digits=8, decimal_places=2, blank=True, null=True) tituloDominio = models.CharField(u'Título de Domínio', max_length=16, blank=True) dataRegistro = models.DateField(u'Data de Registro', blank=True, null=True) registro = models.CharField(u'Registro', max_length=16, blank=True) receitaFederal = models.CharField(u'Nº Receita Federal (ITR)', max_length=16) qualidadeAgua = models.CharField(u'Qualidade da Água', max_length=8, choices=QUALIDADE_AGUA, blank=True) destinoLixo = models.ManyToManyField(DestinoLixo, verbose_name=u'Destino do Lixo', blank=True) utilizacaoAgrotoxico = models.ManyToManyField( Agrotoxico, verbose_name=u'Utilização de Agrotóxicos', blank=True) destinoEmbalagemAgrotoxico = models.ManyToManyField( DestinoEmbalagemAgrotoxico, verbose_name=u'Destino da Embalagem de Agrotóxico', blank=True) preparoSolo = models.ManyToManyField(PreparoSolo, verbose_name=u'Preparo do Solo', blank=True) areaErosao = models.DecimalField(u'Área com Erosão', max_digits=8, decimal_places=2, blank=True, null=True, default='0.0') praticaConservacaoSolo = models.ManyToManyField( PraticaConservacaoSolo, verbose_name=u'Pratica de Conservação do Solo', blank=True) insumosOrganicos = models.ManyToManyField( InsumosOrganicos, verbose_name=u'Insumos Orgânicos', blank=True) rotacaoCultura = models.BooleanField(u'Rotação de Cultura', blank=True) utilizacaoArvores = models.ManyToManyField( UtilizacaoArvores, verbose_name=u'Utilização de Árvores', blank=True) class Meta: verbose_name = u'Unidade de Produção Familiar' verbose_name_plural = u'Unidades de Produção Familiar' ordering = ['beneficiario__denominacao'] def get_absolute_url(self): return '/mapagro/unidade/%i/' % self.id def get_beneficiario_url(self): return '/mapagro/beneficiario/%i/' % self.beneficiario.id def get_renda_url(self): return '/mapagro/renda/%i/%i/' % (self.id, self.beneficiario.id)
class Route(DescriptionedModel): points = models.ManyToManyField('Point', through='RoutePointM2M', verbose_name=_('points')) center = LocationField(verbose_name=_('center'), based_fields=['name'], default='POINT(0.0 0.0)') saved_by = models.ManyToManyField(Tourist, through='SavedRoutes', blank=True, related_name='saved_by', verbose_name=_('saved_by')) tourist = models.ForeignKey(Tourist, on_delete=models.CASCADE, verbose_name=_('tourist')) popularity = models.PositiveIntegerField(_('popularity'), blank=True, null=True) cost = models.PositiveIntegerField(_('cost'), blank=True, null=True) extremality = models.PositiveSmallIntegerField(_('extremality'), blank=True, null=True) objects = models.GeoManager() @cached_property def formatted_points(self): data = dict(points=[]) for p in self.tourist_points: point = dict(location=p.location.get_coords()) point['name'] = p.name point['description'] = p.description data['points'].append(point) return data @cached_property def length(self): # километры length = sum( a.location.distance(b.location) for (a, b) in self._pairwise(self.tourist_points)) * 100 return round(length) @cached_property def pairwised_points(self): """route.points.all() -> (point0, points1), (point1, point2), (point2, point3), ...""" return self._pairwise(self.tourist_points) @staticmethod def _pairwise(iterable): """iterable -> (iterable[0], iterable[1]), (iterable[1], iterable[2]), ...)""" a, b = tee(iterable) next(b, None) return zip(a, b) @cached_property def tourist_points(self): return self.points.filter(companypoint__isnull=True, adminpoint__isnull=True) @cached_property def company_points(self): return self.points.filter(companypoint__isnull=False, adminpoint__isnull=True) @cached_property def admin_points(self): return self.points.filter(adminpoint__isnull=False, companypoint__isnull=True) def __str__(self): return self.name
class Trip(models.Model): route = models.ForeignKey(Route, models.CASCADE) inbound = models.BooleanField(default=False) journey_pattern = models.CharField(max_length=255, blank=True) ticket_machine_code = models.CharField(max_length=255, blank=True, db_index=True) block = models.CharField(max_length=255, blank=True, db_index=True) destination = models.ForeignKey('busstops.StopPoint', models.SET_NULL, null=True, blank=True) calendar = models.ForeignKey(Calendar, models.CASCADE) sequence = models.PositiveSmallIntegerField(null=True, blank=True) notes = models.ManyToManyField(Note, blank=True) start = SecondsField() end = SecondsField() garage = models.ForeignKey('Garage', models.SET_NULL, null=True, blank=True) def __str__(self): return format_timedelta(self.start) def start_time(self): return format_timedelta(self.start) def end_time(self): return format_timedelta(self.end) class Meta: index_together = ( ('route', 'start', 'end'), ) def __cmp__(a, b): """Compare two journeys""" if a.sequence is not None and a.sequence is not None: a_time = a.sequence b_time = b.sequence else: a_time = a.start b_time = b.start a_times = a.stoptime_set.all() b_times = b.stoptime_set.all() if a_times and b_times and a_times[0].get_key() != b_times[0].get_key(): if a.destination_id == b.destination_id: a_time = a.end b_time = b.end else: times = {time.get_key(): time.arrival_or_departure() for time in a_times} for time in b_times: key = time.get_key() if key in times: a_time = times[key] b_time = time.arrival_or_departure() break if a_time > b_time: return 1 if a_time < b_time: return -1 return 0 def copy(self, start): difference = start - self.start new_trip = Trip.objects.get(id=self.id) times = list(new_trip.stoptime_set.all()) new_trip.id = None new_trip.start += difference new_trip.end += difference new_trip.save() for time in times: time.id = None time.arrival += difference time.departure += difference time.trip = new_trip time.save() def __repr__(self): return str(self.start) def get_absolute_url(self): return reverse('trip_detail', args=(self.id,))
class App(models.Model): def only_filename(instance, filename): return filename name = models.CharField(max_length=200, null=True, blank=True) title = models.CharField(max_length=200, null=True, blank=True) description = models.TextField(null=True, blank=True) short_description = models.TextField(null=True, blank=True) app_url = models.URLField(null=True, blank=True) author = models.CharField(max_length=200, null=True, blank=True) author_website = models.URLField(null=True, blank=True) license = models.CharField(max_length=200, null=True, blank=True) tags = TaggableManager() date_installed = models.DateTimeField( 'Date Installed', auto_now_add=True, null=True) installed_by = models.ForeignKey( geonode_settings.AUTH_USER_MODEL, null=True, blank=True) single_instance = models.BooleanField( default=False, null=False, blank=False) category = models.ManyToManyField( AppType, related_name='apps') license = models.CharField(max_length=100, null=True, blank=True) status = models.CharField( max_length=100, blank=False, null=False, default='Alpha') owner_url = models.URLField(null=True, blank=True) help_url = models.URLField(null=True, blank=True) app_img_url = models.TextField(max_length=1000, blank=True, null=True) rating = models.IntegerField(default=0, null=True, blank=True) contact_name = models.CharField(max_length=200, null=True, blank=True) contact_email = models.EmailField(null=True, blank=True) version = models.CharField(max_length=10) store = models.ForeignKey(AppStore, null=True) order = models.IntegerField(null=True, default=0) default_config = JSONField(default={}) class meta(object): ordering = ['order'] def __str__(self): return self.title def __unicode__(self): return self.title @property def settings_url(self): try: return reverse("%s_settings" % self.name) except BaseException as e: logger.error(e.message) return None @property def new_url(self): try: return reverse("%s.new" % self.name) except BaseException as e: logger.error(e.message) return None _apps_config = None @property def apps_config(self): if App._apps_config is None: App._apps_config = AppsConfig() return App._apps_config @property def config(self): return self.apps_config.get_by_name(self.name)
class Species(OptionalPictogramMixin): SPECIES = 1 REGULATORY = 2 name = models.CharField(max_length=250, db_column='nom', verbose_name=_(u"Name")) period01 = models.BooleanField(default=False, db_column='periode01', verbose_name=_(u"January")) period02 = models.BooleanField(default=False, db_column='periode02', verbose_name=_(u"February")) period03 = models.BooleanField(default=False, db_column='periode03', verbose_name=_(u"March")) period04 = models.BooleanField(default=False, db_column='periode04', verbose_name=_(u"April")) period05 = models.BooleanField(default=False, db_column='periode05', verbose_name=_(u"May")) period06 = models.BooleanField(default=False, db_column='periode06', verbose_name=_(u"June")) period07 = models.BooleanField(default=False, db_column='periode07', verbose_name=_(u"July")) period08 = models.BooleanField(default=False, db_column='periode08', verbose_name=_(u"August")) period09 = models.BooleanField(default=False, db_column='periode09', verbose_name=_(u"September")) period10 = models.BooleanField(default=False, db_column='periode10', verbose_name=_(u"October")) period11 = models.BooleanField(default=False, db_column='periode11', verbose_name=_(u"November")) period12 = models.BooleanField(default=False, db_column='periode12', verbose_name=_(u"Decembre")) practices = models.ManyToManyField(SportPractice, db_table='s_r_espece_pratique_sportive', verbose_name=_(u"Sport practices")) url = models.URLField(blank=True, verbose_name="URL") radius = models.IntegerField(db_column='rayon', blank=True, null=True, verbose_name=_(u"Bubble radius"), help_text=_(u"meters")) category = models.IntegerField( verbose_name=_(u"Category"), db_column='categorie', editable=False, default=SPECIES, choices=((SPECIES, pgettext_lazy(u"Singular", u"Species")), (REGULATORY, _(u"Regulatory")))) eid = models.CharField(verbose_name=_(u"External id"), max_length=1024, blank=True, null=True, db_column='id_externe') class Meta: ordering = ['name'] db_table = 's_b_espece_ou_suite_zone_regl' verbose_name = pgettext_lazy(u"Singular", u"Species") verbose_name_plural = _(u"Species") def __unicode__(self): return self.name def pretty_period(self): return u", ".join([ unicode( self._meta.get_field('period{:02}'.format(p)).verbose_name) for p in range(1, 13) if getattr(self, 'period{:02}'.format(p)) ]) def pretty_practices(self): return u", ".join( [unicode(practice) for practice in self.practices.all()])
class Site(BaseModel): # Formerly the SculptureSite model. # QAZ: How, when, by whom and why are site_ids assigned? site_id = models.CharField(max_length=16, blank=True) status = models.ForeignKey('SiteStatus') visit_date = models.CharField(max_length=216, blank=True, help_text='Eg: 01 Jan 2013', verbose_name='visit date(s)') authors = models.ManyToManyField(Contributor, blank=True, related_name='sites') fieldworker_may_2017 = models.CharField( max_length=2048, blank=True, help_text="Fieldworkers as of May 2018", verbose_name="May 2018 Fieldworkers") # I know 2017 != 2018 name = models.CharField(max_length=256) country = models.ForeignKey(Country) grid_reference = models.CharField('national grid reference', max_length=25, blank=True, help_text='E.g. SO 123 321 or N 31 22') latitude = models.FloatField(blank=True, null=True) longitude = models.FloatField(blank=True, null=True) location = models.PointField(blank=True, null=True) regions = models.ManyToManyField(Region, blank=True, related_name='sites', through='SiteRegion') dioceses = models.ManyToManyField(Diocese, blank=True, related_name='sites', through='SiteDiocese') dedications = models.ManyToManyField(Dedication, blank=True, related_name='sites', through='SiteDedication') settlement = models.ForeignKey(Settlement, blank=True, null=True, help_text='Type of building/monument', related_name='sites') description = mezzanine.core.fields.RichTextField(blank=True) history = mezzanine.core.fields.RichTextField(blank=True) comments = mezzanine.core.fields.RichTextField(blank=True) fieldworker_notes = models.TextField( blank=True, help_text= 'Admin use only; not published. Please add date and initials to all comments.' ) editor_notes = models.TextField( blank=True, help_text= 'Admin use only; not published. Please add date and initials to all comments.' ) glossary_terms = models.ManyToManyField(GlossaryTerm, blank=True, editable=False, related_name='sites') objects = models.GeoManager() published = PublishedSiteManager() bng_proj = pyproj.Proj(init='epsg:27700') ing_proj = pyproj.Proj(init='epsg:29901') geo_proj = pyproj.Proj(init='epsg:4326') title = models.CharField(blank=True, null=True, max_length=256) class Meta: app_label = 'sculpture' ordering = ['name'] def __str__(self): return self.name def _convert_refs(self): for field in ('description', 'history', 'comments'): content = sculpture.utils.convert_refs_to_links( getattr(self, field)) setattr(self, field, content) def _derive_gis_data(self): """Sets latitude, longitude and point fields, derived from grid_reference field value.""" result = re.search(sculpture.constants.GRID_PATTERN, self.grid_reference) if result is not None: if result.group('bng'): proj = self.bng_proj grid_group = 2 easting_group = 3 northing_group = 4 else: proj = self.ing_proj grid_group = 6 easting_group = 7 northing_group = 8 easting, northing = self._expand_grid_reference( result.group(grid_group), result.group(easting_group), result.group(northing_group)) longitude, latitude = pyproj.transform(proj, self.geo_proj, easting, northing) self.location = Point(longitude, latitude, srid=4326) self.latitude = latitude self.longitude = longitude def _expand_grid_reference(self, grid, easting, northing): """Returns full easting and northing values (in metres) by joining `easting` and `northing` to `grid`. :param grid: BNG or ING grid label :type grid: `str` :param easting: easting value relative to `grid` :type easting: `str` :param northing: northing value relative to `grid` :type northing: `str` :rtype: `tuple` of `int` """ base = sculpture.constants.NG_TILES.get(grid, '') # easting and northing may be a variable number of digits, so # convert them to the appropriate number of metres. 23 = # 23000m, 234 = 23400m, etc. easting = int(easting) * 10**(5 - len(easting)) northing = int(northing) * 10**(5 - len(northing)) # Each grid is 100km x 100km. full_easting = base[0] * 100000 + easting full_northing = base[1] * 100000 + northing return full_easting, full_northing def feature_sets(self): """Returns the FeatureSets that are related to this Site. Used to generate search facets, since relations of relations are not handled by Haystack, apparently. """ from .feature_set import FeatureSet return FeatureSet.objects.filter_by_site(self.id) @models.permalink def get_absolute_url(self): return ('site_display', (), {'site_id': str(self.id)}) def get_dedication_by_period(self, period_name): """Returns this site's dedications for the period `period_name`. :rtype: `QuerySet` """ try: period = Period.objects.get(name=period_name) except Period.DoesNotExist: return Dedication.objects.none() return self.sitededication_set.filter(period=period) def get_dedication_now(self): """Returns this site's dedications associated with the period "now". :rtype: `QuerySet` """ return self.get_dedication_by_period(sculpture.constants.DATE_NOW) def get_dedications(self): """Returns this site's dedications for all periods, grouped by period. :rtype: `dict` """ dedications = {} for period in (sculpture.constants.DATE_MEDIEVAL, sculpture.constants.DATE_NOW): dedications[period] = self.get_dedication_by_period(period) return dedications def get_features(self): """Returns this site's features organised into nested SortedDicts by feature set.""" features = SortedDict() features['features'] = [] # Iterate over Features, which are in the desired final order, # and group them according to the FeatureSet hierarchy. This # grouping removes duplication (ie, each FeatureSet will occur # only once), which may change the specified order. The first # occurrence of a FeatureSet determines its placement in the # order. for feature in self.features.all(): fs_rank = features feature_sets = feature.get_feature_set_hierarchy() i = 1 for feature_set in feature_sets: container = SortedDict() fs_rank = fs_rank.setdefault(feature_set, container) if 'features' not in fs_rank: fs_rank['features'] = [] i += 1 fs_rank['features'].append(feature) return features def get_images(self): from .feature_image import FeatureImage return list(self.images.all()) + list( FeatureImage.objects.filter(feature__site=self)) def get_images_features_first(self): from .feature_image import FeatureImage return list(FeatureImage.objects.filter(feature__site=self)) + list( self.images.all()) def get_region_by_period(self, period_name): """Returns this site's regions of period `period_name`. :param period_name: name of period :type period_name: `str` :rtype: `QuerySet` """ try: period = Period.objects.get(name=period_name) except Period.DoesNotExist: return Region.objects.none() return self.siteregion_set.filter(period=period) def get_region_traditional(self): """Returns this site's regions associated with the "traditional" period. :rtype: `QuerySet` """ counties = self.get_region_by_period( sculpture.constants.DATE_TRADITIONAL) return counties def get_tags(self, user): """Returns a QuerySet of SiteTags associated with this site and belonging to `user`. :param user: user profile :type user: `UserProfile` """ return self.tags.filter(user=user) def get_title(self): """Returns the title of the site. The title is composed of the dedication, name and region. :rtype: `unicode` """ region = sculpture.utils.get_first_name(self.get_region_traditional(), 'region') dedication = sculpture.utils.get_first_name(self.get_dedication_now(), 'dedication') title = ', '.join([dedication, self.name, region]) return title.strip(' ,') def _link_glossary(self): """Recreates links to the glossary within the rich text fields for this site. Changes the content of some fields on self, but does not save self. Changes, and saves, the Features of this site. """ terms = GlossaryTerm.objects.get_regexps() all_used_terms = [] for field in ('description', 'history', 'comments'): text, used_terms = sculpture.utils.add_glossary_terms( getattr(self, field), terms) setattr(self, field, text) all_used_terms.extend(used_terms) for feature in self.features.all(): all_used_terms.extend(feature.link_glossary(terms)) feature.save() return list(set(all_used_terms)) def save(self, *args, **kwargs): # Relink glossary terms. glossary_set = False try: glossary_terms = self._link_glossary() glossary_set = True except: pass self._convert_refs() self._derive_gis_data() self.title = self.get_title() super(Site, self).save(*args, **kwargs) if glossary_set: # Set the glossary terms used in this site report. self.glossary_terms.clear() self.glossary_terms.add(*glossary_terms)
class Job(GeoQBase, Assignment): """ Mid-level organizational object. """ GRID_SERVICE_VALUES = ['usng', 'mgrs'] GRID_SERVICE_CHOICES = [(choice, choice) for choice in GRID_SERVICE_VALUES] EDITORS = ['geoq','osm'] EDITOR_CHOICES = [(choice, choice) for choice in EDITORS] analysts = models.ManyToManyField(User, blank=True, null=True, related_name="analysts") teams = models.ManyToManyField(Group, blank=True, null=True, related_name="teams") reviewers = models.ManyToManyField(User, blank=True, null=True, related_name="reviewers") progress = models.SmallIntegerField(max_length=2, blank=True, null=True) project = models.ForeignKey(Project, related_name="project") grid = models.CharField(max_length=5, choices=GRID_SERVICE_CHOICES, default=GRID_SERVICE_VALUES[0], help_text='Select usng for Jobs inside the US, otherwise use mgrs') tags = models.CharField(max_length=50, blank=True, null=True, help_text='Useful tags to search social media with') editor = models.CharField(max_length=20, help_text='Editor to be used for creating features', choices=EDITOR_CHOICES, default=EDITOR_CHOICES[0]) editable_layer = models.ForeignKey( 'maps.EditableMapLayer', blank=True, null=True) map = models.ForeignKey('maps.Map', blank=True, null=True) feature_types = models.ManyToManyField('maps.FeatureType', blank=True, null=True) required_courses = models.ManyToManyField(Training, blank=True, null=True, help_text="Courses that must be passed to open these cells") class Meta: permissions = ( ) ordering = ('-created_at',) def get_absolute_url(self): return reverse('job-detail', args=[self.id]) def get_update_url(self): return reverse('job-update', args=[self.id]) def aois_geometry(self): return self.aois.all().collect() def aois_envelope(self): """ Returns the envelope of related AOIs geometry. """ return getattr(self.aois.all().collect(), 'envelope', None) def aoi_count(self): return self.aois.count() @property def aoi_counts_html(self): count = defaultdict(int) for cell in AOI.objects.filter(job__id=self.id): count[cell.status] += 1 return str(', '.join("%s: <b>%r</b>" % (key, val) for (key, val) in count.iteritems())) @property def user_count(self): return self.analysts.count() @property def base_layer(self): if self.map is not None and self.map.layers is not None: layers = sorted([l for l in self.map.layers if l.is_base_layer], key = lambda x: x.stack_order) if len(layers) > 0: layer = layers[0].layer return [layer.name, layer.url, layer.attribution] else: return [] else: return [] def features_table_html(self): counts = {} for feature_item in self.feature_set.all(): status = str(feature_item.status) featuretype = str(feature_item.template.name) if not featuretype in counts: counts[featuretype] = {} if not status in counts[featuretype]: counts[featuretype][status] = 0 counts[featuretype][status] += 1 #TODO: Also return this as JSON if len(counts): output = "<table class='job_feature_list'>" header = "<th><i>Feature Counts</i></th>" for (featuretype, status_obj) in counts.iteritems(): header = header + "<th><b>" + cgi.escape(featuretype) + "</b></th>" output += "<tr>" + header + "</tr>" for status in STATUS_VALUES_LIST: status = str(status) row = "<td><b>" + status + "</b></td>" for (featuretype, status_obj) in counts.iteritems(): if status in status_obj: val = status_obj[status] else: val = 0 row += "<td>" + cgi.escape(str(val)) + "</td>" output += "<tr>" + row + "</tr>" output += "</table>" else: output = "" return output def complete(self): """ Returns the completed AOIs. """ return self.aois.filter(status='Completed') def in_work(self): """ Returns the AOIs currently being worked on or in review """ return self.aois.filter(Q(status='In work') | Q(status='Awaiting review') | Q(status='In review')) def in_work_count(self): return self.in_work().count() def complete_count(self): return self.complete().count() def complete_percent(self): if self.aois.count() > 0: return round(float(self.complete().count() * 100) / self.aois.count(), 2) return 0.0 def total_count(self): return self.aois.count() def geoJSON(self, as_json=True): """ Returns geoJSON of the feature. """ geojson = SortedDict() geojson["type"] = "FeatureCollection" geojson["features"] = [json.loads(aoi.geoJSON()) for aoi in self.aois.all()] return clean_dumps(geojson) if as_json else geojson def features_geoJSON(self, as_json=True, using_style_template=True): geojson = SortedDict() geojson["type"] = "FeatureCollection" geojson["properties"] = dict(id=self.id) geojson["features"] = [n.geoJSON(as_json=False, using_style_template=using_style_template) for n in self.feature_set.all()] return clean_dumps(geojson, indent=2) if as_json else geojson def grid_geoJSON(self, as_json=True): """ Return geoJSON of grid for export """ geojson = SortedDict() geojson["type"] = "FeatureCollection" geojson["features"] = [json.loads(aoi.grid_geoJSON()) for aoi in self.aois.all()] return clean_dumps(geojson) if as_json else geojson def base_layer_object(self): """ create base layer object that can override leaflet base OSM map """ obj = {} if len(self.base_layer) > 0: obj["layers"] = [self.base_layer] return obj
class Polygon(models.Model): """ Polygon represent geo object, and could refer to collection of lower polygons """ # Polygon as polygon polygon_id = models.CharField(max_length=50, primary_key=True) organizations = models.ManyToManyField(Organization, blank=True) shape = models.PolygonField(null=True, blank=True) centroid = models.PointField(null=True, blank=True) address = models.CharField(max_length=800, null=True, blank=True) layer = models.ForeignKey('self', blank=True, null=True) # Polygon as layer country = 0 region = 1 area = 2 district = 3 building = 4 LEVEL = ( (country, _("Root polygon")), (region, _("Regions of country")), (area, _("Subregions or big sities")), (district, _("Towns or districts of city")), (building, _("Houses")) ) level = models.IntegerField(choices=LEVEL, default=building) is_verified = models.BooleanField(default=True) updated = models.DateTimeField(auto_now=True) # outdated fields is_default = models.BooleanField(default=False) zoom = models.IntegerField(blank=True, null=True) claims = models.IntegerField(default=0) objects = models.GeoManager() @property def total_claims(self): claims = 0 if self.level == self.building: claims += sum([x.claims for x in self.organizations.all()]) else: cached = cache.get('claims_for::%s' % self.polygon_id) if cached is not None: claims = cached else: childs = self.polygon_set.all() for child in childs: claims += child.total_claims cache.set('claims_for::%s' % self.polygon_id, claims) return claims def color_spot(self, value, max_value): if max_value: percent = value * 100 / max_value else: percent = 0 if percent <= 20: return 'green' elif percent <= 70: return 'yellow' else: return 'red' @property def get_color(self): cached = cache.get('color_for::%s' % self.polygon_id) if cached is not None: color = cached else: if self.layer: brothers = self.layer.polygon_set.all() max_claims_value = max([x.total_claims for x in brothers]) else: max_claims_value = 0 color = self.color_spot( self.total_claims, max_claims_value)\ if self.total_claims else 'grey' cache.set('color_for::%s' % self.polygon_id, color) return color # def orgs_count(self): # return self.organizations.all().count() def first_organization(self): orgs = self.organizations.all() if orgs: return orgs[0] else: return None def __str__(self): return 'Polygon ' + str(self.polygon_id) def polygon_to_json(self, shape=True): # reverse coordinates for manualy adding polgygons if shape and self.shape: geometry = json.loads(self.shape.json) [x.reverse() for x in geometry["coordinates"][0]] else: geometry = None centroid = list(self.centroid.coords) centroid.reverse() responce = { "type": "Feature", "properties": { "ID": self.polygon_id, "centroid": centroid, 'address': self.address, 'parent_id': self.layer.polygon_id if self.layer else None, 'level': self.level, # "polygon_claims": self.claims }, "geometry": geometry } if self.level == self.building: orgs = [] polygon_claims = 0 for org in self.organizations.all(): org_claims = org.claims polygon_claims += org_claims orgs.append({'id': org.id, 'name': org.name, 'claims_count': org_claims, # 'claim_types': org.claim_types() 'org_type_id': org.org_type.type_id }) responce["properties"]["organizations"] = orgs responce["properties"]["polygon_claims"] = polygon_claims else: responce["properties"]["polygon_claims"] = self.total_claims # print(responce) return responce
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")