Exemplo n.º 1
0
class GeometryMixin(models.Model):
    geometry = MultiPolygonField(verbose_name=_('Country border geometry'), null=True, blank=True)
    geometry_simplified = MultiPolygonField(verbose_name=_('Simplified Geometry'), null=True, blank=True)

    class Meta:
        abstract = True

    @classmethod
    def to_multipolygon(cls, geos_geom: [MultiPolygon, Polygon]) -> MultiPolygon:
        return MultiPolygon(geos_geom) if isinstance(geos_geom, Polygon) else geos_geom

    @classmethod
    def optimize_geometry(cls, geometry: [GEOSGeometry]) -> [MultiPolygon]:
        if geometry is None:
            return geometry

        # magic numbers
        tolerance = 0.03
        tolerance_divider = 4
        max_attempts = 5

        for _i in range(max_attempts):
            geometry_simplified = geometry.simplify(tolerance=tolerance)
            if not geometry_simplified.empty:
                return cls.to_multipolygon(geometry_simplified)

            tolerance = tolerance / tolerance_divider

        return geometry

    def save(self, *args, **kwargs):
        self.geometry_simplified = self.optimize_geometry(self.geometry)

        super().save(*args, **kwargs)
Exemplo n.º 2
0
class Province(models.Model):
    name = models.CharField(max_length=50)
    code = models.IntegerField(null=True, blank=True)
    boundary = MultiPolygonField(null=True, blank=True)

    def __str__(self):
        return self.name
Exemplo n.º 3
0
class MultiAreaSource(models.Model):
    """A multi-polygon geometry demarcating the area of a Project or Real Estate boundaries (where applicable)"""

    # IDENTIFICATION

    name = models.CharField(max_length=255)

    # LINKS

    asset = models.ForeignKey('portfolio.ProjectAsset', null=True, blank=True, on_delete=models.CASCADE)

    # ATTRIBUTES
    location = MultiPolygonField()

    #
    # BOOKKEEPING FIELDS
    #
    creation_date = models.DateTimeField(auto_now_add=True)
    last_change_date = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('portfolio:MultiAreaSource_edit', kwargs={'pk': self.pk})

    class Meta:
        verbose_name = "Multi Area Source"
        verbose_name_plural = "Multi Area Sources"
class Shape(Model):  # Should this just be called Border?
    """
    A Shape is a region polygon associated with a State from a start_date to an end_date.
    This should accomodate States with discontiguous territories and existences.
    A Shape may optionally have Events attached to its start_date and end_date.
    """
    state = ForeignKey(State, on_delete=CASCADE)
    # TODO: Figure out the right way to store the shapefile here. PolygonField feels like the right approach, but is it?
    # Progress! It will probably involve LayerMapping (https://docs.djangoproject.com/en/2.0/ref/contrib/gis/tutorial/#importing-spatial-data)
    # TODO: Add this field once this app is dockerized and Postgres + PostGIS are working
    shape = MultiPolygonField(blank=True, null=True)
    source = TextField(
        help_text=
        'Citation for where you found this map. Guide: http://rmit.libguides.com/harvardvisual/maps.'
    )
    start_date = DateField(help_text='When this border takes effect.')
    start_event = ForeignKey(
        'Event',
        on_delete=SET_NULL,
        null=True,
        blank=True,
        related_name='new_borders',
        help_text=
        "If this field is set, this event's date overwrites the start_date")
    end_date = DateField(help_text='When this border ceases to exist.')
    end_event = ForeignKey(
        'Event',
        on_delete=SET_NULL,
        null=True,
        blank=True,
        related_name='prior_borders',
        help_text=
        "If this field is set, this event's date overwrites the end_date")

    def get_bordering_shapes(self, date=None):
        """
        TODO: This should return the list of shapes that border it @date.
        If date is None, it should return all shapes thar border it throughout its existence.
        I imagine this will require a custom PostGIS query. Or maybe just the __touches GeoDjango query.
        """
        Shape.objects.filter(
            start_date__lte=date,
            end_date__gte=date,
            shape__touches=self.shape) if date else Shape.objects.filter(
                shape__touches=self.shape)

    def clean(self):
        """
        Sets the start/end_date to the date of the associated events?
        This will need to be called again if the event's date ever changes.
        """
        if self.start_event:
            self.start_date = self.start_event.date
        if self.end_event:
            self.end_date = self.end_event.date

    def __str__(self):
        return f'{self.state}: {self.start_date} to {self.end_date}'
Exemplo n.º 5
0
class CategoryDbca(models.Model):
    '''
    This model is used for defining the categories
    '''
    wkb_geometry = MultiPolygonField(srid=4326, blank=True, null=True)
    category_name = models.CharField(max_length=20, blank=True, null=True)

    class Meta:
        app_label = 'disturbance'
Exemplo n.º 6
0
class RegionGeomMA(models.Model):
    # note: these are only the fields we care about
    gid = models.IntegerField(primary_key=True)
    town = models.TextField()
    geom = MultiPolygonField(geography=True)

    class Meta:
        db_table = "official_region_geom_ma"
        managed = False
Exemplo n.º 7
0
class District(models.Model):
    name = models.CharField(max_length=50)
    code = models.IntegerField(null=True, blank=True)
    province = models.ForeignKey('Province',
                                 related_name='district',
                                 on_delete=models.CASCADE)
    boundary = MultiPolygonField(null=True, blank=True)

    def __str__(self):
        return self.name
Exemplo n.º 8
0
class WaCoast(models.Model):
    '''
    This model is used for validating if the apiary site is in the valid area
    '''
    wkb_geometry = MultiPolygonField(srid=4326, blank=True, null=True)
    type = models.CharField(max_length=30, blank=True, null=True)
    source = models.CharField(max_length=50, blank=True, null=True)
    smoothed = models.BooleanField(default=False)

    class Meta:
        app_label = 'disturbance'
Exemplo n.º 9
0
class RegionDbca(models.Model):
    wkb_geometry = MultiPolygonField(srid=4326, blank=True, null=True)
    region_name = models.CharField(max_length=200, blank=True, null=True)
    office = models.CharField(max_length=200, blank=True, null=True)
    object_id = models.PositiveIntegerField(blank=True, null=True)

    class Meta:
        ordering = [
            'object_id',
        ]
        app_label = 'disturbance'
Exemplo n.º 10
0
class UsStates(Model):
    state = CharField(max_length=2)
    name = CharField(max_length=24)
    fips = CharField(max_length=2)
    lon = FloatField()
    lat = FloatField()
    geom = MultiPolygonField(srid=4326)

    # Returns the string representation of the model.
    def __str__(self):  # __unicode__ on Python 2
        return self.name
Exemplo n.º 11
0
class Locality(models.Model):
    name = models.CharField(max_length=128,
                            db_column='name',
                            help_text=_('Name of locality'),
                            blank=False)
    description = models.TextField(blank=True,
                                   db_column='description',
                                   verbose_name=_('description'),
                                   help_text=_('Description of the locality'))
    locality_type = models.ForeignKey('LocalityType',
                                      on_delete=models.PROTECT,
                                      db_column='locality_type_id',
                                      verbose_name=_('locality type'),
                                      help_text=_('Type of locality'),
                                      blank=False,
                                      null=False)
    geometry = MultiPolygonField(blank=True,
                                 db_column='geometry',
                                 verbose_name=_('geometry'),
                                 help_text=_('Geometry of locality'),
                                 spatial_index=True)
    metadata = JSONField(db_column='metadata',
                         verbose_name=_('metadata'),
                         help_text=_('Metadata associated to locality'),
                         default=empty_JSON,
                         blank=True,
                         null=True)

    is_part_of = models.ManyToManyField("self", symmetrical=False, blank=True)

    class Meta:
        verbose_name = _('Locality')
        verbose_name_plural = _('Localities')

        ordering = ['-name']

    def clean(self, *args, **kwargs):
        try:
            self.locality_type.validate_metadata(self.metadata)
        except ValidationError as error:
            raise ValidationError({'metadata': error})

        super().clean(*args, **kwargs)

    def validate_point(self, point):
        if not self.geometry.contains(point):
            msg = _("Point is not contained within the locality's geometry")
            raise ValidationError(msg)

    def __str__(self):
        return '{locality_type}: {name}'.format(
            locality_type=self.locality_type, name=self.name)
Exemplo n.º 12
0
class CensusBlocksResults(models.Model):
    """ Stores geometries and results from the Census blocks shapefile.

    """
    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    geom = MultiPolygonField(srid=4326, blank=True, null=True)
    job = models.ForeignKey(AnalysisJob,
                            related_name='census_block_results',
                            on_delete=models.CASCADE,
                            null=True,
                            blank=True)

    overall_score = models.FloatField(blank=True, null=True)
Exemplo n.º 13
0
class CommunitySpace(models.Model):
    province = models.ForeignKey('Province',
                                 related_name='community_spaces',
                                 on_delete=models.CASCADE)
    district = models.ForeignKey('District',
                                 related_name='community_spaces',
                                 on_delete=models.CASCADE)
    municipality = models.ForeignKey('Municipality',
                                     related_name='community_spaces',
                                     on_delete=models.CASCADE)
    ward = models.CharField(max_length=100, blank=True, null=True)
    cid = models.CharField(max_length=100, null=True, blank=True)
    title = models.CharField(max_length=1000)
    description = models.TextField(blank=True, null=True)
    current_land_use = models.CharField(max_length=300, blank=True, null=True)
    coordinates_elevation = models.CharField(max_length=200,
                                             null=True,
                                             blank=True)
    usable_area = models.FloatField(default=Decimal('0.0000'),
                                    null=True,
                                    blank=True)
    total_area = models.FloatField(default=Decimal('0.0000'),
                                   null=True,
                                   blank=True)
    elevation = models.CharField(max_length=1000, null=True, blank=True)
    location = PointField(geography=True, srid=4326, blank=True, null=True)
    capacity = models.FloatField(blank=True,
                                 null=True,
                                 default=Decimal('0.0000'))
    type = models.CharField(max_length=100, null=True, blank=True)
    address = models.CharField(max_length=200, blank=True, null=True)
    main_community_space = models.ForeignKey(MainCommunitySpace,
                                             on_delete=models.CASCADE,
                                             related_name="community_spaces",
                                             null=True,
                                             blank=True)
    polygons = MultiPolygonField(null=True, blank=True)

    def __str__(self):
        return self.title

    @property
    def latitude(self):
        if self.location:
            return self.location.y

    @property
    def longitude(self):
        if self.location:
            return self.location.x
Exemplo n.º 14
0
class EPCI(models.Model):
    class TypeEPCI(models.TextChoices):
        CA = "CA", "Communauté d'agglomération"
        CC = "CC", "Communauté de communes"
        CU = "CU", "Communauté urbaine"
        METROPOLE = "ME", "Métropole"

    TYPE_CA = "CA"
    TYPE_CC = "CC"
    TYPE_CU = "CU"
    TYPE_METROPOLE = "ME"

    code = models.CharField("Code SIREN",
                            max_length=10,
                            editable=False,
                            unique=True)

    type = models.CharField("Type d'EPCI",
                            max_length=2,
                            choices=TypeEPCI.choices)

    nom = models.CharField("Nom de l'EPCI", max_length=300)

    population = models.PositiveIntegerField("Population", null=True)

    geometry = MultiPolygonField("Géométrie",
                                 geography=True,
                                 srid=4326,
                                 null=True,
                                 spatial_index=True)

    def __str__(self):
        return f"{self.nom} ({self.code})"

    def as_dict(self):
        """Sérialise l'instance (compatible JSON)"""
        return {
            "code": self.code,
            "nom": self.nom,
            "type": self.type,
            "population": self.population,
            "communes": {c.code: c.nom
                         for c in self.communes.all()},
        }

    class Meta:
        verbose_name = "EPCI"
        verbose_name_plural = "EPCI"

        ordering = ("code", "nom")
Exemplo n.º 15
0
class RegionGIS(models.Model):
    wkb_geometry = MultiPolygonField(srid=4326, blank=True, null=True)
    region_name = models.CharField(max_length=200, blank=True, null=True)
    office = models.CharField(max_length=200, blank=True, null=True)
    object_id = models.PositiveIntegerField(blank=True, null=True)

    class Meta:
        ordering = [
            'object_id',
        ]
        app_label = 'wildlifecompliance'

    def __str__(self):
        return "{}: {}".format(self.id, self.region_name)
Exemplo n.º 16
0
class TaskElement(models.Model):
    tasks = models.ManyToManyField(Task, related_name='task_element')
    title = models.CharField(max_length=200)
    building_nr = models.IntegerField(null=True, blank=True)
    info1 = models.CharField(max_length=200)
    info2 = models.CharField(max_length=200)
    info3 = models.CharField(max_length=200)
    element_name = models.CharField(max_length=200)
    element_geom = MultiPolygonField(null=True, blank=True)
    json = JSONField(blank=True)
    is_imported = models.BooleanField(default=False)

    def __str__(self):
        return self.element_name
Exemplo n.º 17
0
    def get_queryset(self):
        code_postal = get_object_or_404(CodePostal, code=self.kwargs["code"])
        geom = code_postal.communes.aggregate(geom=Union(
            Cast("geometry", output_field=MultiPolygonField(
                geography=False))))["geom"]

        if geom:
            return queryset_elus_proches(
                self.request.user.person,
                geom).filter(commune__geometry__dwithin=(
                    geom,
                    D(km=5),
                ))
        return EluMunicipal.objects.none()
Exemplo n.º 18
0
class Departement(TypeNomMixin, models.Model):
    code = models.CharField("Code INSEE",
                            max_length=3,
                            editable=False,
                            unique=True)
    nom = models.CharField("Nom du département",
                           max_length=200,
                           editable=False)

    chef_lieu = models.ForeignKey(
        "Commune",
        verbose_name="Chef-lieu",
        on_delete=models.PROTECT,
        related_name="+",
        related_query_name="chef_lieu_de",
    )
    region = models.ForeignKey(
        "Region",
        verbose_name="Région",
        on_delete=models.PROTECT,
        related_name="departements",
        related_query_name="departement",
    )

    population = models.PositiveIntegerField("Population", null=True)

    geometry = MultiPolygonField("Géométrie",
                                 geography=True,
                                 srid=4326,
                                 null=True,
                                 spatial_index=True)

    def __str__(self):
        return f"{self.nom} ({self.code})"

    def as_dict(self):
        return {
            "code": self.code,
            "nom": self.nom,
            "population": self.population,
            "chefLieu": {
                "nom": self.chef_lieu.nom_complet,
                "code": self.chef_lieu.code,
            },
        }

    class Meta:
        verbose_name = "Département"
        ordering = ("code", )
Exemplo n.º 19
0
class Municipality(models.Model):
    name = models.CharField(max_length=50)
    province = models.ForeignKey('Province',
                                 related_name='municipality',
                                 on_delete=models.CASCADE,
                                 blank=True,
                                 null=True)
    district = models.ForeignKey('District',
                                 related_name='municipality',
                                 on_delete=models.CASCADE)
    hlcit_code = models.CharField(max_length=100, blank=True, null=True)
    boundary = MultiPolygonField(null=True, blank=True)

    def __str__(self):
        return self.name
Exemplo n.º 20
0
class Taxlot(models.Model):
    class Meta:
        verbose_name = 'Taxlot'
        verbose_name_plural = 'Taxlots'
        app_label = 'landmapper'

    shape_leng = models.FloatField(null=True, blank=True)
    shape_area = models.FloatField(null=True, blank=True)

    geometry = MultiPolygonField(
        srid=3857,
        null=True, blank=True,
        verbose_name="Grid Cell Geometry"
    )
    objects = GeoManager()

    @property
    def area_in_sq_miles(self):
        true_area = self.geometry.transform(2163, clone=True).area
        return sq_meters_to_sq_miles(true_area)

    @property
    def formatted_area(self):
        return int((self.area_in_sq_miles * 10) + .5) / 10.

    def serialize_attributes(self):
        attributes = []
        attributes.append({'title': 'Area', 'data': '%.1f sq miles' % (self.area_in_sq_miles)})
        # attributes.append({'title': 'Description', 'data': self.description})
        return { 'event': 'click', 'attributes': attributes }

    # @classmethod
    # def fill_color(self):
    #     return '776BAEFD'

    @classmethod
    def outline_color(self):
        return '776BAEFD'

    class Options:
        verbose_name = 'Taxlot'
        icon_url = None
        export_png = False
        manipulators = []
        optional_manipulators = []
        form = None
        form_template = None
        show_template = None
Exemplo n.º 21
0
class TaskConflict(models.Model):
    tasks = models.ManyToManyField(Task, related_name='task_conflict')
    element = models.OneToOneField(TaskElement,
                                   related_name='conflict',
                                   on_delete=models.CASCADE)
    title = models.CharField(max_length=200, default='No title')
    building_nr = models.IntegerField(null=True, blank=True)
    info1 = models.CharField(max_length=200)
    info2 = models.CharField(max_length=200)
    info3 = models.CharField(max_length=200)
    conflict_name = models.CharField(max_length=200)
    conflict_geom = MultiPolygonField(null=True, blank=True)
    json = JSONField(blank=True)
    is_fixed = models.BooleanField(default=False)

    def __str__(self):
        return self.conflict_name
Exemplo n.º 22
0
class CollectiviteDepartementale(TypeNomMixin, models.Model):
    TYPE_CONSEIL_DEPARTEMENTAL = "D"
    TYPE_CONSEIL_METROPOLE = "M"
    TYPE_CHOICES = (
        (TYPE_CONSEIL_DEPARTEMENTAL, "Conseil départemental"),
        (TYPE_CONSEIL_METROPOLE, "Conseil de métropole"),
    )

    code = models.CharField("Code INSEE", max_length=4, unique=True)
    type = models.CharField("Type de collectivité départementale",
                            max_length=1,
                            choices=TYPE_CHOICES)
    actif = models.BooleanField("En cours d'existence", default=True)

    nom = models.CharField("Nom", max_length=200)

    departement = models.ForeignKey(
        "Departement",
        on_delete=models.PROTECT,
        verbose_name="Circonscription administrative correspondante",
    )

    population = models.PositiveIntegerField("Population", null=True)

    geometry = MultiPolygonField("Géométrie",
                                 geography=True,
                                 srid=4326,
                                 null=True)

    class Meta:
        verbose_name = "Collectivité à compétences départementales"
        verbose_name_plural = "Collectivités à compétences départementales"
        ordering = ("code", )

    def __str__(self):
        return f"{self.nom} ({self.code})"

    def as_dict(self):
        return {
            "code": self.code,
            "nom": self.nom_complet,
            "population": self.population,
            "type": self.type,
        }
Exemplo n.º 23
0
class CollectiviteDepartementale(TypeNomMixin, models.Model):
    TYPE_CONSEIL_DEPARTEMENTAL = "D"
    TYPE_STATUT_PARTICULIER = "S"
    TYPE_CHOICES = (
        (TYPE_CONSEIL_DEPARTEMENTAL, "Conseil départemental"),
        (TYPE_STATUT_PARTICULIER, "Collectivité à statut particulier"),
    )

    code = models.CharField("Code INSEE", max_length=4, unique=True)
    type = models.CharField("Type de collectivité départementale",
                            max_length=1,
                            choices=TYPE_CHOICES)
    actif = models.BooleanField("En cours d'existence", default=True)

    nom = models.CharField("Nom", max_length=200)

    region = models.ForeignKey("Region",
                               on_delete=models.PROTECT,
                               verbose_name="Région",
                               null=True)

    population = models.PositiveIntegerField("Population", null=True)

    geometry = MultiPolygonField("Géométrie",
                                 geography=True,
                                 srid=4326,
                                 null=True,
                                 spatial_index=True)

    class Meta:
        verbose_name = "Collectivité à compétences départementales"
        verbose_name_plural = "Collectivités à compétences départementales"
        ordering = ("code", )

    def __str__(self):
        return f"{self.nom} ({self.code})"

    def as_dict(self):
        return {
            "code": self.code,
            "nom": self.nom,
            "population": self.population,
            "type": self.type,
        }
Exemplo n.º 24
0
class Region(Described):
    """
    Regions are geographical boundaries which will come in 2 different levels:
    - Country
    - Province
    They will only know their boundaries data. They will also be MANAGED by certain
      staff members that can be assigned to them. Remarks in particular for each
      region.
    """

    # Filtering data (by region).
    boundaries = MultiPolygonField(verbose_name=_('Boundaries'))
    # Managers.
    managers = models.ForeignKey('User',
                                 related_name='managed_%(class)s_records',
                                 blank=True,
                                 on_delete=models.PROTECT)

    class Meta:
        abstract = True
Exemplo n.º 25
0
class CirconscriptionLegislative(models.Model):
    code = models.CharField(
        verbose_name="Numéro de la circonscription",
        max_length=10,
        blank=False,
        editable=False,
        null=False,
    )

    departement = models.ForeignKey(
        Departement,
        null=True,
        on_delete=models.CASCADE,
    )

    geometry = MultiPolygonField("Géométrie",
                                 geography=True,
                                 srid=4326,
                                 null=True)

    def __str__(self):
        code_dep, num = self.code.split("-")
        num = int(num)
        if num == 1:
            ordinal = "1ère"
        else:
            ordinal = f"{num}ème"

        if self.departement:
            return f"{ordinal} {self.departement.nom_avec_charniere}"
        elif code_dep == "99":
            return f"{ordinal} des Français de l'Étranger"
        elif code_dep == "977":
            return f"{ordinal} de Saint-Barthélémy et Saint-Martin"
        else:
            return f"{ordinal} {NOMS_COM[code_dep].nom_avec_charniere}"

    class Meta:
        verbose_name = "Circonscription législative"
        verbose_name_plural = "Circonscriptions législatives"
        ordering = ("code", )
Exemplo n.º 26
0
class WorldBorder(Model):
    # Regular Django fields corresponding to the attributes in the
    # world borders shapefile.
    name = CharField(max_length=50)
    area = IntegerField()
    pop2005 = IntegerField('Population 2005')
    fips = CharField('FIPS Code', max_length=2)
    iso2 = CharField('2 Digit ISO', max_length=2)
    iso3 = CharField('3 Digit ISO', max_length=3)
    un = IntegerField('United Nations Code')
    region = IntegerField('Region Code')
    subregion = IntegerField('Sub-Region Code')
    lon = FloatField()
    lat = FloatField()

    # GeoDjango-specific: a geometry field (MultiPolygonField), and
    # overriding the default manager with a GeoManager instance.
    mpoly = MultiPolygonField()

    # Returns the string representation of the model.
    def __str__(self):  # __unicode__ on Python 2
        return self.name
Exemplo n.º 27
0
class Region(BaseModel):
    name = models.CharField(max_length=50)
    slug = models.SlugField()
    geom = MultiPolygonField(srid=4326)

    @classmethod
    def get_for_point(cls, pt):
        return cls.objects.get(geom__contains=pt)

    def save(self, *args, **kwargs):
        if not self.slug or self.slug == "":
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse("region-detail", kwargs={"slug": self.slug})

    def __str__(self):
        return self.name

    class Meta:
        ordering = ("name",)
Exemplo n.º 28
0
class Region(TypeNomMixin, models.Model):
    code = models.CharField("Code INSEE",
                            max_length=3,
                            editable=False,
                            unique=True)
    nom = models.CharField("Nom de la région", max_length=200, editable=False)

    chef_lieu = models.ForeignKey(
        "Commune",
        verbose_name="Chef-lieu",
        on_delete=models.PROTECT,
        related_name="+",
        related_query_name="chef_lieu_de",
    )

    population = models.PositiveIntegerField("Population", null=True)

    geometry = MultiPolygonField("Géométrie",
                                 geography=True,
                                 srid=4326,
                                 null=True)

    def __str__(self):
        return self.nom

    def as_dict(self):
        return {
            "code": self.code,
            "nom": self.nom,
            "population": self.population,
            "chefLieu": {
                "nom": self.chef_lieu.nom_complet,
                "code": self.chef_lieu.code,
            },
        }

    class Meta:
        verbose_name = "Région"
        ordering = ("nom", )  # personne ne connait les codes de région
Exemplo n.º 29
0
class Region(RegionInterface, models.Model):
    title = models.CharField(max_length=128)
    polygon = MultiPolygonField(geography=True)
    parent = models.ForeignKey('self',
                               on_delete=models.CASCADE,
                               null=True,
                               blank=True)
    modified = models.DateTimeField(auto_now=True)
    wikidata_id = ExternalIdField(max_length=20,
                                  link='https://www.wikidata.org/wiki/{id}',
                                  null=True)
    osm_id = models.PositiveIntegerField(unique=True)
    osm_data = JSONField(default=dict)
    is_enabled = models.BooleanField(default=True)

    objects = RegionManager()

    class Meta:
        verbose_name = 'Region'
        verbose_name_plural = 'Regions'

    def __str__(self):
        return '{} ({})'.format(self.title, self.id)

    def __init__(self, *args, **kwargs):
        super(Region, self).__init__(*args, **kwargs)

    @property
    @cacheable
    def polygon_bounds(self) -> List:
        return self.polygon.extent

    @property
    def _strip_polygon(self):
        precision = 0.01 + 0.004 * (self.polygon.area / 10.0)
        return self.polygon.simplify(precision, preserve_topology=True)

    @property
    @cacheable
    def polygon_strip(self) -> List:
        simplify = self._strip_polygon
        return encode_geometry(simplify, min_points=10)

    @property
    @cacheable
    def polygon_gmap(self) -> List:
        precision = 0.005 + 0.001 * (self.polygon.area / 100.0)
        simplify = self.polygon.simplify(precision, preserve_topology=True)
        return encode_geometry(simplify)

    @property
    @cacheable
    def polygon_center(self) -> List:
        # http://lists.osgeo.org/pipermail/postgis-users/2007-February/014612.html
        def calc_polygon(strip, force):
            def calc_part(result, subpolygon):
                for point in subpolygon.coords[0]:
                    result['count'] += 1
                    result['lat'] += point[0]
                    result['lng'] += point[1]
                return result

            result = {'lat': 0.0, 'lng': 0.0, 'count': 0}
            if isinstance(strip, MultiPolygon):
                for part in strip:
                    if force or part.num_points > 10:
                        result = calc_part(result, part)
            else:
                result = calc_part(result, strip)
            return result

        result = calc_polygon(self._strip_polygon, force=False)
        if result['count'] == 0:
            result = calc_polygon(self._strip_polygon, force=True)
        return [
            result['lat'] / result['count'], result['lng'] / result['count']
        ]

    def infobox_status(self, lang: str) -> Dict:
        fields = ('name', 'wiki', 'capital', 'coat_of_arms', 'flag')
        trans = self.load_translation(lang)
        result = {field: field in trans.infobox for field in fields}
        result['capital'] = result.get('capital') and isinstance(
            trans.infobox['capital'], dict)
        return result

    @property
    @cacheable
    def polygon_infobox(self) -> Dict:
        def get_marker(infobox):
            by_capital = infobox.get('capital', {})
            if 'lat' in by_capital and 'lon' in by_capital:
                return {'lat': by_capital['lat'], 'lon': by_capital['lon']}
            else:
                center = self.polygon_center
                return {'lat': center[1], 'lon': center[0]}

        result = {}
        for trans in self.translations.all():
            infobox = trans.infobox
            infobox.pop('geonamesID', None)
            if isinstance(infobox.get('capital'), dict):
                del (infobox['capital']['id'])
            infobox['marker'] = get_marker(infobox)
            result[trans.language_code] = infobox
        return result

    @classmethod
    def caches(cls) -> List[str]:
        result = []
        for name in dir(cls):
            method = getattr(cls, name)
            if isinstance(
                    method,
                    property) and method.fget.__name__ == 'cache_wrapper':
                result.append(name)
        return result

    def json(self, lang: str) -> Dict:
        translation = self.load_translation(lang)
        result = {
            'id': str(self.id),
            'name': translation.name,
            'items': self.items(lang)
        }
        result['items_exists'] = len(result['items']) > 0
        return result

    def items(self, lang: str) -> List[Dict]:
        child_query = Region.objects.filter(parent_id=OuterRef('pk'))
        return [{
            'id': str(x.id),
            'name': x.lang,
            'items_exists': x.items_exists
        } for x in self.region_set.filter(
            translations__language_code=lang).annotate(
                items_exists=Exists(child_query), lang=F(
                    'translations__name')).order_by('lang').distinct()]

    def fetch_polygon(self) -> None:
        def content():
            cache = os.path.join(settings.GEOJSON_DIR,
                                 '{}.geojson'.format(self.osm_id))
            if not os.path.exists(cache):
                url = settings.OSM_URL.format(id=self.osm_id,
                                              key=settings.OSM_KEY,
                                              level=self.osm_data['level'])
                fetch_logger.info(f'Download from {url}')
                response = requests.get(url)
                if response.status_code != 200:
                    raise Exception('Bad request')
                zipfile = ZipFile(BytesIO(response.content))
                zip_names = zipfile.namelist()
                if len(zip_names) != 1:
                    raise Exception('Too many geometries')
                filename = zip_names.pop()
                with open(cache, 'wb') as c:
                    c.write(zipfile.open(filename).read())
            with open(cache, 'r') as c:
                return json.loads(c.read())

        def update_self(properties, geometry, type):
            def extract_data(properties):
                result = {'level': int(properties['admin_level'])}
                fields = ['boundary', 'ISO3166-1:alpha3', 'timezone']
                for field in fields:
                    result[field] = properties['alltags'].get(field, None)
                return result

            self.title = properties['name']
            self.polygon = GEOSGeometry(json.dumps(geometry))
            self.wikidata_id = properties.get('wikidata')
            self.osm_id = properties['id']
            self.osm_data = extract_data(properties)

        fetch_logger.info(f'Update polygon: {self.id}')
        feature = content()['features'][0]
        update_self(**feature)
        self.save()

        for lang in settings.ALLOWED_LANGUAGES:
            trans = self.load_translation(lang)
            trans.master = self
            trans.name = self.title
            trans.save()

    def fetch_items_list(self) -> Dict:
        params = {
            'caller': 'boundaries-4.3.14',
            'database': 'planet3',
            'parent': self.osm_id
        }
        headers = {
            'Referer':
            'https://wambachers-osm.website/boundaries/',
            'User-Agent':
            'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',
            'X-Requested-With':
            'XMLHttpRequest',
            'Cookie':
            'JSESSIONID=node01ln7zb8ljtnbacctakke8yb6s49.node0; osm_boundaries_map=1%7C40.65536999999962%7C45.25161549584642%7C10%7C0BT%7Copen; osm_boundaries_base=4%7Cfalse%7Cjson%7Czip%7Cnull%7Cfalse%7Clevels%7Cland%7C4.3%7Ctrue%7C3',
        }
        fetch_logger.info(f'Get items: {self.id}')
        response = requests.post(
            'https://wambachers-osm.website/boundaries/getJsTree6',
            params=params,
            headers=headers)
        items = response.json()
        fetch_logger.info(f'Count items for {self.id}: {len(items)}')
        return items

    def fetch_items(self) -> None:
        for item in self.fetch_items_list():
            if item['data']['admin_level'] >= 8:
                continue
            region, _ = Region.objects.get_or_create(
                osm_id=item['id'],
                defaults={
                    'parent': self,
                    'osm_data': {
                        'level': item['data']['admin_level']
                    }
                })
            region.fetch_polygon()
            region.fetch_infobox()

    def fetch_infobox(self) -> None:
        fetch_logger.info(f'Get infobox: {self.wikidata_id}')
        if self.wikidata_id is None or self.wikidata_id == '':
            rows = {lang: {} for lang in settings.ALLOWED_LANGUAGES}
        else:
            wikidata_id = None if self.parent is None else self.parent.wikidata_id
            rows = query_by_wikidata_id(country_id=wikidata_id,
                                        item_id=self.wikidata_id)
        for lang, infobox in rows.items():
            fetch_logger.info(f'Update infobox: {lang}')
            if 'name' not in infobox:
                infobox['name'] = self.title
            trans = self.load_translation(lang)
            trans.master = self
            trans.infobox = infobox
            trans.name = infobox['name']
            trans.save()

    @property
    def translation(self):
        return self.load_translation(get_language())

    def load_translation(self, lang):
        result = self.translations.filter(language_code=lang).first()
        if result is None:
            result = RegionTranslation.objects.create(language_code=lang,
                                                      master=self,
                                                      name='(empty)')
        return result
Exemplo n.º 30
0
class Segment(BaseSegment, models.Model):
    GA_STATUS_NOT_MEMBER = "N"
    GA_STATUS_MEMBER = "m"
    GA_STATUS_MANAGER = "M"
    GA_STATUS_REFERENT = "R"
    GA_STATUS_CHOICES = (
        (GA_STATUS_NOT_MEMBER, "Non membres de GA"),
        (GA_STATUS_MEMBER, "Membres de GA"),
        (GA_STATUS_MANAGER, "Animateur·ices et gestionnaires de GA"),
        (GA_STATUS_REFERENT, "Animateur·ices de GA"),
    )

    name = models.CharField("Nom", max_length=255)

    tags = models.ManyToManyField("people.PersonTag", blank=True)

    is_2022 = models.BooleanField("Inscrits NSP", null=True, blank=True)
    is_insoumise = models.BooleanField("Inscrits LFI",
                                       null=True,
                                       blank=True,
                                       default=True)

    newsletters = ChoiceArrayField(
        models.CharField(choices=Person.NEWSLETTERS_CHOICES, max_length=255),
        default=default_newsletters,
        help_text="Inclure les personnes abonnées aux newsletters suivantes.",
        blank=True,
    )
    supportgroup_status = models.CharField(
        "Limiter aux membres de groupes ayant ce statut",
        max_length=1,
        choices=GA_STATUS_CHOICES,
        blank=True,
    )
    supportgroup_subtypes = models.ManyToManyField(
        "groups.SupportGroupSubtype",
        verbose_name="Limiter aux membres de groupes d'un de ces sous-types",
        blank=True,
    )
    events = models.ManyToManyField(
        "events.Event",
        verbose_name="Limiter aux participant⋅e⋅s à un des événements",
        blank=True,
    )
    events_subtypes = models.ManyToManyField(
        "events.EventSubtype",
        verbose_name="Limiter aux participant⋅e⋅s à un événements de ce type",
        blank=True,
    )
    events_start_date = models.DateTimeField(
        "Limiter aux participant⋅e⋅s à des événements commençant après cette date",
        blank=True,
        null=True,
    )
    events_end_date = models.DateTimeField(
        "Limiter aux participant⋅e⋅s à des événements terminant avant cette date",
        blank=True,
        null=True,
    )
    events_organizer = models.BooleanField(
        "Limiter aux organisateurices (sans effet si pas d'autres filtres événements)",
        blank=True,
        default=False,
    )

    draw_status = models.BooleanField(
        "Limiter aux gens dont l'inscription au tirage au sort est",
        null=True,
        blank=True,
        default=None,
    )

    forms = models.ManyToManyField(
        "people.PersonForm",
        verbose_name="A répondu à au moins un de ces formulaires",
        blank=True,
        related_name="+",
    )

    polls = models.ManyToManyField(
        "polls.Poll",
        verbose_name="A participé à au moins une de ces consultations",
        blank=True,
        related_name="+",
    )

    countries = CountryField("Limiter aux pays", multiple=True, blank=True)
    departements = ChoiceArrayField(
        models.CharField(choices=data.departements_choices, max_length=3),
        verbose_name=
        "Limiter aux départements (calcul à partir du code postal)",
        default=list,
        blank=True,
    )
    area = MultiPolygonField("Limiter à un territoire définit manuellement",
                             blank=True,
                             null=True)

    campaigns = models.ManyToManyField(
        "nuntius.Campaign",
        related_name="+",
        verbose_name="Limiter aux personnes ayant reçu une des campagnes",
        blank=True,
    )

    last_open = models.IntegerField(
        "Limiter aux personnes ayant ouvert un email envoyé au court de derniers jours",
        help_text="Indiquer le nombre de jours",
        blank=True,
        null=True,
    )

    last_click = models.IntegerField(
        "Limiter aux personnes ayant cliqué dans un email envoyé au court des derniers jours",
        help_text="Indiquer le nombre de jours",
        blank=True,
        null=True,
    )

    FEEDBACK_OPEN = 1
    FEEDBACK_CLICKED = 2
    FEEDBACK_NOT_OPEN = 3
    FEEDBACK_NOT_CLICKED = 4
    FEEDBACK_OPEN_NOT_CLICKED = 5
    FEEDBACK_CHOICES = (
        (FEEDBACK_OPEN, "Personnes ayant ouvert"),
        (FEEDBACK_CLICKED, "Personnes ayant cliqué"),
        (FEEDBACK_NOT_OPEN, "Personnes n'ayant pas ouvert"),
        (FEEDBACK_NOT_CLICKED, "Personnes n'ayant pas cliqué"),
        (FEEDBACK_OPEN_NOT_CLICKED, "Personnes ayant ouvert mais pas cliqué"),
    )

    campaigns_feedback = models.PositiveSmallIntegerField(
        "Limiter en fonction de la réaction à ces campagnes",
        blank=True,
        null=True,
        choices=FEEDBACK_CHOICES,
        help_text=
        "Aucun effet si aucune campagne n'est sélectionnée dans le champ précédent",
    )

    registration_date = models.DateTimeField(
        "Limiter aux membres inscrit⋅e⋅s après cette date",
        blank=True,
        null=True)

    registration_duration = models.IntegerField(
        "Limiter aux membres inscrit⋅e⋅s depuis au moins un certain nombre d'heures",
        help_text="Indiquer le nombre d'heures",
        blank=True,
        null=True,
    )

    last_login = models.DateTimeField(
        "Limiter aux membres s'étant connecté⋅e pour la dernière fois après cette date",
        blank=True,
        null=True,
    )

    gender = models.CharField("Genre",
                              max_length=1,
                              blank=True,
                              choices=Person.GENDER_CHOICES)

    born_after = models.DateField("Personnes nées après le",
                                  blank=True,
                                  null=True,
                                  help_text=DATE_HELP_TEXT)
    born_before = models.DateField("Personnes nées avant le",
                                   blank=True,
                                   null=True,
                                   help_text=DATE_HELP_TEXT)

    donation_after = models.DateField(
        "A fait au moins un don (don mensuel inclus) après le",
        blank=True,
        null=True,
        help_text=DATE_HELP_TEXT,
    )
    donation_not_after = models.DateField(
        "N'a pas fait de don (don mensuel inclus) depuis le",
        blank=True,
        null=True,
        help_text=DATE_HELP_TEXT,
    )
    donation_total_min = AmountField(
        "Montant total des dons supérieur ou égal à", blank=True, null=True)
    donation_total_max = AmountField(
        "Montant total des dons inférieur ou égal à", blank=True, null=True)
    donation_total_range = DateRangeField(
        "Pour le filtre de montant total, prendre uniquement en compte les dons entre ces deux dates",
        blank=True,
        null=True,
        help_text=
        "Écrire sous la forme JJ/MM/AAAA. La date de début est incluse, pas la date de fin.",
    )

    subscription = models.BooleanField("A une souscription mensuelle active",
                                       blank=True,
                                       null=True)

    ELUS_NON = "N"
    ELUS_MEMBRE_RESEAU = "M"
    ELUS_REFERENCE = "R"
    ELUS_CHOICES = (
        ("", "Peu importe"),
        (ELUS_MEMBRE_RESEAU, "Uniquement les membres du réseau des élus"),
        (
            ELUS_REFERENCE,
            "Tous les élus, membres ou non du réseau, sauf les exclus du réseau",
        ),
    )

    elu = models.CharField("Est un élu",
                           max_length=1,
                           choices=ELUS_CHOICES,
                           blank=True)

    elu_municipal = models.BooleanField("Avec un mandat municipal",
                                        default=True)
    elu_departemental = models.BooleanField("Avec un mandat départemental",
                                            default=True)
    elu_regional = models.BooleanField("Avec un mandat régional", default=True)
    elu_consulaire = models.BooleanField("Avec un mandat consulaire",
                                         default=True)

    exclude_segments = models.ManyToManyField(
        "self",
        symmetrical=False,
        related_name="+",
        verbose_name="Exclure les personnes membres des segments suivants",
        blank=True,
    )

    add_segments = models.ManyToManyField(
        "self",
        symmetrical=False,
        related_name="+",
        verbose_name="Ajouter les personnes membres des segments suivants",
        blank=True,
    )

    def get_subscribers_q(self):
        # ne pas inclure les rôles inactifs dans les envois de mail
        q = ~Q(role__is_active=False)

        # permettre de créer des segments capables d'inclure des personnes inscrites à aucune des newsletters
        if self.newsletters:
            q &= Q(newsletters__overlap=self.newsletters)

        if self.is_insoumise is not None:
            q = q & Q(is_insoumise=self.is_insoumise)

        if self.is_2022 is not None:
            q = q & Q(is_2022=self.is_2022)

        if self.tags.all().count() > 0:
            q = q & Q(tags__in=self.tags.all())

        if self.supportgroup_status:
            if self.supportgroup_status == self.GA_STATUS_NOT_MEMBER:
                supportgroup_q = ~Q(memberships__supportgroup__published=True)
            elif self.supportgroup_status == self.GA_STATUS_MEMBER:
                supportgroup_q = Q(memberships__supportgroup__published=True)

            elif self.supportgroup_status == self.GA_STATUS_REFERENT:
                supportgroup_q = Q(
                    memberships__supportgroup__published=True,
                    memberships__membership_type__gte=Membership.
                    MEMBERSHIP_TYPE_REFERENT,
                )
            else:
                # ==> self.supportgroup_status == self.GA_STATUS_MANAGER
                supportgroup_q = Q(
                    memberships__supportgroup__published=True,
                    memberships__membership_type__gte=Membership.
                    MEMBERSHIP_TYPE_MANAGER,
                )

            if self.supportgroup_subtypes.all().count() > 0:
                supportgroup_q = supportgroup_q & Q(
                    memberships__supportgroup__subtypes__in=self.
                    supportgroup_subtypes.all())

            q = q & supportgroup_q

        events_filter = {}

        if self.events.all().count() > 0:
            events_filter["in"] = self.events.all()

        if self.events_subtypes.all().count() > 0:
            events_filter["subtype__in"] = self.events_subtypes.all()

        if self.events_start_date is not None:
            events_filter["start_time__gt"] = self.events_start_date

        if self.events_end_date is not None:
            events_filter["end_time__lt"] = self.events_end_date

        if events_filter:
            prefix = "organized_events" if self.events_organizer else "rsvps__event"
            q = q & Q(
                **{f"{prefix}__{k}": v
                   for k, v in events_filter.items()})

            if not self.events_organizer:
                q = q & Q(rsvps__status__in=[
                    RSVP.STATUS_CONFIRMED,
                    RSVP.STATUS_AWAITING_PAYMENT,
                ])

        if self.draw_status is not None:
            q = q & Q(draw_participation=self.draw_status)

        if self.forms.all().count() > 0:
            q = q & Q(form_submissions__form__in=self.forms.all())

        if self.polls.all().count() > 0:
            q = q & Q(poll_choices__poll__in=self.polls.all())

        if self.campaigns.all().count() > 0:
            if self.campaigns_feedback == self.FEEDBACK_OPEN:
                campaign__kwargs = {"campaignsentevent__open_count__gt": 0}
            elif self.campaigns_feedback == self.FEEDBACK_CLICKED:
                campaign__kwargs = {"campaignsentevent__click_count__gt": 0}
            elif self.campaigns_feedback == self.FEEDBACK_NOT_OPEN:
                campaign__kwargs = {"campaignsentevent__open_count": 0}
            elif self.campaigns_feedback == self.FEEDBACK_NOT_CLICKED:
                campaign__kwargs = {"campaignsentevent__click_count": 0}
            elif self.campaigns_feedback == self.FEEDBACK_OPEN_NOT_CLICKED:
                campaign__kwargs = {
                    "campaignsentevent__open_count__gt": 1,
                    "campaignsentevent__click_count": 0,
                }
            else:
                campaign__kwargs = {}

            q = q & Q(
                campaignsentevent__result__in=[
                    CampaignSentStatusType.UNKNOWN,
                    CampaignSentStatusType.OK,
                ],
                campaignsentevent__campaign__in=self.campaigns.all(),
                **campaign__kwargs,
            )

        if self.last_open is not None:
            q = q & Q(
                campaignsentevent__open_count__gt=0,
                campaignsentevent__datetime__gt=now() -
                timedelta(days=self.last_open),
            )

        if self.last_click is not None:
            q = q & Q(
                campaignsentevent__click_count__gt=0,
                campaignsentevent__datetime__gt=now() -
                timedelta(days=self.last_click),
            )

        if len(self.countries) > 0:
            q = q & Q(location_country__in=self.countries)

        if len(self.departements) > 0:
            q = q & Q(data.filtre_departements(*self.departements))

        if self.area is not None:
            q = q & Q(coordinates__intersects=self.area)

        if self.registration_date is not None:
            q = q & Q(created__gt=self.registration_date)

        if self.registration_duration:
            q = q & Q(created__lt=now() -
                      timedelta(hours=self.registration_duration))

        if self.last_login is not None:
            q = q & Q(role__last_login__gt=self.last_login)

        if self.gender:
            q = q & Q(gender=self.gender)

        if self.born_after is not None:
            q = q & Q(date_of_birth__gt=self.born_after)

        if self.born_before is not None:
            q = q & Q(date_of_birth__lt=self.born_before)

        if self.donation_after is not None:
            q = q & Q(payments__created__gt=self.donation_after,
                      **DONATION_FILTER)

        if self.donation_not_after is not None:
            q = q & ~Q(payments__created__gt=self.donation_not_after,
                       **DONATION_FILTER)

        if self.donation_total_min or self.donation_total_max:
            donation_range = (
                {
                    "payments__created__gt": self.donation_total_range.lower,
                    "payments__created__lt": self.donation_total_range.upper,
                } if self.donation_total_range else {})
            annotated_qs = Person.objects.annotate(donation_total=Sum(
                "payments__price",
                filter=Q(**DONATION_FILTER, **donation_range)))

            if self.donation_total_min:
                annotated_qs = annotated_qs.filter(
                    donation_total__gte=self.donation_total_min)

            if self.donation_total_max:
                annotated_qs = annotated_qs.filter(
                    donation_total__lte=self.donation_total_max)

            q = q & Q(id__in=annotated_qs.values_list("id"))

        if self.subscription is not None:
            if self.subscription:
                q = q & Q(subscriptions__status=Subscription.STATUS_ACTIVE)
            else:
                q = q & ~Q(subscriptions__status=Subscription.STATUS_ACTIVE)

        if self.elu:
            if self.elu == Segment.ELUS_MEMBRE_RESEAU:
                q &= Q(membre_reseau_elus=Person.MEMBRE_RESEAU_OUI)
            elif self.elu == Segment.ELUS_REFERENCE:
                q &= ~Q(membre_reseau_elus=Person.MEMBRE_RESEAU_EXCLUS)

            q_mandats = Q()
            for t in [
                    "elu_municipal",
                    "elu_departemental",
                    "elu_regional",
                    "elu_consulaire",
            ]:
                if getattr(self, t):
                    q_mandats |= Q(**{t: True})
            q &= q_mandats

        return q

    def _get_own_filters_queryset(self):
        qs = Person.objects.all()

        if self.elu:
            qs = qs.annotate_elus()

        return qs.filter(
            self.get_subscribers_q()).filter(emails___bounced=False)

    def get_subscribers_queryset(self):
        qs = self._get_own_filters_queryset()

        for s in self.add_segments.all():
            qs = Person.objects.filter(
                Q(pk__in=qs) | Q(pk__in=s.get_subscribers_queryset()))

        for s in self.exclude_segments.all():
            qs = qs.exclude(pk__in=s.get_subscribers_queryset())

        return qs.order_by("id").distinct("id")

    def get_subscribers_count(self):
        return (self._get_own_filters_queryset().order_by("id").distinct(
            "id").count() + sum(s.get_subscribers_count()
                                for s in self.add_segments.all()) -
                sum(s.get_subscribers_count()
                    for s in self.exclude_segments.all()))

    def is_subscriber(self, person):
        return self.get_subscribers_queryset().filter(pk=person.pk).exists()

    get_subscribers_count.short_description = "Personnes"
    get_subscribers_count.help_text = "Estimation du nombre d'inscrits"

    def __str__(self):
        return self.name