Beispiel #1
0
class Session(db.Model):
    """Stores the minimum data to trace the changes. We have two scenarios:
    - one registered user (a developer?) create its own token, and then gets
      a nominative session
    - a client sends us IP and/or email from a remote user we don't know of
    """

    __openapi__ = """
        properties:
            id:
                type: integer
                description: primary key of the session
            client:
                type: string
                description: client name
            user:
                type: string
                description: user name
        """

    user = db.CachedForeignKeyField(User, null=True)
    client = db.CachedForeignKeyField(Client, null=True)
    ip = db.CharField(null=True)  # TODO IPField
    email = db.CharField(null=True)  # TODO EmailField
    contributor_type = db.CharField(null=True)

    def serialize(self, *args):
        # Pretend to be a resource for created_by/modified_by values in
        # resources serialization.
        # Should we also expose the email/ip? CNIL question to be solved.
        return {
            'id':
            self.pk,
            'client':
            self.client.name if self.client else None,
            'user':
            self.user.username if self.user else None,
            'contributor_type':
            self.contributor_type if self.contributor_type else None
        }

    def save(self, **kwargs):
        if not self.user and not self.client:
            raise ValueError('Session must have either a client or a user')
        if not self.contributor_type:
            raise ValueError('Session must have a contributor type')
        super().save(**kwargs)
Beispiel #2
0
class HouseNumber(Model):
    # INSEE + set of OCR-friendly characters (dropped confusing ones
    # (like 0/O, 1/I…)) from La Poste.
    CEA_FORMAT = Municipality.INSEE_FORMAT + '[234679ABCEGHILMNPRSTUVXYZ]{5}'
    identifiers = ['cia', 'laposte', 'ign']
    resource_fields = [
        'number', 'ordinal', 'parent', 'cia', 'laposte', 'ancestors',
        'positions', 'ign', 'postcode'
    ]
    exclude_for_collection = ['ancestors']

    number = db.CharField(max_length=16, null=True)
    ordinal = db.CharField(max_length=16, null=True)
    parent = db.ForeignKeyField(Group)
    cia = db.CharField(max_length=100, null=True, unique=True)
    laposte = db.CharField(length=10,
                           null=True,
                           unique=True,
                           format=CEA_FORMAT)
    ign = db.CharField(max_length=24, null=True, unique=True)
    ancestors = db.ManyToManyField(Group, related_name='_housenumbers')
    postcode = db.CachedForeignKeyField(PostCode, null=True)

    class Meta:
        indexes = ((('parent', 'number', 'ordinal'), True), )
        case_ignoring = ('ordinal', )

    def __str__(self):
        return ' '.join([self.number or '', self.ordinal or ''])

    def save(self, *args, **kwargs):
        self.cia = self.compute_cia()
        super().save(*args, **kwargs)
        self._clean_called = False

    def compute_cia(self):
        return compute_cia(self.parent.fantoir[:5], self.parent.fantoir[5:],
                           self.number,
                           self.ordinal) if self.parent.fantoir else None

    @cached_property
    def municipality(self):
        return Municipality.select().where(
            Municipality.pk == self.parent.municipality.pk).first()

    @property
    def as_export(self):
        """Resources plus relation references without metadata."""
        mask = {f: {} for f in self.resource_fields}
        return self.serialize(mask)
Beispiel #3
0
class PostCode(NamedModel):
    resource_fields = ['code', 'name', 'alias', 'complement', 'municipality']

    complement = db.CharField(max_length=38, null=True)
    code = db.CharField(index=True, format='\d*', length=5)
    municipality = db.CachedForeignKeyField(Municipality,
                                            related_name='postcodes')

    class Meta:
        indexes = ((('code', 'complement', 'municipality'), True), )

    @property
    def housenumbers(self):
        return self.housenumber_set.order_by(
            peewee.SQL('number ASC NULLS FIRST'),
            peewee.SQL('ordinal ASC NULLS FIRST'))
Beispiel #4
0
class Group(NamedModel):
    AREA = 'area'
    WAY = 'way'
    KIND = (
        (WAY, 'way'),
        (AREA, 'area'),
    )
    CLASSICAL = 'classical'
    METRIC = 'metric'
    LINEAR = 'linear'
    MIXED = 'mixed'
    ANARCHICAL = 'anarchical'
    ADDRESSING = (
        (CLASSICAL, 'classical'),
        (METRIC, 'metric'),
        (LINEAR, 'linear'),
        (MIXED, 'mixed types'),
        (ANARCHICAL, 'anarchical'),
    )
    identifiers = ['fantoir', 'laposte', 'ign']
    resource_fields = [
        'name', 'alias', 'fantoir', 'municipality', 'kind', 'laposte', 'ign',
        'addressing'
    ]

    kind = db.CharField(max_length=64, choices=KIND)
    addressing = db.CharField(max_length=16, choices=ADDRESSING, null=True)
    fantoir = db.FantoirField(null=True, unique=True)
    laposte = db.CharField(max_length=8, null=True, unique=True, format='\d*')
    ign = db.CharField(max_length=24, null=True, unique=True)
    municipality = db.CachedForeignKeyField(Municipality,
                                            related_name='groups')

    @property
    def housenumbers(self):
        qs = (self._housenumbers | self.housenumber_set)
        return qs.order_by(peewee.SQL('number ASC NULLS FIRST'),
                           peewee.SQL('ordinal ASC NULLS FIRST'))
Beispiel #5
0
class Versioned(db.Model, metaclass=BaseVersioned):

    ForcedVersionError = ForcedVersionError

    version = db.IntegerField(default=1)
    created_at = db.DateTimeField()
    created_by = db.CachedForeignKeyField(Session)
    modified_at = db.DateTimeField()
    modified_by = db.CachedForeignKeyField(Session)

    class Meta:
        validate_backrefs = False
        unique_together = ('pk', 'version')

    def prepared(self):
        self.lock_version()
        super().prepared()

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

    def store_version(self):
        new = Version.create(model_name=self.resource,
                             model_pk=self.pk,
                             sequential=self.version,
                             data=self.as_version,
                             period=[self.modified_at, None])
        old = None
        if self.version > 1:
            old = self.load_version(self.version - 1)
            old.close_period(new.period.lower)
        if Diff.ACTIVE:
            Diff.create(old=old,
                        new=new,
                        created_at=self.modified_at,
                        insee=self.municipality.insee)

    @property
    def versions(self):
        return Version.select().where(Version.model_name == self.resource,
                                      Version.model_pk == self.pk).order_by(
                                          Version.sequential)

    def load_version(self, ref=None):
        qs = self.versions
        if ref is None:
            ref = self.version
        if isinstance(ref, datetime):
            qs = qs.where(Version.period.contains(ref))
        else:
            qs = qs.where(Version.sequential == ref)
        return qs.first()

    @property
    def locked_version(self):
        return getattr(self, '_locked_version', None)

    @locked_version.setter
    def locked_version(self, value):
        # Should be set only once, and never updated.
        assert not hasattr(
            self, '_locked_version'), 'locked_version is read only'  # noqa
        self._locked_version = value

    def lock_version(self):
        if not self.pk:
            self.version = 1
        self._locked_version = self.version if self.pk else 0

    def increment_version(self):
        self.version = self.version + 1

    def check_version(self):
        if self.version != self.locked_version + 1:
            raise ForcedVersionError('wrong version number: {}'.format(
                self.version))  # noqa

    def update_meta(self):
        session = context.get('session')
        if session:
            if not self.created_by:
                self.created_by = session
            self.modified_by = session
        now = utcnow()
        if not self.created_at:
            self.created_at = now
        self.modified_at = now

    def save(self, *args, **kwargs):
        with self._meta.database.atomic():
            self.check_version()
            self.update_meta()
            try:
                self.source_kind = self.created_by.contributor_type
            except Exception:
                pass
            super().save(*args, **kwargs)
            self.store_version()
            self.lock_version()

    def delete_instance(self, *args, **kwargs):
        with self._meta.database.atomic():
            Redirect.clear(self)
            return super().delete_instance(*args, **kwargs)