class T1: id = Integer(primary_key=True) code = String() val = Integer() parent = Many2One(model='Model.T1')
class Room: id = Integer(primary_key=True) name = String(label="Room name", nullable=False, index=True) capacity = Integer(label="Capacity", nullable=False)
class Person: name = String(primary_key=True) invoiced_addresses = Many2Many(model=Model.Address) delivery_addresses = Many2Many(model=Model.Address)
class Test: id = Integer(primary_key=True) name = String()
class User: """User declaration need for Auth""" login = String(primary_key=True, nullable=False) @classmethod def get_roles(cls, login): """Return the roles of an user :param login: str, login attribute of the user :rtype: list of str (name of the roles) """ # cache the method roles = [login] user = cls.query().filter(cls.login == login).one_or_none() if user: for role in user.roles: roles.extend(role.roles_name) return list(set(roles)) @classmethod def get_acl(cls, login, resource, params=None): """Retun the ACL for a ressource and a user Auth, does not implement any rule to compute ACL, This method allow all user to use the resource ask by controllers. For other configuration, this method must be overwrite :param login: str, login attribute of the user :param resource: str, name of a resource :param params: all options need to compute ACL """ # cache the method return [(Allow, login, ALL_PERMISSIONS)] @classmethod def format_login_params(cls, request): """Return the login and password from query By default the query come from json_body and are named **login** and **password** If the entries come from another place, this method must be overwrite :param request: the request from the controllers """ return request.json_body @classmethod def check_login(cls, login=None, password=None, **kwargs): """Check login / password This method raise an exception, because any credential is stored in this bloks .. warning:: This method must be overwriting by anycredential blok :param login: str, the login attribute of the user :param password: str :param kwargs: any options need to validate credential """ raise HTTPUnauthorized() @classmethod def get_login_location_to(cls, login, request): """Return the default path after the login""" return '/' @classmethod def get_logout_location_to(cls, request): """Return the default path after the logout""" return '/'
class Guest: id = Integer(primary_key=True) name = String(nullable=False) def __repr__(self): return "<Guest(name={self.name!r})>".format(self=self)
class Question: id = Integer(primary_key=True) name = String(label="Name", nullable=False) survey_id = Integer(foreign_key="Model.Survey=>id") position = Integer(label="Position", nullable=True)
class Test2: id = Integer(primary_key=True) name = String() test_id = Integer(foreign_key=Model.Test.use('id'))
class Test2: id = Integer(primary_key=True) name = String() test = One2One(model=Model.Test, backref='test2')
class Goods: """Main data type to represent physical objects managed by the system. The instances of this model are also the ultimate representation of the Goods "staying the same" or "becoming different" under the Operations, which is, ultimately, a subjective decision that has to be left to downstream libraires and applications, or even end users. For instance, everybody agrees that moving something around does not make it different. Therefore, the Move Operation uses the same Goods record in its outcome as in its input. On the other hand, changing a property could be considered enough an alteration of the physical object to consider it different, or not (think of recording some measurement that had not be done earlier.) """ id = Integer(label="Identifier", primary_key=True) """Primary key.""" type = Many2One(model='Model.Wms.Goods.Type', nullable=False, index=True) """The :class:`Goods Type <.Type>`""" code = String(label="Identifying code", index=True) """Uniquely identifying code. This should be about what one is ready to display as a barcode for handling the Goods. It's also meant to be shared with other applications if needed (rather than ids which are only locally unique). """ properties = Many2One(label="Properties", index=True, model='Model.Wms.Goods.Properties') """Link to :class:`Properties`. .. seealso:: :ref:`goods_properties` for functional aspects. .. warning:: don't ever mutate the contents of :attr:`properties` directly, unless what you want is precisely to affect all the Goods records that use them directly. Besides their :attr:`type` and the fields meant for the Wms Base bloks logic, the Goods Model bears flexible data, called *properties*, that are to be manipulated as key/value pairs through the :meth:`get_property` and :meth:`set_property` methods. As far as ``wms_core`` is concerned, values of properties can be of any type, yet downstream applications and libraries can choose to make them direct fields of the :class:`Properties` model. Properties can be shared among several Goods records, for efficiency. The :meth:`set_property` implements the necessary Copy-on-Write mechanism to avoid unintentionnally modify the properties of many Goods records. Technically, this data is deported into the :class:`Properties` Model (see there on how to add additional properties). The properties column value can be None, so that we don't pollute the database with empty lines of Property records, although this is subject to change in the future. """ def __str__(self): return "(id={self.id}, type={self.type})".format(self=self) def __repr__(self): return "Wms.Goods(id={self.id}, type={self.type!r})".format(self=self) def get_property(self, k, default=None): """Property getter, works like :meth:`dict.get`. Actually I'd prefer to simply implement the dict API, but we can't direcly inherit from UserDict yet. This is good enough to provide the abstraction needed for current internal wms_core calls. """ if self.properties is None: return default return self.properties.get(k, default) def set_property(self, k, v): """Property setter. See remarks on :meth:`get_property`. This method implements a simple Copy-on-Write mechanism. Namely, if the properties are referenced by other Goods records, it will duplicate them before actually setting the wished value. """ existing_props = self.properties if existing_props is None: self.properties = self.registry.Wms.Goods.Properties( flexible=dict()) elif existing_props.get(k) != v: cls = self.__class__ if cls.query(cls.id).filter(cls.properties == existing_props, cls.id != self.id).limit(1).count(): self.properties = existing_props.duplicate() self.properties.set(k, v)
class Test2: id = Integer(primary_key=True) name = String() test = Many2One(model=Model.Test, one2many="test2")
class Pizza(Resource): id = Integer(primary_key=True, foreign_key=Resource.use('id').options(ondelete='cascade')) name = String(nullable=False)
class T2: id = Integer(primary_key=True) code = String() val = Integer()
class TestView: code = String(primary_key=True) val1 = Integer() val2 = Integer()
class Engineer(Model.Employee): id = Integer(primary_key=True, foreign_key=Model.Employee.use('id')) engineer_name = String()
class Test2: id = Integer(primary_key=True) name = String() test = Many2Many(model=Model.Test, many2many='test2')
class Track: id = Integer(primary_key=True) name = String(label="Name", nullable=False)
class Test(Mixin.ForbidUpdate): id = Integer(primary_key=True) name = String()
class TodoList: id = Integer(primary_key=True) name = String(label="Name", unique=True, nullable=False)
class Test(Mixin.ReadOnly): id = Integer(primary_key=True) name = String()
class Mapping: key = String(primary_key=True) model = String(primary_key=True, foreign_key=Model.System.Model.use('name')) primary_key = Json(nullable=False) blokname = String(label="Blok name", foreign_key=Model.System.Blok.use('name')) @hybrid_method def filter_by_model_and_key(self, model, key): """ SQLAlechemy hybrid method to filter by model and key :param model: model of the mapping :param key: external key of the mapping """ return (self.model == model) & (self.key == key) @hybrid_method def filter_by_model_and_keys(self, model, *keys): """ SQLAlechemy hybrid method to filter by model and key :param model: model of the mapping :param key: external key of the mapping """ return (self.model == model) & self.key.in_(keys) def remove_element(self, byquery=False): val = self.registry.get(self.model).from_primary_keys( **self.primary_key) logger.info("Remove entity for %r.%r: %r" % ( self.model, self.key, val)) val.delete(byquery=byquery, remove_mapping=False) @classmethod def multi_delete(cls, model, *keys, **kwargs): """ Delete all the keys for this model :param model: model of the mapping :param \*keys: list of the key :rtype: Boolean True if the mappings are removed """ mapping_only = kwargs.get('mapping_only', True) byquery = kwargs.get('byquery', False) query = cls.query() query = query.filter(cls.filter_by_model_and_keys(model, *keys)) count = query.count() if count: if not mapping_only: query.all().remove_element(byquery=byquery) query.delete(synchronize_session='fetch', remove_mapping=False) cls.registry.expire_all() return count return 0 @classmethod def delete(cls, model, key, mapping_only=True, byquery=False): """ Delete the key for this model :param model: model of the mapping :param key: string of the key :rtype: Boolean True if the mapping is removed """ query = cls.query() query = query.filter(cls.filter_by_model_and_key(model, key)) count = query.count() if count: if not mapping_only: query.one().remove_element(byquery=byquery) query.delete(remove_mapping=False) return count return 0 @classmethod def get_mapping_primary_keys(cls, model, key): """ return primary key for a model and an external key :param model: model of the mapping :param key: string of the key :rtype: dict primary key: value or None """ query = cls.query() query = query.filter(cls.filter_by_model_and_key(model, key)) if query.count(): pks = query.first().primary_key cls.check_primary_keys(model, *pks.keys()) return pks return None @classmethod def check_primary_keys(cls, model, *pks): """ check if the all the primary keys match with primary keys of the model :param model: model to check :param pks: list of the primary keys to check :exception: IOMappingCheckException """ for pk in cls.get_model(model).get_primary_keys(): if pk not in pks: raise IOMappingCheckException( "No primary key %r found in %r for model %r" % ( pk, pks, model)) @classmethod def set_primary_keys(cls, model, key, pks, raiseifexist=True, blokname=None): """ Add or update a mmping with a model and a external key :param model: model to check :param key: string of the key :param pks: dict of the primary key to save :param raiseifexist: boolean (True by default), if True and the entry exist then an exception is raised :param blokname: name of the blok where come from the mapping :exception: IOMappingSetException """ if cls.get_mapping_primary_keys(model, key): if raiseifexist: raise IOMappingSetException( "One value found for model %r and key %r" % (model, key)) cls.delete(model, key) if not pks: raise IOMappingSetException( "No value to save %r for model %r and key %r" % ( pks, model, key)) cls.check_primary_keys(model, *pks.keys()) vals = dict(model=model, key=key, primary_key=pks) if blokname is not None: vals['blokname'] = blokname return cls.insert(**vals) @classmethod def set(cls, key, instance, raiseifexist=True, blokname=None): """ Add or update a mmping with a model and a external key :param model: model to check :param key: string of the key :param instance: instance of the model to save :param raiseifexist: boolean (True by default), if True and the entry exist then an exception is raised :param blokname: name of the blok where come from the mapping :exception: IOMappingSetException """ pks = instance.to_primary_keys() return cls.set_primary_keys(instance.__registry_name__, key, pks, blokname=blokname, raiseifexist=raiseifexist) @classmethod def get(cls, model, key): """ return instance of the model with this external key :param model: model of the mapping :param key: string of the key :rtype: instance of the model """ pks = cls.get_mapping_primary_keys(model, key) if pks is None: return None return cls.get_model(model).from_primary_keys(**pks) @classmethod def get_from_model_and_primary_keys(cls, model, pks): query = cls.query().filter(cls.model == model) for mapping in query.all(): if mapping.primary_key == pks: return mapping return None @classmethod def get_from_entry(cls, entry): return cls.get_from_model_and_primary_keys( entry.__registry_name__, entry.to_primary_keys()) @classmethod def __get_models(cls, models): """Return models name if models is not: return all the existing model if models is a list of instance model, convert them :params models: list of model """ if models is None: models = cls.registry.System.Model.query().all().name elif not isinstance(models, (list, tuple)): models = [models] return [m.__registry_name__ if hasattr(m, '__registry_name__') else m for m in models] @classmethod def clean(cls, bloknames=None, models=None): """Clean all mapping with removed object linked:: Mapping.clean(bloknames=['My blok']) .. warning:: For filter only the no blokname:: Mapping.clean(bloknames=[None]) :params bloknames: filter by blok, keep the order to remove the mapping :params models: filter by model, keep the order to remove the mapping """ if bloknames is None: bloknames = cls.registry.System.Blok.query().all().name + [None] elif not isinstance(bloknames, (list, tuple)): bloknames = [bloknames] models = cls.__get_models(models) removed = 0 for blokname in bloknames: for model in models: query = cls.query().filter_by(blokname=blokname, model=model) for key in query.all().key: if cls.get(model, key) is None: cls.delete(model, key) removed += 1 return removed @classmethod def delete_for_blokname(cls, blokname, models=None, byquery=False): """Clean all mapping with removed object linked:: Mapping.clean('My blok') .. warning:: For filter only the no blokname:: Mapping.clean(None) :params blokname: filter by blok :params models: filter by model, keep the order to remove the mapping """ models = cls.__get_models(models) removed = 0 for model in models: query = cls.query().filter_by(blokname=blokname, model=model) for key in query.all().key: if cls.get(model, key): cls.delete(model, key, mapping_only=False, byquery=byquery) cls.registry.flush() removed += 1 return removed
class Authorization: """A model to store autorization rules (permissions for users against an Anyblok model or a Pyramid resource)""" id = Integer(primary_key=True) order = Integer(default=100, nullable=False) resource = String() model = String( foreign_key=Declarations.Model.System.Model.use('name').options( ondelete="cascade")) primary_keys = Json(default={}) filter = Json(default={}) # next step role = Many2One(model=User.Role, foreign_key_options={'ondelete': 'cascade'}) login = String(foreign_key=User.use('login').options(ondelete="cascade")) user = Many2One(model=User) perms = Json(default={}) perm_create = JsonRelated(json_column='perms', keys=['create']) perm_read = JsonRelated(json_column='perms', keys=['read']) perm_update = JsonRelated(json_column='perms', keys=['update']) perm_delete = JsonRelated(json_column='perms', keys=['delete']) @classmethod def get_acl_filter_model(cls): """Return the Model to use to check the permission""" return { 'User': cls.registry.User, 'Role': cls.registry.User.Role, } @classmethod def get_acl(cls, login, resource, params=None): """Return the Pyramid ACL in function of the resource and user :param login: str, login of the user :param resource: str, name of the resource """ # cache the method User = cls.registry.User Role = cls.registry.User.Role query = cls.query() query = query.filter( or_(cls.resource == resource, cls.model == resource)) query = query.order_by(cls.order) Q1 = query.filter(cls.login == login) Q2 = query.join(cls.role).filter(Role.name.in_(User.get_roles(login))) res = [] for query in (Q1, Q2): for self in query.all(): allow_perms = [] deny_perms = [] perms = list((self.perms or {}).keys()) perms.sort() for perm in perms: p = self.perms[perm] query = User.query() query = query.filter(User.login == login) query = query.join(User.roles) if self.filter: query = query.condition_filter( self.filter, cls.get_acl_filter_model()) if 'condition' in p: query = query.condition_filter( p['condition'], cls.get_acl_filter_model()) ismatched = True if query.count() else False if p.get('matched' if ismatched else 'unmatched') is True: allow_perms.append(perm) elif (p.get('matched' if ismatched else 'unmatched') is False): deny_perms.append(perm) if len(allow_perms): res.append((Allow, login, allow_perms)) if len(deny_perms): res.append((Deny, login, deny_perms)) res.append((Deny, login, ALL_PERMISSIONS)) return res @classmethod def before_insert_orm_event(cls, mapper, connection, target): target.check_validity() @classmethod def before_update_orm_event(cls, mapper, connection, target): target.check_validity() def check_validity(self): """When creating or updating a User.Authorization, check that all rules objects exists or return an AuthorizationValidationException :exception: AuthorizationValidationException """ if not (self.role or self.login or self.user or self.role_name): raise AuthorizationValidationException( "No role and login to apply in the authorization (%s)" % self) if not (self.resource or self.model): raise AuthorizationValidationException( "No resource and model to apply in the authorization (%s)" % self) if not self.model and self.primary_keys: raise AuthorizationValidationException( "Primary keys without model to apply in the authorization " "(%s)" % self)
class JourneyWish(Mixin.UuidColumn, Mixin.TrackModel, Mixin.WorkFlow): """This model has Model.JourneyWish as a namespace. It is intented for storing journey wishes that represents travels intentions. For instance, an intention may be caracterized by start date, with an a time frame delimited by earlier and latest departure times, departure and arrival stations, passengers, maximum price, etc... Implemented fields are the following : * Departure station : Many2One relationship with Model.Station * Arrival station : Many2One relationship with Model.Station * Start date : DateTime representing earlier time after which train may leave the departure station * End date : DateTime representing latest time after which train may leave departure station. * Passengers : Many2Many relationship to Model.Passenger * Transportation mean : String, containing type of transport required by user (train, coach, etc...) * Active : boolean that stores if wish has to be processed * Activation date : Date, represent the moment when the wish could start being processed""" user = Many2One( label="User", model=Model.User, nullable=False, one2many="wishes" ) departure = Many2One( label="Departure Station", model=Model.Station, nullable=True, one2many="departures", ) arrival = Many2One( label="Arrival Station", model=Model.Station, nullable=True, one2many="arrivals", ) from_date = DateTime(label="Earlier Departure Date", nullable=True) end_date = DateTime(label="Latest Departure Date", nullable=True) activation_date = Date(label="Activation Date", nullable=True) passengers = Many2Many( model=Model.Passenger, join_table="join_passengers_by_wishes", remote_columns="uuid", local_columns="uuid", m2m_remote_columns="p_uuid", m2m_local_columns="w_uuid", many2many="wishes", ) transportation_mean = String( label="Transportation Mean", default="any", nullable=True ) active = Boolean(label="Active Wish", nullable=True) @classmethod def get_workflow_definition(cls): """This method is aimed at defining workflow used for model Model.JourneyWish""" return { "draft": { "default": True, "allowed_to": ["running", "pending"], "apply_change": "deactivate", }, "running": { "allowed_to": ["cancelled", "expired"], "apply_change": "activate", }, "pending": { "allowed_to": ["cancelled", "expired", "running"], "apply_change": "deactivate", }, "expired": {"apply_change": "deactivate"}, "cancelled": {"apply_change": "deactivate"}, } def activate(self, from_state): if from_state == "draft" and not self.activation_date: self.activation_date = date.today() self.active = True def deactivate(self, from_state): self.active = False def check_state(self): """This method is aimed at being used in order to automatically set workflow state, depending on record attributes.""" if self.state == "pending": # If wish is pending, check is activation_date is set if self.activation_date and self.activation_date <= date.today(): self.state_to("running") elif self.state == "running": # If wish is already running, check that from_date is not in the # past try: now = datetime.now().astimezone() except ValueError: # Python3.5 and below do not support astimezone on 'naive' # dates provided by datetime.now() now = datetime.now(timezone.utc).astimezone() if self.from_date and self.from_date < now: self.state_to("expired")
class Arrival(Operation): """Operation to describe physical arrival of goods in some location. Arrivals store data about the expected or arrived Goods: properties, code… These are copied over to the corresponding Goods records in all cases and stay inert after the fact. In case the Arrival state is ``planned``, these are obviously only unchecked values, but in case it is ``done``, the actual meaning can depend on the application: - maybe the application won't use the ``planned`` state at all, and will only create Arrival after checking them, - maybe the application will inspect the Arrival properties, compare them to reality, update them on the created Goods and cancel downstream operations if needed, before calling :meth:`execute`. TODO maybe provide higher level facilities for validation scenarios. """ TYPE = 'wms_arrival' id = Integer(label="Identifier", primary_key=True, autoincrement=False, foreign_key=Operation.use('id').options(ondelete='cascade')) """Primary key.""" goods_type = Many2One(model='Model.Wms.Goods.Type') """Expected :class:`Goods Type <anyblok_wms_base.bloks.wms_core.goods.Type>`. """ goods_properties = Jsonb(label="Properties of arrived Goods") """Expected :class:`Properties <anyblok_wms_base.bloks.wms_core.goods.Properties>`. They are copied over to the newly created :class:`Goods <anyblok_wms_base.bloks.wms_core.goods.Goods>` as soon as the Arrival is planned, and aren't updated by :meth:`execute`. Matching them with reality is the concern of separate validation processes, and this field can serve for later assessments after the fact. """ goods_code = String(label="Code to set on arrived Goods") """Expected :attr:`Goods code <anyblok_wms_base.bloks.wms_core.goods.Goods.code>`. Can be ``None`` in case the arrival process issues the code only at the time of actual arrival. """ location = Many2One(model='Model.Wms.Location') """Will be the location of the initial Avatar.""" inputs_number = 0 """This Operation is a purely creative one.""" def specific_repr(self): return ("goods_type={self.goods_type!r}, " "location={self.location!r}").format(self=self) def after_insert(self): Goods = self.registry.Wms.Goods self_props = self.goods_properties if self_props is None: props = None else: props = Goods.Properties.create(**self_props) goods = Goods.insert(type=self.goods_type, properties=props, code=self.goods_code) Goods.Avatar.insert( goods=goods, location=self.location, reason=self, state='present' if self.state == 'done' else 'future', dt_from=self.dt_execution, ) def execute_planned(self): Avatar = self.registry.Wms.Goods.Avatar Avatar.query().filter(Avatar.reason == self).one().update( state='present', dt_from=self.dt_execution)
class Order(Mixin.UuidColumn, Mixin.TrackModel, Mixin.WorkFlow): """Sale.Order model """ SCHEMA = OrderBaseSchema @classmethod def get_schema_definition(cls, **kwargs): return cls.SCHEMA(**kwargs) @classmethod def get_workflow_definition(cls): return { 'draft': { 'default': True, 'allowed_to': ['quotation', 'cancelled'] }, 'quotation': { 'allowed_to': ['order', 'cancelled'], 'validators': SchemaValidator( cls.get_schema_definition(exclude=['price_list'])) }, 'order': { 'validators': SchemaValidator( cls.get_schema_definition(exclude=['price_list'])) }, 'cancelled': {}, } code = String(label="Code", nullable=False) channel = String(label="Sale Channel", nullable=False) price_list = Many2One(label="Price list", model=Declarations.Model.Sale.PriceList) delivery_method = String(label="Delivery Method") amount_untaxed = Decimal(label="Amount Untaxed", default=D(0)) amount_tax = Decimal(label="Tax amount", default=D(0)) amount_total = Decimal(label="Total", default=D(0)) def __str__(self): return "{self.uuid} {self.channel} {self.code} {self.state}".format( self=self) def __repr__(self): return "<Sale(id={self.uuid}, code={self.code}," \ " amount_untaxed={self.amount_untaxed},"\ " amount_tax={self.amount_tax},"\ " amount_total={self.amount_total},"\ " channel={self.channel} state={self.state})>".format( self=self) @classmethod def create(cls, price_list=None, **kwargs): data = kwargs.copy() if cls.get_schema_definition: sch = cls.get_schema_definition(registry=cls.registry, exclude=['lines']) if price_list: data["price_list"] = price_list.to_primary_keys() data = sch.load(data) data['price_list'] = price_list return cls.insert(**data) def compute(self): """Compute order total amount""" amount_untaxed = D(0) amount_tax = D(0) amount_total = D(0) for line in self.lines: amount_untaxed += line.amount_untaxed amount_tax += line.amount_tax amount_total += line.amount_total self.amount_untaxed = amount_untaxed self.amount_tax = amount_tax self.amount_total = amount_total
class Task: """Main Task, define the main table""" TASK_TYPE = None id = Integer(primary_key=True) label = String(nullable=False) create_at = DateTime(default=datetime.now, nullable=False) update_at = DateTime(default=datetime.now, nullable=False, auto_update=True) task_type = Selection(selections="get_task_type", nullable=False) order = Integer(nullable=False, default=100) main_task = Many2One(model='Model.Dramatiq.Task', one2many="sub_tasks") @classmethod def get_task_type(cls): """List the task type possible""" return { 'call_method': 'Call classmethod', 'stepbystep': 'Step by Step', 'parallel': 'Parallel steps', } @classmethod def define_mapper_args(cls): """Polymorphism configuration""" mapper_args = super(Task, cls).define_mapper_args() if cls.__registry_name__ == Model.Dramatiq.Task.__registry_name__: mapper_args.update({ 'polymorphic_identity': cls.TASK_TYPE, 'polymorphic_on': cls.task_type, }) else: mapper_args.update({ 'polymorphic_identity': cls.TASK_TYPE, }) return mapper_args def do_the_job(self, main_job=None, run_at=None, with_args=None, with_kwargs=None): """Create a job for this tash and add send it to dramatiq :param main_job: parent job if exist :param run_at: datetime to execute the job :param with_args: tuple of the argument to pass at the job :param with_kwargs: dict of the argument to pass at the job """ values = dict(run_at=run_at, data=dict(with_args=with_args or tuple(), with_kwargs=with_kwargs or dict()), task=self, main_job=main_job) job = self.registry.Dramatiq.Job.insert(**values) # FIXME dramatiq don t accept uuid, waiting the next version self.registry.Dramatiq.Job.run(job_uuid=str(job.uuid), run_at=run_at) def run(self, job): """Execute the task for one job :param job: job executed """ raise Exception("No task definition for job %r" % job) def run_next(self, job): """next action to execute when a sub job finish this task for one job :param job: job executed """ raise Exception("No next action define for this task for job %r" % job)
class Address: id = Integer(primary_key=True) street = String() zip = String() city = String()
class Room: id = Integer(primary_key=True) name = String()
class Person: name = String(primary_key=True) addresses = Many2Many(model=Model.Address)
class T1: id = Integer(primary_key=True) code = String() val = Integer() rs_id = Integer(foreign_key=Model.Rs.use('id')) rs = Many2One(model=Model.Rs, column_names='rs_id')