class SearchPoi(DefaultFields): displayed_name = models.CharField(max_length=1000) bodies = models.ManyToManyField(Body, blank=True) osm_id = models.BigIntegerField(null=True, blank=True) osm_amenity = models.CharField(null=True, max_length=1000) geometry = GeometryField(null=True) exclude_from_search = models.BooleanField(default=False)
class Data(models.Model): geom = GeometryField(default=None) sidewalks = models.TextField(default=None) correct = models.BooleanField(default=True) date = models.DateTimeField() user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) intersection = models.ForeignKey(Intersection, on_delete=models.CASCADE)
class SearchPoi(DefaultFields): displayed_name = models.CharField(max_length=1000) body = models.ForeignKey(Body, blank=True, null=True, on_delete=models.CASCADE) osm_id = models.BigIntegerField(null=True, blank=True) osm_amenity = models.CharField(null=True, max_length=1000) geometry = GeometryField(null=True) exclude_from_search = models.BooleanField(default=False)
class Location(DefaultFields): description = models.TextField(null=True, blank=True) # Unique field to avoid duplicating addresses through location extraction # The 767 is a limitation of InnoDB for indexed columns # See https://stackoverflow.com/q/1827063/3549270 search_str = models.CharField(max_length=767, null=True, blank=True, unique=True) street_address = models.CharField(max_length=512, null=True, blank=True) postal_code = models.CharField(max_length=512, null=True, blank=True) locality = models.CharField(max_length=512, null=True, blank=True) room = models.CharField(max_length=512, null=True, blank=True) is_official = models.BooleanField() osm_id = models.BigIntegerField(null=True, blank=True) geometry = GeometryField(default=None) def __str__(self): return self.description or _("Unknown") def short(self) -> str: """Tries to return a short description of the adress, with a fallback to the long one""" if self.street_address and self.room: return "{}, {}".format(self.street_address, self.room) else: return self.description def for_maps(self) -> str: """Tries to build a good search string for google maps / open street map""" if self.street_address: if self.postal_code and self.locality: return "{}, {} {}".format(self.street_address, self.postal_code, self.locality) else: return self.street_address else: return self.description # noinspection PyUnresolvedReferences def coordinates(self) -> Optional[Dict[str, Any]]: if self.geometry and self.geometry["type"] == "Point": return { "lat": self.geometry["coordinates"][1], "lon": self.geometry["coordinates"][0], } else: return None
class Location(DefaultFields): description = models.TextField(null=True, blank=True) street_address = models.CharField(max_length=512, null=True, blank=True) postal_code = models.CharField(max_length=512, null=True, blank=True) locality = models.CharField(max_length=512, null=True, blank=True) room = models.CharField(max_length=512, null=True, blank=True) is_official = models.BooleanField() osm_id = models.BigIntegerField(null=True, blank=True) geometry = GeometryField(default=None) def __str__(self): return self.description or _("Unknown") def short(self) -> str: """ Tries to return a short description of the adress, with a fallback to the long one """ if self.street_address and self.room: return "{}, {}".format(self.street_address, self.room) else: return self.description def for_maps(self) -> str: """ Tries to build a good search string for google maps / open street map""" if self.street_address: if self.postal_code and self.locality: return "{}, {} {}".format(self.street_address, self.postal_code, self.locality) else: return self.street_address else: return self.description # noinspection PyUnresolvedReferences def coordinates(self) -> Optional[Dict[str, Any]]: if self.geometry and self.geometry["type"] == "Point": return { "lat": self.geometry["coordinates"][1], "lon": self.geometry["coordinates"][0], } else: return None
class Location(DefaultFields): name = models.CharField(max_length=200) short_name = models.CharField(max_length=50) description = models.TextField(null=True, blank=True) # Need to work around a cyclic import here bodies = models.ManyToManyField("mainapp.Body", blank=True) is_official = models.BooleanField() osm_id = models.BigIntegerField(null=True, blank=True) geometry = GeometryField(default=None) def __str__(self): return self.short_name def coordinates(self): if self.geometry and self.geometry['type'] == 'Point': return { "lat": self.geometry['coordinates'][1], "lon": self.geometry['coordinates'][0], } else: return None
class Location(DefaultFields): short_description = models.TextField(null=True, blank=True) description = models.TextField(null=True, blank=True) # Need to work around a cyclic import here bodies = models.ManyToManyField("mainapp.Body", blank=True) is_official = models.BooleanField() osm_id = models.BigIntegerField(null=True, blank=True) geometry = GeometryField(default=None) streetAddress = models.CharField(max_length=512, null=True, blank=True) room = models.CharField(max_length=512, null=True, blank=True) postalCode = models.CharField(max_length=512, null=True, blank=True) locality = models.CharField(max_length=512, null=True, blank=True) def __str__(self): return self.short_description or self.description or _("Unknown") def coordinates(self): if self.geometry and self.geometry['type'] == 'Point': return { "lat": self.geometry['coordinates'][1], "lon": self.geometry['coordinates'][0], } else: return None
class BaseComment(BaseModel): parent_field = None # Required for factories and API parent_model = None # Required for factories and API geojson = GeometryField(blank=True, null=True, verbose_name=_('location')) authorization_code = models.CharField(verbose_name=_('authorization code'), max_length=32, blank=True) author_name = models.CharField(verbose_name=_('author name'), max_length=255, blank=True, null=True) plugin_identifier = models.CharField(verbose_name=_('plugin identifier'), blank=True, max_length=255) plugin_data = models.TextField(verbose_name=_('plugin data'), blank=True) label = models.ForeignKey("Label", verbose_name=_('label'), blank=True, null=True) language_code = models.CharField(verbose_name=_('language code'), blank=True, max_length=15) n_votes = models.IntegerField( verbose_name=_('vote count'), help_text=_('number of votes given to this comment'), default=0, editable=False ) n_unregistered_votes = models.IntegerField( verbose_name=_('unregistered vote count'), help_text=_('number of unregistered votes'), default=0, editable=False ) voters = models.ManyToManyField( settings.AUTH_USER_MODEL, verbose_name=_('voters'), help_text=_('users who voted for this comment'), related_name="voted_%(app_label)s_%(class)s", blank=True ) class Meta: abstract = True @property def parent(self): """ :rtype: Commentable|None """ return getattr(self, self.parent_field, None) @property def parent_id(self): """ :rtype: int|str|None """ return getattr(self, "%s_id" % self.parent_field, None) def _detect_lang(self): try: candidates = detect_langs(self.content.lower()) for candidate in candidates: if candidate.lang in [lang['code'] for lang in settings.PARLER_LANGUAGES[None]]: if candidate.prob > settings.DETECT_LANGS_MIN_PROBA: self.language_code = candidate.lang break except LangDetectException: pass def save(self, *args, **kwargs): if not (self.plugin_data or self.content or self.label): raise ValueError("Comments must have either plugin data, textual content or label") if not self.author_name and self.created_by_id: self.author_name = (self.created_by.get_display_name() or None) if not self.language_code and self.content: self._detect_lang() return super(BaseComment, self).save(*args, **kwargs) def recache_n_votes(self): n_votes = self.voters.all().count() + self.n_unregistered_votes if n_votes != self.n_votes: self.n_votes = n_votes self.save(update_fields=("n_votes", "n_unregistered_votes")) def recache_parent_n_comments(self): if self.parent_id: # pragma: no branch self.parent.recache_n_comments() def can_edit(self, request): """ Whether the given request (HTTP or DRF) is allowed to edit this Comment. """ is_authenticated = request.user.is_authenticated() if is_authenticated and self.created_by == request.user: # also make sure the hearing is still commentable try: self.parent.check_commenting(request) except ValidationError: return False return True return False
class GeoDirectoryMixin(models.Model): """ A model with a corresponding entry in Terralego. The entry will be updated at every save. You can pass `terralego_commit` at False to force not updating the entry. This model is designed to be one-way only. This means that it won't update from terralego automatically. If you update the entry from somewhere else, you will have to call `_update_from_terralego_entry` manually. """ terralego_id = models.UUIDField(verbose_name=_('Terralego id'), editable=False, null=True) terralego_last_update = models.DateTimeField(_('Terralego last update'), editable=False, null=True) terralego_geometry = GeometryField(_('Terralego geometry field'), blank=True, null=True) terralego_tags = models.TextField(_('Terralego tags'), blank=True, null=True) # JSON list of tags class Meta: abstract = True # Save/update handling def update_from_terralego_data(self, data): """ Set self.geometry and self.tags with the values in data and cache it. :param data: the geojson representing the entry """ self.terralego_id = data['id'] self.terralego_last_update = timezone.now() self.terralego_geometry = data['geometry'] self.terralego_tags = json.dumps(data['properties']['tags']) def _update_tags_with_model(self, tags): model_path = '{0}.{1}'.format(self._meta.app_label, self._meta.object_name) if tags is None: tags = [] if model_path not in tags: # Add the model_path to the tags tags.insert(0, model_path) elif tags[0] != model_path: # The model_path is already there but not in first place tags.remove(model_path) tags.insert(0, model_path) return tags def update_from_terralego_entry(self): """ Get the terralego entry related to self.terralego_id and update the instance tags and geometry. """ if self.terralego_id is not None and conf.TERRALEGO.get('ENABLED', True): data = geodirectory.get_entry(self.terralego_id) self.update_from_terralego_data(data) def save_to_terralego(self): """ Create or update the entry in terralego, adding the model_path to the tags if needed. """ tags = self.terralego_tags and json.loads(self.terralego_tags) or None tags = self._update_tags_with_model(tags) self.terralego_tags = json.dumps(tags) # Save tags in case of error before the update_from_terralego_data if self.terralego_id is None: data = geodirectory.create_entry(self.terralego_geometry, tags) else: data = geodirectory.update_entry(self.terralego_id, self.terralego_geometry, tags) self.update_from_terralego_data(data) def delete_from_terralego(self, set_id_null=True): """ Delete the entry in terralego """ geodirectory.delete_entry(self.terralego_id) if set_id_null: self.terralego_id = None self.save(terralego_commit=False) def delete(self, *args, **kwargs): if self.terralego_id: try: self.delete_from_terralego(set_id_null=False) except RequestException as e: logger.error('Error while deleting from terralego: {0}'.format(e)) return super(GeoDirectoryMixin, self).delete(*args, **kwargs) def save(self, *args, **kwargs): terralego_commit = kwargs.pop('terralego_commit', True) if terralego_commit and self.terralego_geometry is not None and conf.TERRALEGO.get('ENABLED', True): try: self.save_to_terralego() except RequestException as e: logger.error('Error while saving to terralego: {0}'.format(e)) return super(GeoDirectoryMixin, self).save(*args, **kwargs) # Geodirectory methods def closest(self, tags=None): """ Get the closest entry of this entry. :param tags: Optional. A list of tags to filter the entries on which the request is made. :return: An instance of GeoDirectoryMixin if the entry is one, or a dict describing the entry. """ try: entry = geodirectory.closest(self.terralego_id, tags) except RequestException as e: return logger.error('Error while getting closest: {0}'.format(e)) return convert_geodirectory_entry_to_model_instance(entry)
class Hearing(StringIdBaseModel, TranslatableModel): open_at = models.DateTimeField(verbose_name=_('opening time'), default=timezone.now) close_at = models.DateTimeField(verbose_name=_('closing time'), default=timezone.now) force_closed = models.BooleanField(verbose_name=_('force hearing closed'), default=False) translations = TranslatedFields( title=models.CharField(verbose_name=_('title'), max_length=255), borough=models.CharField(verbose_name=_('borough'), blank=True, default='', max_length=200), ) servicemap_url = models.CharField(verbose_name=_('service map URL'), default='', max_length=255, blank=True) geojson = GeometryField(blank=True, null=True, verbose_name=_('area')) organization = models.ForeignKey(Organization, verbose_name=_('organization'), related_name="hearings", blank=True, null=True) labels = models.ManyToManyField("Label", verbose_name=_('labels'), blank=True) followers = models.ManyToManyField( settings.AUTH_USER_MODEL, verbose_name=_('followers'), help_text=_('users who follow this hearing'), related_name='followed_hearings', blank=True, editable=False) slug = AutoSlugField( verbose_name=_('slug'), populate_from='title', editable=True, unique=True, blank=True, help_text=_( 'You may leave this empty to automatically generate a slug')) n_comments = models.IntegerField(verbose_name=_('number of comments'), blank=True, default=0, editable=False) contact_persons = models.ManyToManyField(ContactPerson, verbose_name=_('contact persons'), related_name='hearings') objects = BaseModelManager.from_queryset(HearingQueryset)() original_manager = models.Manager() class Meta: verbose_name = _('hearing') verbose_name_plural = _('hearings') def __str__(self): return (self.title or self.id) @property def closed(self): return self.force_closed or not (self.open_at <= now() <= self.close_at) def check_commenting(self, request): if self.closed: raise ValidationError( _("%s is closed and does not allow comments anymore") % self, code="hearing_closed") def check_voting(self, request): if self.closed: raise ValidationError( _("%s is closed and does not allow voting anymore") % self, code="hearing_closed") @property def preview_code(self): if not self.pk: return None return get_hmac_b64_encoded(self.pk) @property def preview_url(self): if not (self.preview_code and hasattr(settings, 'DEMOCRACY_UI_BASE_URL')): return '' url = urljoin(settings.DEMOCRACY_UI_BASE_URL, '/hearing/%s/?preview=%s' % (self.pk, self.preview_code)) return format_html('<a href="%s">%s</a>' % (url, url)) def save(self, *args, **kwargs): slug_field = self._meta.get_field('slug') # we need to manually use autoslug utils here with ModelManager, because automatic slug populating # uses our default manager, which can lead to a slug collision between this and a deleted hearing self.slug = generate_unique_slug(slug_field, self, self.slug, Hearing.original_manager) super().save(*args, **kwargs) def recache_n_comments(self): new_n_comments = (self.sections.all().aggregate( Sum('n_comments')).get('n_comments__sum') or 0) if new_n_comments != self.n_comments: self.n_comments = new_n_comments self.save(update_fields=("n_comments", )) def get_main_section(self): try: return self.sections.get(type__identifier=InitialSectionType.MAIN) except ObjectDoesNotExist: return None def is_visible_for(self, user): if self.published and self.open_at < now(): return True if not user.is_authenticated(): return False if user.is_superuser: return True user_organization = user.get_default_organization() if not (user_organization and self.organization): return False return user_organization == self.organization
class Intersection(models.Model): geom = GeometryField() rank = models.IntegerField(default=0)
class Hearing(Commentable, StringIdBaseModel): open_at = models.DateTimeField(verbose_name=_('opening time'), default=timezone.now) close_at = models.DateTimeField(verbose_name=_('closing time'), default=timezone.now) force_closed = models.BooleanField(verbose_name=_('force hearing closed'), default=False) title = models.CharField(verbose_name=_('title'), max_length=255) abstract = models.TextField(verbose_name=_('abstract'), blank=True, default='') borough = models.CharField(verbose_name=_('borough'), blank=True, default='', max_length=200) servicemap_url = models.CharField(verbose_name=_('service map URL'), default='', max_length=255, blank=True) geojson = GeometryField(blank=True, null=True, verbose_name=_('area')) labels = models.ManyToManyField("Label", verbose_name=_('labels'), blank=True) followers = models.ManyToManyField( settings.AUTH_USER_MODEL, verbose_name=_('followers'), help_text=_('users who follow this hearing'), related_name='followed_hearings', blank=True, editable=False) class Meta: verbose_name = _('hearing') verbose_name_plural = _('hearings') def __str__(self): return (self.title or self.id) @property def closed(self): return self.force_closed or not (self.open_at <= now() <= self.close_at) def check_commenting(self, request): if self.closed: raise ValidationError( _("%s is closed and does not allow comments anymore") % self, code="hearing_closed") super().check_commenting(request) @property def preview_code(self): if not self.pk: return None return get_hmac_b64_encoded(self.pk) @property def preview_url(self): if not (self.preview_code and hasattr(settings, 'DEMOCRACY_UI_BASE_URL')): return '' url = urljoin(settings.DEMOCRACY_UI_BASE_URL, '/hearing/%s/?preview=%s' % (self.pk, self.preview_code)) return format_html('<a href="%s">%s</a>' % (url, url))