Exemple #1
0
class ProxyableModel(Model):
    proxy = db.ForeignKeyField(Proxy, unique=True)
    municipality = db.ForeignKeyField(Municipality,
                                      related_name='{classname}s')
    attributes = db.HStoreField(null=True)

    class Meta:
        auto_increment = False

    @property
    def housenumbers(self):
        qs = (self.proxy.housenumbers | self.proxy.housenumber_set)
        return qs.order_by(peewee.SQL('number'), peewee.SQL('ordinal'))

    def save(self, *args, **kwargs):
        if not self.id:
            proxy = Proxy(kind=self.resource)
            proxy.save()
            self.proxy = proxy.id
            self.id = proxy.id
            kwargs['force_insert'] = True
        return super().save(*args, **kwargs)

    def delete_instance(self, recursive=False, delete_nullable=False):
        with self._meta.database.atomic():
            super().delete_instance(recursive, delete_nullable)
            self.proxy.delete_instance()
Exemple #2
0
class HouseNumber(Model):
    identifiers = ['cia', 'laposte', 'ign']
    resource_fields = [
        'number', 'ordinal', 'parent', 'cia', 'laposte', 'ancestors',
        'positions', 'ign', 'postcode'
    ]
    readonly_fields = Model.readonly_fields + ['cia']

    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(max_length=10, null=True, unique=True)
    ign = db.CharField(max_length=24, null=True, unique=True)
    ancestors = db.ManyToManyField(Group, related_name='_housenumbers')
    postcode = db.ForeignKeyField(PostCode, null=True)

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

    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(str(self.parent.municipality.insee),
                           self.parent.get_fantoir(), self.number,
                           self.ordinal)
Exemple #3
0
class Flag(db.Model):

    __openapi__ = """
        properties:
            at:
                type: string
                format: date-time
                description: when the flag has been created
            by:
                type: string
                description: identifier of the client who flagged the version
        """

    version = db.ForeignKeyField(Version, related_name='flags')
    client = db.ForeignKeyField(Client)
    session = db.ForeignKeyField(Session)
    created_at = db.DateTimeField()

    def save(self, *args, **kwargs):
        if not self.created_at:
            self.created_at = utcnow()
        super().save(*args, **kwargs)

    def serialize(self, *args):
        return {'at': self.created_at, 'by': self.client.flag_id}
Exemple #4
0
class Diff(db.Model):

    __openapi__ = """
        properties:
            resource:
                type: string
                description: name of the resource the diff is applied to
            resource_id:
                type: string
                description: id of the resource the diff is applied to
            created_at:
                type: string
                format: date-time
                description: the date and time the diff has been created at
            old:
                type: object
                description: the resource before the change
            new:
                type: object
                description: the resource after the change
            diff:
                type: object
                description: detail of changed properties
            """

    # Allow to skip diff at very first data import.
    ACTIVE = True

    # old is empty at creation.
    old = db.ForeignKeyField(Version, null=True)
    # new is empty after delete.
    new = db.ForeignKeyField(Version, null=True)
    diff = db.BinaryJSONField()
    created_at = db.DateTimeField()

    class Meta:
        validate_backrefs = False
        order_by = ('pk', )

    def save(self, *args, **kwargs):
        if not self.diff:
            old = self.old.data if self.old else {}
            new = self.new.data if self.new else {}
            self.diff = make_diff(old, new)
        super().save(*args, **kwargs)
        Redirect.from_diff(self)

    def serialize(self, *args):
        version = self.new or self.old
        return {
            'increment': self.pk,
            'old': self.old.data if self.old else None,
            'new': self.new.data if self.new else None,
            'diff': self.diff,
            'resource': version.model_name.lower(),
            'resource_id': version.data['id'],
            'created_at': self.created_at
        }
Exemple #5
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
    """
    user = db.ForeignKeyField(User, null=True)
    client = db.ForeignKeyField(Client, null=True)
    ip = db.CharField(null=True)  # TODO IPField
    email = db.CharField(null=True)  # TODO EmailField
Exemple #6
0
class Grant(db.Model):
    user = db.ForeignKeyField(User)
    client = db.ForeignKeyField(Client)
    code = db.CharField(max_length=255, index=True, null=False)
    redirect_uri = db.CharField()
    scope = db.CharField(null=True)
    expires = db.DateTimeField()

    @property
    def scopes(self):
        return self.scope.split() if self.scope else None
Exemple #7
0
class Diff(db.Model):

    # Allow to skip diff at very first data import.
    ACTIVE = True

    # old is empty at creation.
    old = db.ForeignKeyField(Version, null=True)
    # new is empty after delete.
    new = db.ForeignKeyField(Version, null=True)
    diff = db.BinaryJSONField()
    created_at = db.DateTimeField()

    class Meta:
        validate_backrefs = False
        manager = SelectQuery
        order_by = ('id', )

    def save(self, *args, **kwargs):
        if not self.diff:
            meta = set([
                'id', 'created_by', 'modified_by', 'created_at', 'modified_at',
                'version'
            ])
            old = self.old.as_resource if self.old else {}
            new = self.new.as_resource if self.new else {}
            keys = set(list(old.keys()) + list(new.keys())) - meta
            self.diff = {}
            for key in keys:
                old_value = old.get(key)
                new_value = new.get(key)
                if new_value != old_value:
                    self.diff[key] = {
                        'old': str(old_value),
                        'new': str(new_value)
                    }
        super().save(*args, **kwargs)
        IdentifierRedirect.from_diff(self)

    @property
    def as_resource(self):
        version = self.new or self.old
        return {
            'increment': self.id,
            'old': self.old.as_resource if self.old else None,
            'new': self.new.as_resource if self.new else None,
            'diff': self.diff,
            'resource': version.model_name.lower(),
            'resource_id': version.model_id,
            'created_at': self.created_at
        }
Exemple #8
0
class Client(ResourceModel):
    identifiers = ['client_id']

    GRANT_AUTHORIZATION_CODE = 'authorization_code'
    GRANT_IMPLICIT = 'implicit'
    GRANT_PASSWORD = '******'
    GRANT_CLIENT_CREDENTIALS = 'client_credentials'
    GRANT_TYPES = (
        # (GRANT_AUTHORIZATION_CODE, _('Authorization code')),
        # (GRANT_IMPLICIT, _('Implicit')),
        (GRANT_PASSWORD, _('Resource owner password-based')),
        (GRANT_CLIENT_CREDENTIALS, _('Client credentials')),
    )
    default_scopes = ['contrib']

    client_id = db.UUIDField(unique=True, default=uuid.uuid4)
    name = db.CharField(max_length=100)
    user = db.ForeignKeyField(User)
    client_secret = db.CharField(unique=True, max_length=55, index=True)
    redirect_uris = db.ArrayField(db.CharField)
    grant_type = db.CharField(choices=GRANT_TYPES)
    is_confidential = db.BooleanField(default=False)

    @property
    def default_redirect_uri(self):
        return self.redirect_uris[0] if self.redirect_uris else None

    @property
    def allowed_grant_types(self):
        return [id for id, name in self.GRANT_TYPES]
Exemple #9
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'
    ]
    readonly_fields = Model.readonly_fields + ['cia']

    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.ForeignKeyField(PostCode, null=True)

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

    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(str(self.parent.municipality.insee),
                           self.parent.get_fantoir(), self.number,
                           self.ordinal)

    @cached_property
    def municipality(self):
        return Municipality.select().join(
            Group, on=Municipality.pk == self.parent.municipality.pk).first()
Exemple #10
0
class Client(ResourceModel):
    identifiers = ['client_id']
    resource_fields = ['name', 'user', 'scopes', 'contributor_types']

    GRANT_CLIENT_CREDENTIALS = 'client_credentials'
    GRANT_TYPES = ((GRANT_CLIENT_CREDENTIALS, _('Client credentials')), )
    TYPE_IGN = 'ign'
    TYPE_LAPOSTE = 'laposte'
    TYPE_DGFIP = 'dgfip'
    TYPE_ETALAB = 'etalab'
    TYPE_OSM = 'osm'
    TYPE_SDIS = 'sdis'
    TYPE_MUNICIPAL = 'municipal_administration'
    TYPE_ADMIN = 'admin'
    TYPE_DEV = 'develop'
    TYPE_INSEE = 'insee'
    TYPE_VIEWER = 'viewer'
    CONTRIBUTOR_TYPE = (TYPE_SDIS, TYPE_OSM, TYPE_LAPOSTE, TYPE_IGN,
                        TYPE_DGFIP, TYPE_ETALAB, TYPE_MUNICIPAL, TYPE_ADMIN,
                        TYPE_INSEE, TYPE_DEV, TYPE_VIEWER)

    client_id = db.UUIDField(unique=True, default=uuid.uuid4)
    name = db.CharField(max_length=100)
    user = db.ForeignKeyField(User)
    client_secret = db.CharField(unique=True, max_length=55)
    redirect_uris = db.ArrayField(db.CharField)
    grant_type = db.CharField(choices=GRANT_TYPES)
    is_confidential = db.BooleanField(default=False)
    contributor_types = db.ArrayField(db.CharField,
                                      default=[TYPE_VIEWER],
                                      null=True)
    scopes = db.ArrayField(db.CharField, default=[], null=True)

    @property
    def default_redirect_uri(self):
        return self.redirect_uris[0] if self.redirect_uris else None

    @property
    def allowed_grant_types(self):
        return [id for id, name in self.GRANT_TYPES]

    #Necessaire pour OAuth
    @property
    def default_scopes(self):
        return self.scopes

    def save(self, *args, **kwargs):
        if not self.client_secret:
            self.client_secret = generate_secret()
            self.redirect_uris = ['http://localhost/authorize']  # FIXME
            self.grant_type = self.GRANT_CLIENT_CREDENTIALS
        if not self.contributor_types:
            self.contributor_types = ['viewer']
        super().save(*args, **kwargs)
Exemple #11
0
class Position(Model):

    POSTAL = 'postal'
    ENTRANCE = 'entrance'
    BUILDING = 'building'
    STAIRCASE = 'staircase'
    UNIT = 'unit'
    PARCEL = 'parcel'
    SEGMENT = 'segment'
    UTILITY = 'utility'
    KIND = (
        (POSTAL, _('postal delivery')),
        (ENTRANCE, _('entrance')),
        (BUILDING, _('building')),
        (STAIRCASE, _('staircase identifier')),
        (UNIT, _('unit identifier')),
        (PARCEL, _('parcel')),
        (SEGMENT, _('road segment')),
        (UTILITY, _('utility service')),
    )

    resource_fields = ['center', 'source', 'housenumber', 'attributes',
                       'kind', 'comment', 'parent']

    center = db.PointField(verbose_name=_("center"))
    housenumber = db.ForeignKeyField(HouseNumber)
    parent = db.ForeignKeyField('self', related_name='children', null=True)
    source = db.CharField(max_length=64, null=True)
    kind = db.CharField(max_length=64, choices=KIND)
    attributes = db.HStoreField(null=True)
    comment = peewee.TextField(null=True)

    class Meta:
        unique_together = ('housenumber', 'source')

    @property
    def center_resource(self):
        if not isinstance(self.center, Point):
            self.center = Point(*self.center)
        return self.center.geojson
Exemple #12
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.ForeignKeyField(User, null=True)
    client = db.ForeignKeyField(Client, null=True)
    ip = db.CharField(null=True)  # TODO IPField
    email = db.CharField(null=True)  # TODO EmailField

    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
        }

    def save(self, **kwargs):
        if not self.user and not self.client:
            raise ValueError('Session must have either a client or a user')
        super().save(**kwargs)
Exemple #13
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)
Exemple #14
0
class PostCode(NamedModel):
    resource_fields = ['code', 'name', 'alias', 'municipality']

    code = db.PostCodeField(index=True)
    municipality = db.ForeignKeyField(Municipality, related_name='postcodes')

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

    @property
    def housenumbers(self):
        return self.housenumber_set.order_by(
            peewee.SQL('number ASC NULLS FIRST'),
            peewee.SQL('ordinal ASC NULLS FIRST'))
Exemple #15
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.ForeignKeyField(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'))
Exemple #16
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=10, null=True, unique=True)
    ign = db.CharField(max_length=24, null=True, unique=True)
    municipality = db.ForeignKeyField(Municipality, related_name='groups')

    @property
    def tmp_fantoir(self):
        return '#' + re.sub(r'[\W]', '', unidecode(self.name)).upper()

    def get_fantoir(self):
        # Without INSEE code.
        return self.fantoir[5:] if self.fantoir else self.tmp_fantoir

    @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'))
Exemple #17
0
class Client(ResourceModel):
    identifiers = ['client_id']
    resource_fields = ['name', 'user']

    GRANT_AUTHORIZATION_CODE = 'authorization_code'
    GRANT_IMPLICIT = 'implicit'
    GRANT_PASSWORD = '******'
    GRANT_CLIENT_CREDENTIALS = 'client_credentials'
    GRANT_TYPES = (
        # (GRANT_AUTHORIZATION_CODE, _('Authorization code')),
        # (GRANT_IMPLICIT, _('Implicit')),
        (GRANT_PASSWORD, _('Resource owner password-based')),
        (GRANT_CLIENT_CREDENTIALS, _('Client credentials')),
    )
    default_scopes = ['contrib']
    FLAGS = ['ign', 'laposte', 'local_authority']
    FLAG_IDS = tuple((i, i) for i in FLAGS) + (None, 'None')

    client_id = db.UUIDField(unique=True, default=uuid.uuid4)
    name = db.CharField(max_length=100)
    user = db.ForeignKeyField(User)
    client_secret = db.CharField(unique=True, max_length=55)
    redirect_uris = db.ArrayField(db.CharField)
    grant_type = db.CharField(choices=GRANT_TYPES)
    is_confidential = db.BooleanField(default=False)
    flag_id = db.CharField(choices=FLAG_IDS, default=None, null=True)

    @property
    def default_redirect_uri(self):
        return self.redirect_uris[0] if self.redirect_uris else None

    @property
    def allowed_grant_types(self):
        return [id for id, name in self.GRANT_TYPES]

    def save(self, *args, **kwargs):
        if not self.client_secret:
            self.client_secret = generate_secret()
            self.redirect_uris = ['http://localhost/authorize']  # FIXME
            self.grant_type = self.GRANT_CLIENT_CREDENTIALS
        super().save(*args, **kwargs)
Exemple #18
0
class Token(db.Model):
    session = db.ForeignKeyField(Session)
    token_type = db.CharField(max_length=40)
    access_token = db.CharField(max_length=255)
    refresh_token = db.CharField(max_length=255, null=True)
    scope = db.CharField(max_length=255)
    expires = db.DateTimeField()

    def __init__(self, **kwargs):
        expires_in = kwargs.pop('expires_in', 60 * 60)
        kwargs['expires'] = datetime.now() + timedelta(seconds=expires_in)
        super().__init__(**kwargs)

    @property
    def scopes(self):
        # TODO: custom charfield
        return self.scope.split() if self.scope else None

    def is_valid(self, scopes=None):
        """
        Checks if the access token is valid.
        :param scopes: An iterable containing the scopes to check or None
        """
        return not self.is_expired() and self.allow_scopes(scopes)

    def is_expired(self):
        """
        Check token expiration with timezone awareness
        """
        return datetime.now() >= self.expires

    def allow_scopes(self, scopes):
        """
        Check if the token allows the provided scopes
        :param scopes: An iterable containing the scopes to check
        """
        if not scopes:
            return True

        provided_scopes = set(self.scope.split())
        resource_scopes = set(scopes)

        return resource_scopes.issubset(provided_scopes)

    @property
    def user(self):
        return self.session.user

    @classmethod
    def create_with_session(cls, **data):
        if not data.get('ip') and not data.get('email'):
            return None
        if not data.get('client_id'):
            return None
        session_data = {
            "email": data.get('email'),
            "ip": data.get('ip'),
            "client": Client.first(Client.client_id == data['client_id'])
        }
        session = Session.create(**session_data)  # get or create?
        data['session'] = session.id
        return Token.create(**data)
Exemple #19
0
class Token(db.Model):
    session = db.ForeignKeyField(Session)
    token_type = db.CharField(max_length=40)
    access_token = db.CharField(max_length=255)
    refresh_token = db.CharField(max_length=255, null=True)
    scopes = db.ArrayField(db.CharField, default=[], null=True)
    expires = db.DateTimeField()
    contributor_type = db.CharField(choices=Client.CONTRIBUTOR_TYPE, null=True)

    def __init__(self, **kwargs):
        expires_in = kwargs.pop('expires_in', 60 * 60)
        kwargs['expires'] = utcnow() + timedelta(seconds=expires_in)
        super().__init__(**kwargs)

    def is_valid(self, scopes=None):
        """
        Checks if the access token is valid.
        :param scopes: An iterable containing the scopes to check or None
        """
        return not self.is_expired() and self.allow_scopes(scopes)

    def is_expired(self):
        """
        Check token expiration with timezone awareness
        """
        return utcnow() >= self.expires

    def allow_scopes(self, scopes):
        """
        Check if the token allows the provided scopes
        :param scopes: An iterable containing the scopes to check
        """
        if not scopes:
            return True

        provided_scopes = set(self.scope.split())
        resource_scopes = set(scopes)

        return resource_scopes.issubset(provided_scopes)

    @property
    def user(self):
        return self.session.user

    @classmethod
    def create_with_session(cls, **data):
        if not data.get('ip') and not data.get('email'):
            return None, None
        if not data.get('client_id'):
            return None, 'Client id missing'
        client = Client.first(Client.client_id == data['client_id'])
        if len(client.contributor_types) == 0:
            return None, 'Client has none contributor types'
        contributor_type = client.contributor_types[0]
        if data.get('contributor_type'):
            if data.get('contributor_type') not in client.contributor_types:
                return None, 'wrong contributor type : must be in the list {}'.format(
                    client.contributor_types)
        if len(client.contributor_types) > 1:
            if not data.get('contributor_type'):
                return None, 'Contributor type missing'
            contributor_type = data.get('contributor_type')

        session_data = {
            "email": data.get('email'),
            "ip": data.get('ip'),
            "contributor_type": contributor_type,
            "client": client
        }
        session = Session.create(**session_data)  # get or create?
        data['session'] = session.pk
        data['scopes'] = client.scopes
        data['contributor_type'] = session.contributor_type
        if session.contributor_type == Client.TYPE_VIEWER:
            data['scopes'] = None
        return Token.create(**data), None
Exemple #20
0
class Versioned(db.Model, metaclass=BaseVersioned):

    ForcedVersionError = ForcedVersionError

    version = db.IntegerField(default=1)
    created_at = db.DateTimeField()
    created_by = db.ForeignKeyField(Session)
    modified_at = db.DateTimeField()
    modified_by = db.ForeignKeyField(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:  # TODO remove this if, session should be mandatory.
            try:
                getattr(self, 'created_by', None)
            except Session.DoesNotExist:
                # Field is not nullable, we can't access it when it's not yet
                # defined.
                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()
            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)
Exemple #21
0
class Versioned(db.Model, metaclass=BaseVersioned):

    ForcedVersionError = ForcedVersionError

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

    class Meta:
        abstract = True
        validate_backrefs = False
        unique_together = ('id', 'version')

    @classmethod
    def get(cls, *query, **kwargs):
        instance = super().get(*query, **kwargs)
        instance.lock_version()
        return instance

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

    def _serialize(self, fields):
        data = {}
        for name, field in fields.items():
            value = getattr(self, field.name)
            value = field.db_value(value)
            data[field.name] = value
        return data

    def serialize(self, fields=None):
        return dumps(self._serialize(self._meta.fields))

    def store_version(self):
        old = None
        if self.version > 1:
            old = self.load_version(self.version - 1)
        new = Version.create(model_name=self.__class__.__name__,
                             model_id=self.id,
                             sequential=self.version,
                             data=self.serialize())
        if Diff.ACTIVE:
            Diff.create(old=old, new=new, created_at=self.modified_at)

    @property
    def versions(self):
        return Version.select().where(
            Version.model_name == self.__class__.__name__,
            Version.model_id == self.id)

    def load_version(self, id):
        return self.versions.where(Version.sequential == id).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.id:
            self.version = 1
        self._locked_version = self.version if self.id 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:
            try:
                getattr(self, 'created_by', None)
            except Session.DoesNotExist:
                # Field is not nullable, we can't access it when it's not yet
                # defined.
                self.created_by = session
            self.modified_by = session
        now = datetime.now()
        if not self.created_at:
            self.created_at = now
        self.modified_at = now

    def save(self, *args, **kwargs):
        self.check_version()
        self.update_meta()
        super().save(*args, **kwargs)
        self.store_version()
        self.lock_version()
Exemple #22
0
class Position(Model):

    POSTAL = 'postal'
    ENTRANCE = 'entrance'
    BUILDING = 'building'
    STAIRCASE = 'staircase'
    UNIT = 'unit'
    PARCEL = 'parcel'
    SEGMENT = 'segment'
    UTILITY = 'utility'
    UNKNOWN = 'unknown'
    AREA = 'area'
    KIND = (
        (POSTAL, _('postal delivery')),
        (ENTRANCE, _('entrance')),
        (BUILDING, _('building')),
        (STAIRCASE, _('staircase identifier')),
        (UNIT, _('unit identifier')),
        (PARCEL, _('parcel')),
        (SEGMENT, _('road segment')),
        (UTILITY, _('utility service')),
        (AREA, _('area')),
        (UNKNOWN, _('unknown')),
    )

    DGPS = 'dgps'
    GPS = 'gps'
    IMAGERY = 'imagery'
    PROJECTION = 'projection'
    INTERPOLATION = 'interpolation'
    OTHER = 'other'
    POSITIONING = (
        (DGPS, _('via differencial GPS')),
        (GPS, _('via GPS')),
        (IMAGERY, _('via imagery')),
        (PROJECTION, _('computed via projection')),
        (INTERPOLATION, _('computed via interpolation')),
        (OTHER, _('other')),
    )

    identifiers = ['laposte', 'ign']
    resource_fields = [
        'center', 'source', 'housenumber', 'kind', 'comment', 'parent',
        'positioning', 'name', 'ign', 'laposte'
    ]

    name = db.CharField(max_length=200, null=True)
    center = db.PointField(verbose_name=_("center"), null=True, index=True)
    housenumber = db.ForeignKeyField(HouseNumber, related_name='positions')
    parent = db.ForeignKeyField('self', related_name='children', null=True)
    source = db.CharField(max_length=64, null=True)
    kind = db.CharField(max_length=64, choices=KIND)
    positioning = db.CharField(max_length=32, choices=POSITIONING)
    ign = db.CharField(max_length=24, null=True, unique=True)
    laposte = db.CharField(max_length=10, null=True, unique=True)
    comment = db.TextField(null=True)

    class Meta:
        indexes = ((('housenumber', 'source'), True), )

    @classmethod
    def validate(cls, validator, document, instance):
        errors = {}
        default = instance and validator.update and instance.name
        name = document.get('name', default)
        default = instance and validator.update and instance.center
        center = document.get('center', default)
        if not name and not center:
            msg = 'A position must have either a center or a name.'
            errors['center'] = msg
            errors['name'] = msg
        return errors