class Role(CacheableMixin, db.Model, RoleMixin, MarshalMixin): cache_label = 'users' query_class = query_callable() id = db.Column(db.Integer(), primary_key=True) name = db.Column(db.String(80), unique=True) description = db.Column(db.String(255))
class ZUPC(db.Model, MarshalMixin, CacheableMixin): cache_label = 'zupc' query_class = query_callable() id = Column(db.Integer, primary_key=True) departement_id = Column(db.Integer, db.ForeignKey('departement.id')) nom = Column(db.String(255), label='Nom') insee = Column(db.String(), nullable=True) shape = Column(Geography(geometry_type='MULTIPOLYGON', srid=4326, spatial_index=False), label='Geography of the shape') departement = db.relationship('Departement', backref=db.backref('departements'), lazy='joined') parent_id = Column(db.Integer, db.ForeignKey('ZUPC.id')) parent = db.relationship('ZUPC', remote_side=[id], lazy='joined') active = Column(db.Boolean, default=False) __geom = None __preped_geom = None __bounds = None def __repr__(self): return '<ZUPC %r>' % str(self.id) def __str__(self): return self.nom @property def geom(self): if self.__geom is None: self.__geom = to_shape(self.shape) return self.__geom @property def preped_geom(self): if self.__preped_geom is None: self.__preped_geom = prep(self.geom) return self.__preped_geom @property def bounds(self): if not self.__bounds: self.__bounds = self.geom.bounds return self.__bounds @property def bottom(self): return self.bounds[1] @property def left(self): return self.bounds[0] @property def top(self): return self.bounds[3] @property def right(self): return self.bounds[2]
class VehicleDescription(HistoryMixin, CacheableMixin, db.Model, AsDictMixin): @declared_attr def added_by(cls): return Column(db.Integer, db.ForeignKey('user.id')) cache_label = 'taxis' query_class = query_callable() def __init__(self, vehicle_id, added_by): db.Model.__init__(self) HistoryMixin.__init__(self) self.vehicle_id = vehicle_id self.added_by = added_by id = Column(db.Integer, primary_key=True) model_id = Column(db.Integer, db.ForeignKey("model.id")) __model = db.relationship('Model', lazy='joined') constructor_id = Column(db.Integer, db.ForeignKey("constructor.id")) __constructor = db.relationship('Constructor', lazy='joined') model_year = Column(db.Integer, label=u'Année', nullable=True, description=u'Année de mise en production du véhicule') engine = Column(db.String(80), label=u'Motorisation', nullable=True, description=u'Motorisation du véhicule, champ P3') horse_power = Column( db.Float(), label=u'Puissance', nullable=True, description=u'Puissance du véhicule en chevaux fiscaux') relais = Column(db.Boolean, label=u'Relais', default=False, nullable=True, description=u'Est-ce un véhicule relais') horodateur = Column(db.String(255), label=u'Horodateur', nullable=True, description=u'Modèle de l\'horodateur') taximetre = Column(db.String(255), label=u'Taximètre', nullable=True, description=u'Modèle du taximètre') date_dernier_ct = Column( db.Date(), label=u'Date du dernier CT (format année-mois-jour)', description=u'Date du dernier contrôle technique') date_validite_ct = Column( db.Date(), label=u'Date de la fin de validité du CT (format année-mois-jour)', description=u'Date de fin de validité du contrôle technique') special_need_vehicle = Column( db.Boolean, name='special_need_vehicle', label=u'Véhicule spécialement aménagé pour PMR ', nullable=True) type_ = Column(Enum('sedan', 'mpv', 'station_wagon', 'normal', name='vehicle_type_enum'), label='Type', nullable=True) luxury = Column(db.Boolean, name='luxury', label='Luxe ?', nullable=True) credit_card_accepted = Column(db.Boolean, name='credit_card_accepted', label=u'Carte bancaire acceptée ?', nullable=True) nfc_cc_accepted = Column( db.Boolean, name='nfc_cc_accepted', label=u'Paiement sans contact sur carte bancaire accepté ?', nullable=True) amex_accepted = Column(db.Boolean, name='amex_accepted', label=u'AMEX acceptée ?', nullable=True) bank_check_accepted = Column(db.Boolean, name='bank_check_accepted', label=u'Chèque bancaire accepté ?', nullable=True) fresh_drink = Column(db.Boolean, name='fresh_drink', label=u'Boisson fraiche ?', nullable=True) dvd_player = Column(db.Boolean, name='dvd_player', label='Lecteur DVD ?', nullable=True) tablet = Column(db.Boolean, name='tablet', label='Tablette ?', nullable=True) wifi = Column(db.Boolean, name='wifi', label=u'Wifi à bord ?', nullable=True) baby_seat = Column(db.Boolean, name='baby_seat', label=u'Siège bébé ?', nullable=True) bike_accepted = Column(db.Boolean, name='bike_accepted', label=u'Transport de vélo', nullable=True) pet_accepted = Column(db.Boolean, name='pet_accepted', label=u'Animaux de compagnie acceptés ?', nullable=True) air_con = Column(db.Boolean, name='air_con', label=u'Véhicule climatisé', nullable=True) electronic_toll = Column(db.Boolean, name='electronic_toll', label=u'Véhicule équipé du télépéage', nullable=True) gps = Column(db.Boolean, name='gps', label=u'Véhicule équipé d\'un GPS', nullable=True) cpam_conventionne = Column(db.Boolean, name='cpam_conventionne', label=u'Conventionné assurance maladie', nullable=True) every_destination = Column(db.Boolean, name='every_destination', label=u'Toute destination', nullable=True) color = Column(db.String(255), name='color', label='Couleur : ', nullable=True) vehicle_id = Column(db.Integer, db.ForeignKey('vehicle.id')) UniqueConstraint('vehicle_id', 'added_by', name="uq_vehicle_description") _status = Column(Enum(*status_vehicle_description_enum, name='status_taxi_enum'), nullable=True, default='free', name='status') nb_seats = Column( db.Integer, name='nb_seats', description=u'Nombre de places assises disponibles pour les voyageurs', label=u'Nombre de places') __table_args__ = (db.UniqueConstraint('vehicle_id', 'added_by', name="_uq_vehicle_description"), ) @property def status(self): return self._status @status.setter def status(self, value): assert value is None or value == 'None' or value in status_vehicle_description_enum,\ '{} is not a valid status, (valid statuses are {})'\ .format(value, status_vehicle_description_enum) self._status = value from .taxis import Taxi operator = User.query.get(self.added_by) for t in Taxi.query.join(Taxi.vehicle, aliased=True).filter_by(id=self.vehicle_id): t.set_avaibility(operator.email, self._status) @classmethod def to_exclude(cls): return list(HistoryMixin.to_exclude()) + ['status'] @property def constructor(self): return self.__constructor.name @constructor.setter def constructor(self, name): self.__constructor = Constructor(name) @property def model(self): return self.__model.name @model.setter def model(self, name): self.__model = Model(name) @property def characteristics(self): return VehicleDescription.get_characs(lambda o, f: getattr(o, f), self) @staticmethod def get_characs(getattr_, obj): fields = [ 'special_need_vehicle', 'every_destination', 'gps', 'electronic_toll', 'air_con', 'pet_accepted', 'bike_accepted', 'baby_seat', 'wifi', 'tablet', 'dvd_player', 'fresh_drink', 'amex_accepted', 'bank_check_accepted', 'nfc_cc_accepted', 'credit_card_accepted', 'luxury' ] return list(compress(fields, map(lambda f: getattr_(obj, f), fields)))
class Vehicle(CacheableMixin, db.Model, AsDictMixin, MarshalMixin, FilterOr404Mixin): cache_label = 'taxis' query_class = query_callable() id = Column(db.Integer, primary_key=True) licence_plate = Column(db.String(80), label=u'Immatriculation', description=u'Immatriculation du véhicule', unique=True) descriptions = db.relationship("VehicleDescription", lazy='joined') def __init__(self, licence_plate=None): if isinstance(licence_plate, self.__class__): self.licence_plate = licence_plate.licence_plate else: self.licence_plate @classmethod def marshall_obj(cls, show_all=False, filter_id=False, level=0, api=None): if level >= 2: return {} return_ = super(Vehicle, cls).marshall_obj(show_all, filter_id, level=level + 1, api=api) dict_description = VehicleDescription.marshall_obj(show_all, filter_id, level=level + 1, api=api) for k, v in dict_description.items(): dict_description[k].attribute = 'description.{}'.format(k) return_.update(dict_description) return_.update({ "model": fields.String(attribute="description.model"), "constructor": fields.String(attribute="description.constructor") }) if not filter_id: return_["id"] = fields.Integer() return return_ @property def description(self): return self.get_description() def get_description(self, user=None): if not user: user = current_user returned_description = None for description in self.descriptions: if description.added_by == user.id: returned_description = description return returned_description @property def model(self): return self.description.model if self.description else None @property def constructor(self): return self.description.constructor.name if self.description else None @property def model_year(self): return self.description.model_year if self.description else None @property def engine(self): return self.description.engine if self.description else None @property def horse_power(self): return self.description.horse_power if self.description else None @property def relais(self): return self.description.relais if self.description else None @property def horodateur(self): return self.description.horodateur if self.description else None @property def taximetre(self): return self.description.taximetre if self.description else None @property def date_dernier_ct(self): return self.description.date_dernier_ct if self.description else None @property def date_validite_ct(self): return self.description.date_validite_ct if self.description else None @property def type_(self): return self.description.type_ if self.description else None def __repr__(self): return '<Vehicle %r>' % unicode(self.id) def __eq__(self, other): return self.__repr__() == other.__repr__() def __ne__(self, other): return not self.__eq__(other) @classmethod def to_exclude(cls): columns = list( filter(lambda f: isinstance(getattr(HistoryMixin, f), Column), HistoryMixin.__dict__.keys())) columns += ["Vehicle", "vehicle_taxi", "descriptions"] return columns
class Hail(HistoryMixin, CacheableMixin, db.Model, AsDictMixin, GetOr404Mixin): @declared_attr def added_by(cls): return db.Column(db.Integer, db.ForeignKey('user.id')) cache_label = 'hails' query_class = query_callable() public_fields = [ 'creation_datetime', 'customer_address', 'customer_id', 'customer_lat', 'customer_lon', 'customer_phone_number', 'id', 'incident_customer_reason', 'incident_taxi_reason', 'last_status_change', 'operateur', 'rating_ride', 'rating_ride_reason', 'reporting_customer', 'reporting_customer_reason', 'status', 'taxi', 'taxi_phone_number' ] id = db.Column(db.String, primary_key=True) creation_datetime = db.Column(db.DateTime, nullable=False) operateur_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) _operateur = db.relationship('User', primaryjoin=(operateur_id == User.id), lazy='joined') customer_id = db.Column(db.String, nullable=False) customer_lon = db.Column(db.Float, nullable=False) customer_lat = db.Column(db.Float, nullable=False) customer_address = db.Column(db.String, nullable=False) customer_phone_number = db.Column(db.String, nullable=False) taxi_id = db.Column(db.String, db.ForeignKey('taxi.id'), nullable=False) taxi_relation = db.relationship('Taxi', backref="taxi", lazy="joined") _status = db.Column(db.Enum(*status_enum_list, name='hail_status'), default='emitted', nullable=False, name='status') last_status_change = db.Column(db.DateTime) db.ForeignKeyConstraint( ['operateur_id', 'customer_id'], ['customer.operateur_id', 'customer.id'], ) taxi_phone_number = db.Column(db.String, nullable=True) rating_ride = db.Column(db.Integer) rating_ride_reason = db.Column(db.Enum(*rating_ride_reason_enum, name='reason_ride_enum'), nullable=True) incident_customer_reason = db.Column(db.Enum( *incident_customer_reason_enum, name='incident_customer_reason_enum'), nullable=True) incident_taxi_reason = db.Column(db.Enum(*incident_taxi_reason_enum, name='incident_taxi_reason_enum'), nullable=True) # Reporting of the customer by the taxi reporting_customer = db.Column(db.Boolean, nullable=True) reporting_customer_reason = db.Column( db.Enum(*reporting_customer_reason_enum, name='reporting_customer_reason_enum'), nullable=True) initial_taxi_lon = db.Column(db.Float, nullable=True) initial_taxi_lat = db.Column(db.Float, nullable=True) change_to_sent_to_operator = db.Column(db.DateTime, nullable=True) change_to_received_by_operator = db.Column(db.DateTime, nullable=True) change_to_received_by_taxi = db.Column(db.DateTime, nullable=True) change_to_accepted_by_taxi = db.Column(db.DateTime, nullable=True) change_to_accepted_by_customer = db.Column(db.DateTime, nullable=True) change_to_declined_by_taxi = db.Column(db.DateTime, nullable=True) change_to_declined_by_customer = db.Column(db.DateTime, nullable=True) change_to_incident_taxi = db.Column(db.DateTime, nullable=True) change_to_incident_customer = db.Column(db.DateTime, nullable=True) change_to_timeout_taxi = db.Column(db.DateTime, nullable=True) change_to_timeout_customer = db.Column(db.DateTime, nullable=True) change_to_failure = db.Column(db.DateTime, nullable=True) def __init__(self, *args, **kwargs): self.id = str(get_short_uuid()) self.creation_datetime = datetime.now().isoformat() db.Model.__init__(self) HistoryMixin.__init__(self) super(self.__class__, self).__init__(**kwargs) @validates('rating_ride_reason') def validate_rating_ride_reason(self, key, value): #We need to restrict this to a subset of statuses assert value is None or value in rating_ride_reason_enum,\ 'Bad rating_ride_reason\'s value. It can be: {}'.format( rating_ride_reason_enum) if current_user.id != self.added_by: raise RuntimeError() return value @validates('incident_customer_reason') def validate_incident_customer_reason(self, key, value): assert self.status == 'incident_customer', 'Bad status' assert value is None or value in incident_customer_reason_enum,\ 'Bad rating_ride_reason\'s value. It can be: {}'.format( incident_customer_reason_enum) if current_user.id != self.added_by: raise RuntimeError() return value @validates('incident_taxi_reason') def validate_incident_taxi_reason(self, key, value): assert self.status == 'incident_taxi', 'Bad status' assert value is None or value in incident_taxi_reason_enum,\ 'Bad rating_ride_reason\'s value. It can be: {}'.format( incident_taxi_reason_enum) if current_user.id != self.operateur_id: raise RuntimeError() return value @validates('reporting_customer_reason') def validate_reporting_customer_reason(self, key, value): assert value is None or value in reporting_customer_reason_enum,\ 'Bad reporting_customer_reason\'s value. It can be: {}'.format( reporting_customer_reason_enum) if current_user.id != self.operateur_id: raise RuntimeError() return value @validates('reporting_customer') def validate_reporting_customer(self, key, value): if current_user.id != self.operateur_id: raise RuntimeError() return value @validates('rating_ride') def validate_rating_taxi(self, key, value): #We need to restrict this to a subset of statuses assert 1 <= value <= 5, 'Rating value has to be 1 <= value <= 5' return value timeouts = { 'received': (15, 'failure'), 'sent_to_operator': (10, 'failure'), 'received_by_operator': (10, 'failure'), 'received_by_taxi': (30, 'timeout_taxi'), 'accepted_by_taxi': (20, 'timeout_customer') } roles_accepted = { 'received': ['moteur', 'admin'], 'received_by_taxi': ['operateur', 'admin'], 'accepted_by_taxi': ['operateur', 'admin'], 'declined_by_taxi': ['operateur', 'admin'], 'incident_taxi': ['operateur', 'admin'], 'incident_customer': ['moteur', 'admin'], 'accepted_by_customer': ['moteur', 'admin'], 'declined_by_customer': ['moteur', 'admin'], } status_required = { 'sent_to_operator': 'received', 'received_by_operator': 'received', 'received_by_taxi': 'received_by_operator', 'accepted_by_taxi': 'received_by_taxi', 'declined_by_taxi': 'received_by_taxi', 'accepted_by_customer': 'accepted_by_taxi', 'declined_by_customer': 'accepted_by_taxi', } @property def status(self): time, next_status = self.timeouts.get(self._status, (None, None)) if time: self.check_time_out(time, next_status) return self._status @status.setter def status(self, value): old_status = self._status assert value in status_enum_list if value == self._status: return True roles_accepted = self.roles_accepted.get(value, None) if roles_accepted: perm = Permission(*[RoleNeed(role) for role in roles_accepted]) if not perm.can(): raise RuntimeError("You're not authorized to set this status") status_required = self.status_required.get(value, None) if status_required and self._status != status_required: raise ValueError("You cannot set status from {} to {}".format( self._status, value)) self._status = value self.status_changed() taxi = TaxiM.cache.get(self.taxi_id) taxi.synchronize_status_with_hail(self) client = influx_db.get_client(current_app.config['INFLUXDB_TAXIS_DB']) try: client.write_points([{ "measurement": "hails_status_changed", "tags": { "added_by": User.query.get(self.added_by).email, "operator": self.operateur.email, "zupc": taxi.ads.zupc.insee, "previous_status": old_status, "status": self._status }, "time": datetime.utcnow().strftime('%Y%m%dT%H:%M:%SZ'), "fields": { "value": 1 } }]) except Exception as e: current_app.logger.error('Influxdb Error: {}'.format(e)) def status_changed(self): self.last_status_change = datetime.now() field = 'change_to_{}'.format(self.status) if hasattr(self, field): setattr(self, field, self.last_status_change) def check_time_out(self, duration, timeout_status): if datetime.now() < (self.last_status_change + timedelta(seconds=duration)): return True self.status = timeout_status db.session.commit() return False def to_dict(self): self.check_time_out() return self.as_dict() @property def taxi(self): carac = TaxiM.retrieve_caracs(self.taxi_id).get( self.operateur.email, None) if not carac: return {} return { 'position': { 'lon': carac['lon'], 'lat': carac['lat'] }, 'last_update': carac['timestamp'], 'id': self.taxi_id } @property def operateur(self): return User.query.get(self.operateur_id)
class Taxi(CacheableMixin, db.Model, HistoryMixin, AsDictMixin, GetOr404Mixin, TaxiRedis): @declared_attr def added_by(cls): return Column(db.Integer, db.ForeignKey('user.id')) cache_label = 'taxis' query_class = query_callable() def __init__(self, *args, **kwargs): db.Model.__init__(self) HistoryMixin.__init__(self) kwargs['id'] = kwargs.get('id', None) if not kwargs['id']: kwargs['id'] = str(get_short_uuid()) super(self.__class__, self).__init__(**kwargs) HistoryMixin.__init__(self) TaxiRedis.__init__(self, self.id) id = Column(db.String, primary_key=True) vehicle_id = db.Column(db.Integer, db.ForeignKey('vehicle.id'), nullable=True) vehicle = db.relationship('Vehicle', backref='vehicle_taxi', lazy='joined') ads_id = db.Column(db.Integer, db.ForeignKey('ADS.id'), nullable=True) ads = db.relationship('ADS', backref='ads', lazy='joined') driver_id = db.Column(db.Integer, db.ForeignKey('driver.id'), nullable=True) driver = db.relationship('Driver', backref='driver', lazy='joined') _ACTIVITY_TIMEOUT = 15 * 60 #Used for dash @property def rating(self): return 4.5 @property def status(self): return self.vehicle.description.status @status.setter def status(self, status): self.vehicle.description.status = status self.last_update_at = datetime.now() def is_free(self, min_time=None): return self._is_free(self.vehicle.descriptions, lambda desc: User.query.get(desc.added_by).email, lambda desc: desc.status, min_time) def set_free(self): #For debugging purposes for desc in self.vehicle.descriptions: desc.status = 'free' @property def driver_professional_licence(self): return self.driver.professional_licence @property def vehicle_licence_plate(self): return self.vehicle.licence_plate @property def ads_numero(self): return self.ads.numero @property def driver_insee(self): return self.ads.insee @property def driver_departement(self): return self.driver.departement map_hail_status_taxi_status = { 'emitted': 'free', 'received': 'answering', 'sent_to_operator': 'answering', 'received_by_operator': 'answering', 'received_by_taxi': 'answering', 'accepted_by_taxi': 'answering', 'accepted_by_customer': 'oncoming', 'declined_by_taxi': 'free', 'declined_by_customer': 'free', 'incident_customer': 'free', 'incident_taxi': 'free', 'timeout_customer': 'free', 'timeout_taxi': 'free', 'outdated_customer': 'free', 'outdated_taxi': 'free', 'failure': 'free' } def synchronize_status_with_hail(self, hail): description = self.vehicle.get_description(hail.operateur) description.status = self.map_hail_status_taxi_status[hail.status] self.last_update_at = datetime.now() RawTaxi.flush(self.id)
class User(CacheableMixin, db.Model, UserMixin, MarshalMixin, FilterOr404Mixin, UserBase): cache_label = 'users' query_class = query_callable() id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(255), unique=True) password = db.Column(db.String(255)) active = db.Column(db.Boolean()) confirmed_at = db.Column(db.DateTime()) roles = db.relationship('Role', secondary=roles_users, lazy='joined', query_class=query_callable()) apikey = db.Column(db.String(36), nullable=False) hail_endpoint_production = Column(db.String, nullable=True, label=u'Hail endpoint production', description='Hail endpoint production') hail_endpoint_staging = Column(db.String, nullable=True, label=u'Hail endpoint staging', description='Hail endpoint staging') hail_endpoint_testing = Column(db.String, nullable=True, label=u'Hail endpoint testing', description='Hail endpoint testing') commercial_name = Column(db.String, nullable=True, label='Nom commercial', description='Votre nom commercial') phone_number_customer = Column( db.String, nullable=True, label=u'Numéro de téléphone du service client', description=u'Numéro de téléphone de support pour les clients') phone_number_technical = Column( db.String, nullable=True, label=u'Numéro de téléphone du contact technique', description=u'Numéro de téléphone du contact technique') email_customer = Column(db.String, nullable=True, label=u'Email du service client', description=u'Email de support pour les clients') email_technical = Column(db.String, nullable=True, label=u'Email du contact technique', description=u'Email du contact technique') logos = db.relationship('Logo', backref="user", lazy='joined') operator_header_name = Column( db.String, nullable=True, label=u'Nom du header http pour l\'authentification', description=u"""Cet header sera envoyé lors de la communication de l'ODT vers votre serveur""") operator_api_key = Column( db.String, nullable=True, label=u'Valeur de la clé d\'api', description=u"""Valeur de la clé d'api envoyé par l'ODT à votre serveur pour l'authentification.""") def __init__(self, *args, **kwargs): kwargs['apikey'] = str(uuid.uuid4()) kwargs['password'] = encrypt_password(kwargs['password']) kwargs['active'] = True super(self.__class__, self).__init__(*args, **kwargs)