1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2025-12-03 17:26:54 +01:00

Write an instance creation script and replace the SQLMigrationHandler by the MysqlMigrationHandler

see README.md for more informations about instance creation
This commit is contained in:
Yann 2015-12-02 14:46:50 +01:00
commit 4383606fbb
11 changed files with 1588 additions and 137 deletions

View file

@ -1,156 +1,547 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
## @package EditorialModel.migrationhandler.sql import copy
# @brief A dummy migration handler import pymysql
#
# According to it every modifications are possible
#
import EditorialModel import EditorialModel
from DataSource.MySQL.common_utils import MySQL
from DataSource.dummy.migrationhandler import DummyMigrationHandler from DataSource.dummy.migrationhandler import DummyMigrationHandler
from EditorialModel.fieldtypes.generic import GenericFieldType
from EditorialModel.model import Model # The global MH algorithm is as follow :
from mosql.db import Database # A create_table(table_name, pk_name, pk_opt) method that create a table
from Lodel.utils.mosql import create, alter_add # with one pk field
# An add_column(table_name, field_name, field_opt) method that add a column to a table
#
# The create_default_table method will call both methods to create the object and relation tables
#
# Supported operations :
# - EmClass creation
# - EmClass deletion (untested)
# - EmField creation
# - EmField deletion (untested)
# - rel2type attribute creation
# - rel2type attribute deletion (unstested)
#
# Unsupported operations :
# - EmClass rename
# - EmField rename
# - rel2type field rename
# - rel2type attribute rename
# - EmFieldType changes
#
# @todo Unified datasources and migration handlers via utils functions
## Manage Model changes ## @brief Modify a MySQL database given editorial model changes
class SQLMigrationHandler(DummyMigrationHandler): class MysqlMigrationHandler(DummyMigrationHandler):
fieldtype_to_sql = { ## @brief Construct a MysqlMigrationHandler
'char': "CHAR(255)", # @param host str : The db host
'integer': 'INT' # @param user str : The db user
} # @param password str : The db password
# @param db str : The db name
def __init__(self, host, user, passwd, db, module=pymysql, db_engine='InnoDB', foreign_keys=True, debug=False, dryrun=False, drop_if_exists=False):
self.datasource = MySQL
self._dbmodule = module
#Connect to MySQL
self.db = self._dbmodule.connect(host=host, user=user, passwd=passwd, db=db)
self.debug = debug
self.dryrun = dryrun
self.db_engine = db_engine
self.foreign_keys = foreign_keys if db_engine == 'InnoDB' else False
self.drop_if_exists = drop_if_exists
#Create default tables
self._create_default_tables(self.drop_if_exists)
def __init__(self, module=None, *conn_args, **conn_kargs): ## @brief Delete all table created by the MH
super(SQLMigrationHandler, self).__init__(False) # @param model Model : the Editorial model
def __purge_db(self, model):
for uid in [c.uid for c in model.components('EmClass')]:
try:
self.delete_emclass_table(model, uid)
except self._dbmodule.err.InternalError as e:
print(e)
self.db = Database(module, *conn_args, **conn_kargs) for tname in [MySQL.get_r2t2table_name(f.em_class.name, model.component(f.rel_to_type_id).name) for f in model.components('EmField') if f.fieldtype == 'rel2type']:
self._pk_column = (EditorialModel.classtypes.pk_name(), 'INTEGER PRIMARY KEY AUTOINCREMENT') try:
self._main_table_name = 'object' self._query("DROP TABLE %s;" % tname)
self._relation_table_name = 'relation' except self._dbmodule.err.InternalError as e:
print(e)
self._install_tables() for tname in [MySQL.relations_table_name, MySQL.objects_table_name]:
try:
self._query("DROP TABLE %s;" % tname)
except self._dbmodule.err.InternalError as e:
print(e)
## @brief Record a change in the EditorialModel and indicate wether or not it is possible to make it ## @brief Modify the db given an EM change
# @note The states ( initial_state and new_state ) contains only fields that changes # @param em model : The EditorialModel.model object to provide the global context
# @param model model : The EditorialModel.model object to provide the global context
# @param uid int : The uid of the change EmComponent # @param uid int : The uid of the change EmComponent
# @param initial_state dict | None : dict with field name as key and field value as value. Representing the original state. None mean creation of a new component. # @param initial_state dict | None : dict with field name as key and field value as value. Representing the original state. None mean creation of a new component.
# @param new_state dict | None : dict with field name as key and field value as value. Representing the new state. None mean component deletion # @param new_state dict | None : dict with field name as key and field value as value. Representing the new state. None mean component deletion
# @throw EditorialModel.exceptions.MigrationHandlerChangeError if the change was refused # @throw EditorialModel.exceptions.MigrationHandlerChangeError if the change was refused
def register_change(self, model, uid, initial_state, new_state): def register_change(self, em, uid, initial_state, new_state, engine=None):
# find type of component change if engine is None:
if initial_state is None: engine = self.db_engine
state_change = 'new' if isinstance(em.component(uid), EditorialModel.classes.EmClass):
elif new_state is None: if initial_state is None:
state_change = 'del' #EmClass creation
else: self.create_emclass_table(em, uid, engine)
state_change = 'upgrade' elif new_state is None:
#EmClass deletion
self.delete_emclass_table(em, uid)
elif isinstance(em.component(uid), EditorialModel.fields.EmField):
emfield = em.component(uid)
if emfield.rel_field_id is None:
#non relationnal field
if initial_state is None:
#non relationnal EmField creation
if emfield.name not in EditorialModel.classtypes.common_fields.keys():
self.add_col_from_emfield(em, uid)
elif new_state is None:
#non relationnal EmField deletion
if emfield.name not in EditorialModel.classtypes.common_fields.keys():
self.del_col_from_emfield(em, uid)
else:
#relationnal field
if initial_state is None:
#Rel2type attr creation
self.add_relationnal_field(em, uid)
elif new_state is None:
#Rel2type attr deletion
self.del_relationnal_field(em, uid)
# call method to handle the database change ## @brief dumdumdummy
component_name = Model.name_from_emclass(type(model.component(uid))) # @note implemented to avoid the log message of EditorialModel.migrationhandler.dummy.DummyMigrationHandler
handler_func = component_name.lower() + '_' + state_change def register_model_state(self, em, state_hash):
if hasattr(self, handler_func): pass
getattr(self, handler_func)(model, uid, initial_state, new_state)
# New Class, a table must be created ## @brief Exec a query
def emclass_new(self, model, uid, initial_state, new_state): # @param query str : SQL query
class_table_name = self._class_table_name(new_state['name']) def _query(self, query):
self._query_bd( if self.debug:
create(table=class_table_name, column=[self._pk_column]) print(query + "\n")
) if not self.dryrun:
with self.db.cursor() as cur:
# New Field, must create a column in Class table or in Class_Type relational attribute table
# @todo common fields creation does not allow to add new common fields. It should
def emfield_new(self, model, uid, initial_state, new_state):
# field is of type rel2type, create the relational class_type table and return
if new_state['fieldtype'] == 'rel2type':
# find relational_type name, and class name of the field
class_name = self._class_table_name_from_field(model, new_state)
type_name = model.component(new_state['rel_to_type_id']).name
table_name = self._relational_table_name(class_name, type_name)
self._query_bd(
create(table=table_name, column=[self._pk_column]),
)
return
# Column creation
#
# field is internal, create a column in the objects table
if new_state['internal']:
if new_state['fieldtype'] == 'pk': # this column has already beeen created by self._install_tables()
return
if new_state['name'] in EditorialModel.classtypes.common_fields: # this column has already beeen created by self._install_tables()
return
# field is relational (rel_field_id), create a column in the class_type table
elif new_state['rel_field_id']:
class_name = self._class_table_name_from_field(model, new_state)
rel_type_id = model.component(new_state['rel_field_id']).rel_to_type_id
type_name = model.component(rel_type_id).name
table_name = self._relational_table_name(class_name, type_name)
# else create a column in the class table
else:
table_name = self._class_table_name_from_field(model, new_state)
field_definition = self._fieldtype_definition(new_state['fieldtype'], new_state)
self._query_bd(
alter_add(table=table_name, column=[(new_state['name'],field_definition)])
)
## convert fieldtype name to SQL definition
def _fieldtype_definition(self, fieldtype, options):
basic_type = GenericFieldType.from_name(fieldtype).ftype
if basic_type == 'int':
return 'INT'
elif basic_type == 'char':
max_length = options['max_length'] if 'max_length' in options else 255
return 'CHAR(%s)' % max_length
elif basic_type == 'text':
return 'TEXT'
elif basic_type == 'bool':
return 'BOOLEAN'
elif basic_type == 'datetime':
definition = 'DATETIME'
if 'now_on_create' in options and options['now_on_create']:
definition += ' DEFAULT CURRENT_TIMESTAMP'
#if 'now_on_update' in options and options['now_on_update']:
#definition += ' ON UPDATE CURRENT_TIMESTAMP'
return definition
raise EditorialModel.exceptions.MigrationHandlerChangeError("Basic type '%s' of fieldtype '%s' is not compatible with SQL migration Handler" % basic_type, fieldtype)
## Test if internal tables must be created, create it if it must
def _install_tables(self):
# create common fields definition
common_fields = [self._pk_column]
for name, options in EditorialModel.classtypes.common_fields.items():
if options['fieldtype'] != 'pk':
common_fields.append((name, self._fieldtype_definition(options['fieldtype'], options)))
# create common tables
self._query_bd(
create(table=self._main_table_name, column=common_fields),
create(table=self._relation_table_name, column=[('relation_id','INTEGER PRIMARY KEY AUTOINCREMENT'), ('superior_id','INT'), ('subdordinate_id','INT'), ('nature','CHAR(255)'), ('depth','INT'), ('rank','INT')])
)
def _query_bd(self, *queries):
with self.db as cur:
for query in queries:
print(query)
cur.execute(query) cur.execute(query)
self.db.commit() # autocommit
def _class_table_name(self, class_name): ## @brief Add a relationnal field
return 'class_' + class_name # Add a rel2type attribute
# @note this function handles the table creation
# @param em Model : EditorialModel.model.Model instance
# @param rfuid int : Relationnal field uid
def add_relationnal_field(self, em, rfuid):
emfield = em.component(rfuid)
if not isinstance(emfield, EditorialModel.fields.EmField):
raise ValueError("The given uid is not an EmField uid")
def _relational_table_name(self, class_name, type_name): r2tf = em.component(emfield.rel_field_id)
return 'r2t_' + class_name + '_' + type_name tname = self._r2t2table_name(em, r2tf)
pkname, pkftype = self._relation_pk
def _class_table_name_from_field(self, model, field): #If not exists create a relational table
class_id = field['class_id'] self._create_table(tname, pkname, pkftype, self.db_engine, if_exists='nothing')
class_name = model.component(class_id).name #Add a foreign key if wanted
class_table_name = self._class_table_name(class_name) if self.foreign_keys:
return class_table_name self._add_fk(tname, self.datasource.relations_table_name, pkname, pkname)
#Add the column
self._add_column(tname, emfield.name, emfield.fieldtype_instance())
#Update table triggers
self._generate_triggers(tname, self._r2type2cols(em, r2tf))
## @brief Delete a rel2type attribute
#
# Delete a rel2type attribute
# @note this method handles the table deletion
# @param em Model : EditorialModel.model.Model instance
# @param rfuid int : Relationnal field uid
def del_relationnal_field(self, em, rfuid):
emfield = em.component(rfuid)
if not isinstance(emfield, EditorialModel.fields.EmField):
raise ValueError("The given uid is not an EmField uid")
r2tf = em.component(emfield.rel_field_id)
tname = self._r2t2table_name(em, r2tf)
if len(self._r2type2cols(em, r2tf)) == 1:
#The table can be deleted (no more attribute for this rel2type)
self._query("""DROP TABLE {table_name}""".format(table_name=tname))
else:
self._del_column(tname, emfield.name)
#Update table triggers
self._generate_triggers(tname, self._r2type2cols(em, r2tf))
## @brief Given an EmField uid add a column to the corresponding table
# @param em Model : A Model instance
# @param uid int : An EmField uid
def add_col_from_emfield(self, em, uid):
emfield = em.component(uid)
if not isinstance(emfield, EditorialModel.fields.EmField):
raise ValueError("The given uid is not an EmField uid")
emclass = emfield.em_class
tname = self._emclass2table_name(emclass)
self._add_column(tname, emfield.name, emfield.fieldtype_instance())
# Refresh the table triggers
cols_l = self._class2cols(emclass)
self._generate_triggers(tname, cols_l)
## @brief Given a class uid create the coressponding table
# @param em Model : A Model instance
# @param uid int : An EmField uid
def create_emclass_table(self, em, uid, engine):
emclass = em.component(uid)
if not isinstance(emclass, EditorialModel.classes.EmClass):
raise ValueError("The given uid is not an EmClass uid")
pkname, pktype = self._common_field_pk
table_name = self._emclass2table_name(emclass)
self._create_table(table_name, pkname, pktype, engine=engine)
if self.foreign_keys:
self._add_fk(table_name, self.datasource.objects_table_name, pkname, pkname)
## @brief Given an EmClass uid delete the corresponding table
# @param em Model : A Model instance
# @param uid int : An EmField uid
def delete_emclass_table(self, em, uid):
emclass = em.component(uid)
if not isinstance(emclass, EditorialModel.classes.EmClass):
raise ValueError("The give uid is not an EmClass uid")
tname = self._emclass2table_name(emclass)
# Delete the table triggers to prevent errors
self._generate_triggers(tname, dict())
tname = self.datasource.escape_idname(tname)
self._query("""DROP TABLE {table_name};""".format(table_name=tname))
## @brief Given an EmField delete the corresponding column
# @param em Model : an @ref EditorialModel.model.Model instance
# @param uid int : an EmField uid
def delete_col_from_emfield(self, em, uid):
emfield = em.component(uid)
if not isinstance(emfield, EditorialModel.fields.EmField):
raise ValueError("The given uid is not an EmField uid")
emclass = emfield.em_class
tname = self._emclass2table_name(emclass)
# Delete the table triggers to prevent errors
self._generate_triggers(tname, dict())
self._del_column(tname, emfield.name)
# Refresh the table triggers
cols_ls = self._class2cols(emclass)
self._generate_triggers(tname, cols_l)
## @brief Delete a column from a table
# @param tname str : The table name
# @param fname str : The column name
def _del_column(self, tname, fname):
tname = self.datasource.escape_idname(tname)
fname = self.datasource.escape_idname(fname)
self._query("""ALTER TABLE {table_name} DROP COLUMN {col_name};""".format(table_name=tname, col_name=fname))
## @brief Construct a table name given an EmClass instance
# @param emclass EmClass : An EmClass instance
# @return a table name
def _emclass2table_name(self, emclass):
return self.datasource.get_table_name_from_class(emclass.name)
#return "class_%s"%emclass.name
## @brief Construct a table name given a rela2type EmField instance
# @param em Model : A Model instance
# @param emfield EmField : An EmField instance
# @return a table name
def _r2t2table_name(self, em, emfield):
emclass = emfield.em_class
emtype = em.component(emfield.rel_to_type_id)
return self.datasource.get_r2t2table_name(emclass.name, emtype.name)
#return "%s_%s_%s"%(emclass.name, emtype.name, emfield.name)
## @brief Generate a columns_fieldtype dict given a rel2type EmField
# @param em Model : an @ref EditorialModel.model.Model instance
# @param emfield EmField : and @ref EditorialModel.fields.EmField instance
def _r2type2cols(self, em, emfield):
return {f.name: f.fieldtype_instance() for f in em.components('EmField') if f.rel_field_id == emfield.uid}
## @brief Generate a columns_fieldtype dict given an EmClass
# @param emclass EmClass : An EmClass instance
# @return A dict with column name as key and EmFieldType instance as value
def _class2cols(self, emclass):
if not isinstance(emclass, EditorialModel.classes.EmClass):
raise ValueError("The given uid is not an EmClass uid")
return {f.name: f.fieldtype_instance() for f in emclass.fields() if f.name not in EditorialModel.classtypes.common_fields.keys()}
## @brief Create object and relations tables
# @param drop_if_exist bool : If true drop tables if exists
def _create_default_tables(self, drop_if_exist=False):
if_exists = 'drop' if drop_if_exist else 'nothing'
#Object tablea
tname = self.datasource.objects_table_name
pk_name, pk_ftype = self._common_field_pk
self._create_table(tname, pk_name, pk_ftype, engine=self.db_engine, if_exists=if_exists)
#Adding columns
cols = {fname: self._common_field_to_ftype(fname) for fname in EditorialModel.classtypes.common_fields}
for fname, ftype in cols.items():
if fname != pk_name:
self._add_column(tname, fname, ftype)
#Creating triggers
self._generate_triggers(tname, cols)
#Relation table
tname = self.datasource.relations_table_name
pk_name, pk_ftype = self._relation_pk
self._create_table(tname, pk_name, pk_ftype, engine=self.db_engine, if_exists=if_exists)
#Adding columns
for fname, ftype in self._relation_cols.items():
self._add_column(tname, fname, ftype)
#Creating triggers
self._generate_triggers(tname, self._relation_cols)
## @return true if the name changes
def _name_change(self, initial_state, new_state):
return 'name' in initial_state and initial_state['name'] != new_state['name']
## @brief Create a table with primary key
# @param table_name str : table name
# @param pk_name str : pk column name
# @param pk_specs str : see @ref _field_to_sql()
# @param engine str : The engine to use with this table
# @param charset str : The charset of this table
# @param if_exist str : takes values in ['nothing', 'drop']
def _create_table(self, table_name, pk_name, pk_ftype, engine, charset='utf8', if_exists='nothing'):
#Escaped table name
etname = self.datasource.escape_idname(table_name)
pk_type = self._field_to_type(pk_ftype)
pk_specs = self._field_to_specs(pk_ftype)
if if_exists == 'drop':
self._query("""DROP TABLE IF EXISTS {table_name};""".format(table_name=etname))
qres = """
CREATE TABLE {table_name} (
{pk_name} {pk_type} {pk_specs},
PRIMARY KEY({pk_name})
) ENGINE={engine} DEFAULT CHARSET={charset};"""
elif if_exists == 'nothing':
qres = """CREATE TABLE IF NOT EXISTS {table_name} (
{pk_name} {pk_type} {pk_specs},
PRIMARY KEY({pk_name})
) ENGINE={engine} DEFAULT CHARSET={charset};"""
else:
raise ValueError("Unexpected value for argument if_exists '%s'." % if_exists)
self._query(qres.format(
table_name=self.datasource.escape_idname(table_name),
pk_name=self.datasource.escape_idname(pk_name),
pk_type=pk_type,
pk_specs=pk_specs,
engine=engine,
charset=charset
))
## @brief Add a column to a table
# @param table_name str : The table name
# @param col_name str : The columns name
# @param col_fieldtype EmFieldype the fieldtype
def _add_column(self, table_name, col_name, col_fieldtype, drop_if_exists=False):
add_col = """ALTER TABLE {table_name}
ADD COLUMN {col_name} {col_type} {col_specs};"""
etname = self.datasource.escape_idname(table_name)
ecname = self.datasource.escape_idname(col_name)
add_col = add_col.format(
table_name=etname,
col_name=ecname,
col_type=self._field_to_type(col_fieldtype),
col_specs=self._field_to_specs(col_fieldtype),
)
try:
self._query(add_col)
except self._dbmodule.err.InternalError as e:
if drop_if_exists:
self._del_column(table_name, col_name)
self._add_column(table_name, col_name, col_fieldtype, drop_if_exists)
else:
#LOG
print("Aborded, column `%s` exists" % col_name)
## @brief Add a foreign key
# @param src_table_name str : The name of the table where we will add the FK
# @param dst_table_name str : The name of the table the FK will point on
# @param src_col_name str : The name of the concerned column in the src_table
# @param dst_col_name str : The name of the concerned column in the dst_table
def _add_fk(self, src_table_name, dst_table_name, src_col_name, dst_col_name):
stname = self.datasource.escape_idname(src_table_name)
dtname = self.datasource.escape_idname(dst_table_name)
scname = self.datasource.escape_idname(src_col_name)
dcname = self.datasource.escape_idname(dst_col_name)
fk_name = self.datasource.get_fk_name(src_table_name, dst_table_name)
self._del_fk(src_table_name, dst_table_name)
self._query("""ALTER TABLE {src_table}
ADD CONSTRAINT {fk_name}
FOREIGN KEY ({src_col}) references {dst_table}({dst_col});""".format(
fk_name=self.datasource.escape_idname(fk_name),
src_table=stname,
src_col=scname,
dst_table=dtname,
dst_col=dcname
))
## @brief Given a source and a destination table, delete the corresponding FK
# @param src_table_name str : The name of the table where the FK is
# @param dst_table_name str : The name of the table the FK point on
# @warning fails silently
def _del_fk(self, src_table_name, dst_table_name):
try:
self._query("""ALTER TABLE {src_table}
DROP FOREIGN KEY {fk_name}""".format(
src_table=self.datasource.escape_idname(src_table_name),
fk_name=self.datasource.escape_idname(self.datasource.get_fk_name(src_table_name, dst_table_name))
))
except self._dbmodule.err.InternalError:
# If the FK don't exists we do not care
pass
## @brief Generate triggers given a table_name and its columns fieldtypes
# @param table_name str : Table name
# @param cols_ftype dict : with col name as key and column fieldtype as value
def _generate_triggers(self, table_name, cols_ftype):
colval_l_upd = dict() # param for update trigger
colval_l_ins = dict() # param for insert trigger
for cname, cftype in cols_ftype.items():
if cftype.ftype == 'datetime':
if cftype.now_on_update:
colval_l_upd[cname] = 'NOW()'
if cftype.now_on_create:
colval_l_ins[cname] = 'NOW()'
self._table_trigger(table_name, 'UPDATE', colval_l_upd)
self._table_trigger(table_name, 'INSERT', colval_l_ins)
## @brief Create trigger for a table
#
# Primarly designed to create trigger for DATETIME types
# The method generates triggers of the form
#
# CREATE TRIGGER BEFORE <moment> ON <table_name>
# FOR EACH ROW SET <for colname, colval in cols_val>
# NEW.<colname> = <colval>,
# <endfor>;
# @param table_name str : The table name
# @param moment str : can be 'update' or 'insert'
# @param cols_val dict : Dict with column name as key and column value as value
def _table_trigger(self, table_name, moment, cols_val):
trigger_name = self.datasource.escape_idname("%s_%s_trig" % (table_name, moment))
#Try to delete the trigger
drop_trig = """DROP TRIGGER IF EXISTS {trigger_name};""".format(trigger_name=trigger_name)
self._query(drop_trig)
col_val_l = ', '.join(["NEW.%s = %s" % (self.datasource.escape_idname(cname), cval)for cname, cval in cols_val.items()])
#Create a trigger if needed
if len(col_val_l) > 0:
trig_q = """CREATE TRIGGER {trigger_name} BEFORE {moment} ON {table_name}
FOR EACH ROW SET {col_val_list};""".format(
trigger_name=trigger_name,
table_name=self.datasource.escape_idname(table_name),
moment=moment, col_val_list=col_val_l
)
self._query(trig_q)
## @brief Identifier escaping
# @param idname str : An SQL identifier
#def _idname_escape(self, idname):
# if '`' in idname:
# raise ValueError("Invalid name : '%s'"%idname)
# return '`%s`'%idname
## @brief Returns column specs from fieldtype
# @param emfieldtype EmFieldType : An EmFieldType insance
# @todo escape default value
def _field_to_specs(self, emfieldtype):
colspec = ''
if not emfieldtype.nullable:
colspec = 'NOT NULL'
if hasattr(emfieldtype, 'default'):
colspec += ' DEFAULT '
if emfieldtype.default is None:
colspec += 'NULL '
else:
colspec += emfieldtype.default # ESCAPE VALUE HERE !!!!
if emfieldtype.name == 'pk':
colspec += ' AUTO_INCREMENT'
return colspec
## @brief Given a fieldtype return a MySQL type specifier
# @param emfieldtype EmFieldType : A fieldtype
# @return the corresponding MySQL type
def _field_to_type(self, emfieldtype):
ftype = emfieldtype.ftype
if ftype == 'char' or ftype == 'str':
res = "VARCHAR(%d)" % emfieldtype.max_length
elif ftype == 'text':
res = "TEXT"
elif ftype == 'datetime':
res = "DATETIME"
# client side workaround for only one column with CURRENT_TIMESTAMP : giving NULL to timestamp that don't allows NULL
# cf. https://dev.mysql.com/doc/refman/5.0/en/timestamp-initialization.html#idm139961275230400
# The solution for the migration handler is to create triggers :
# CREATE TRIGGER trigger_name BEFORE INSERT ON `my_super_table`
# FOR EACH ROW SET NEW.my_date_column = NOW();
# and
# CREATE TRIGGER trigger_name BEFORE UPDATE ON
elif ftype == 'bool':
res = "BOOL"
elif ftype == 'int':
res = "INT"
elif ftype == 'rel2type':
res = "INT"
else:
raise ValueError("Unsuported fieldtype ftype : %s" % ftype)
return res
## @brief Returns a tuple (pkname, pk_ftype)
@property
def _common_field_pk(self):
for fname, fta in EditorialModel.classtypes.common_fields.items():
if fta['fieldtype'] == 'pk':
return (fname, self._common_field_to_ftype(fname))
return (None, None)
## @brief Returns a tuple (rel_pkname, rel_ftype)
# @todo do it
@property
def _relation_pk(self):
return (MySQL.relations_pkname, EditorialModel.fieldtypes.pk.EmFieldType())
## @brief Returns a dict { colname:fieldtype } of relation table columns
@property
def _relation_cols(self):
from_name = EditorialModel.fieldtypes.generic.GenericFieldType.from_name
return {
'id_sup': from_name('integer')(),
'id_sub': from_name('integer')(),
'rank': from_name('integer')(nullable=True),
'depth': from_name('integer')(nullable=True),
'nature': from_name('char')(max_lenght=10, nullable=True),
}
## @brief Given a common field name return an EmFieldType instance
# @param cname str : Common field name
# @return An EmFieldType instance
def _common_field_to_ftype(self, cname):
fta = copy.copy(EditorialModel.classtypes.common_fields[cname])
fto = EditorialModel.fieldtypes.generic.GenericFieldType.from_name(fta['fieldtype'])
del fta['fieldtype']
return fto(**fta)

View file

@ -0,0 +1,156 @@
# -*- coding: utf-8 -*-
## @package EditorialModel.migrationhandler.sql
# @brief A dummy migration handler
#
# According to it every modifications are possible
#
import EditorialModel
from DataSource.dummy.migrationhandler import DummyMigrationHandler
from EditorialModel.fieldtypes.generic import GenericFieldType
from EditorialModel.model import Model
from mosql.db import Database
from Lodel.utils.mosql import create, alter_add
## Manage Model changes
class SQLMigrationHandler(DummyMigrationHandler):
fieldtype_to_sql = {
'char': "CHAR(255)",
'integer': 'INT'
}
def __init__(self, module=None, *conn_args, **conn_kargs):
super(SQLMigrationHandler, self).__init__(False)
self.db = Database(module, *conn_args, **conn_kargs)
self._pk_column = (EditorialModel.classtypes.pk_name(), 'INTEGER PRIMARY KEY AUTO_INCREMENT')
self._main_table_name = 'object'
self._relation_table_name = 'relation'
self._install_tables()
## @brief Record a change in the EditorialModel and indicate wether or not it is possible to make it
# @note The states ( initial_state and new_state ) contains only fields that changes
# @param model model : The EditorialModel.model object to provide the global context
# @param uid int : The uid of the change EmComponent
# @param initial_state dict | None : dict with field name as key and field value as value. Representing the original state. None mean creation of a new component.
# @param new_state dict | None : dict with field name as key and field value as value. Representing the new state. None mean component deletion
# @throw EditorialModel.exceptions.MigrationHandlerChangeError if the change was refused
def register_change(self, model, uid, initial_state, new_state):
# find type of component change
if initial_state is None:
state_change = 'new'
elif new_state is None:
state_change = 'del'
else:
state_change = 'upgrade'
# call method to handle the database change
component_name = Model.name_from_emclass(type(model.component(uid)))
handler_func = component_name.lower() + '_' + state_change
if hasattr(self, handler_func):
getattr(self, handler_func)(model, uid, initial_state, new_state)
# New Class, a table must be created
def emclass_new(self, model, uid, initial_state, new_state):
class_table_name = self._class_table_name(new_state['name'])
self._query_bd(
create(table=class_table_name, column=[self._pk_column])
)
# New Field, must create a column in Class table or in Class_Type relational attribute table
# @todo common fields creation does not allow to add new common fields. It should
def emfield_new(self, model, uid, initial_state, new_state):
# field is of type rel2type, create the relational class_type table and return
if new_state['fieldtype'] == 'rel2type':
# find relational_type name, and class name of the field
class_name = self._class_table_name_from_field(model, new_state)
type_name = model.component(new_state['rel_to_type_id']).name
table_name = self._relational_table_name(class_name, type_name)
self._query_bd(
create(table=table_name, column=[self._pk_column]),
)
return
# Column creation
#
# field is internal, create a column in the objects table
if new_state['internal']:
if new_state['fieldtype'] == 'pk': # this column has already beeen created by self._install_tables()
return
if new_state['name'] in EditorialModel.classtypes.common_fields: # this column has already beeen created by self._install_tables()
return
# field is relational (rel_field_id), create a column in the class_type table
elif new_state['rel_field_id']:
class_name = self._class_table_name_from_field(model, new_state)
rel_type_id = model.component(new_state['rel_field_id']).rel_to_type_id
type_name = model.component(rel_type_id).name
table_name = self._relational_table_name(class_name, type_name)
# else create a column in the class table
else:
table_name = self._class_table_name_from_field(model, new_state)
field_definition = self._fieldtype_definition(new_state['fieldtype'], new_state)
self._query_bd(
alter_add(table=table_name, column=[(new_state['name'],field_definition)])
)
## convert fieldtype name to SQL definition
def _fieldtype_definition(self, fieldtype, options):
basic_type = GenericFieldType.from_name(fieldtype).ftype
if basic_type == 'int':
return 'INT'
elif basic_type == 'char':
max_length = options['max_length'] if 'max_length' in options else 255
return 'CHAR(%s)' % max_length
elif basic_type == 'text':
return 'TEXT'
elif basic_type == 'bool':
return 'BOOLEAN'
elif basic_type == 'datetime':
definition = 'DATETIME'
if 'now_on_create' in options and options['now_on_create']:
definition += ' DEFAULT CURRENT_TIMESTAMP'
#if 'now_on_update' in options and options['now_on_update']:
#definition += ' ON UPDATE CURRENT_TIMESTAMP'
return definition
raise EditorialModel.exceptions.MigrationHandlerChangeError("Basic type '%s' of fieldtype '%s' is not compatible with SQL migration Handler" % basic_type, fieldtype)
## Test if internal tables must be created, create it if it must
def _install_tables(self):
# create common fields definition
common_fields = [self._pk_column]
for name, options in EditorialModel.classtypes.common_fields.items():
if options['fieldtype'] != 'pk':
common_fields.append((name, self._fieldtype_definition(options['fieldtype'], options)))
# create common tables
self._query_bd(
create(table=self._main_table_name, column=common_fields),
create(table=self._relation_table_name, column=[('relation_id','INTEGER PRIMARY KEY AUTOINCREMENT'), ('superior_id','INT'), ('subdordinate_id','INT'), ('nature','CHAR(255)'), ('depth','INT'), ('rank','INT')])
)
def _query_bd(self, *queries):
with self.db as cur:
for query in queries:
print(query)
cur.execute(query)
def _class_table_name(self, class_name):
return 'class_' + class_name
def _relational_table_name(self, class_name, type_name):
return 'r2t_' + class_name + '_' + type_name
def _class_table_name_from_field(self, model, field):
class_id = field['class_id']
class_name = model.component(class_id).name
class_table_name = self._class_table_name(class_name)
return class_table_name

View file

@ -1,3 +1,13 @@
Creating a Lodel "instance":
use the lodel_init.sh script :
lodel_init.sh INSTANCE_NAME INSTANCE_WANTED_PATH [LODEL2_LIB_PATH]
Once the instance is created you can run an interactive python interpreter using :
cd INSTANCE_PATH; python loader.py
If you want to write a script that run is the instance env you have to from loader import *
First test installation : First test installation :
- use python 3.4 - use python 3.4

17
install/Makefile Normal file
View file

@ -0,0 +1,17 @@
all:
refreshdyn:
python -c "import utils; utils.refreshdyn()"
dbinit:
python -c "import utils; utils.db_init()"
.PHONY: clean cleanpycache cleanpyc refreshdyn
clean: cleanpycache
cleanpyc:
-@rm -v *.pyc
cleanpycache: cleanpyc
-@rm -vR __pycache__

171
install/dynleapi.py Normal file
View file

@ -0,0 +1,171 @@
## @author LeFactory
import EditorialModel
from EditorialModel import fieldtypes
from EditorialModel.fieldtypes import naturerelation, char, integer, datetime, pk
import leapi
import leapi.lecrud
import leapi.leobject
import leapi.lerelation
from leapi.leclass import _LeClass
from leapi.letype import _LeType
import DataSource.MySQL.leapidatasource
## @brief _LeCrud concret class
# @see leapi.lecrud._LeCrud
class LeCrud(leapi.lecrud._LeCrud):
_datasource = DataSource.MySQL.leapidatasource.LeDataSourceSQL(**{})
_uid_fieldtype = None
## @brief _LeObject concret class
# @see leapi.leobject._LeObject
class LeObject(LeCrud, leapi.leobject._LeObject):
_me_uid = {1: 'Textes', 2: 'Personnes', 19: 'Numero', 5: 'Article', 6: 'Personne', 13: 'Publication', 14: 'Rubrique'}
_uid_fieldtype = { 'lodel_id': EditorialModel.fieldtypes.pk.EmFieldType(**{'internal': 'automatic'}) }
_leo_fieldtypes = {
'creation_date': EditorialModel.fieldtypes.datetime.EmFieldType(**{'now_on_create': True, 'internal': 'automatic'}),
'string': EditorialModel.fieldtypes.char.EmFieldType(**{'max_length': 128, 'internal': 'automatic'}),
'modification_date': EditorialModel.fieldtypes.datetime.EmFieldType(**{'now_on_update': True, 'now_on_create': True, 'internal': 'automatic'}),
'type_id': EditorialModel.fieldtypes.integer.EmFieldType(**{'internal': 'automatic'}),
'class_id': EditorialModel.fieldtypes.integer.EmFieldType(**{'internal': 'automatic'})
}
## @brief _LeRelation concret class
# @see leapi.lerelation._LeRelation
class LeRelation(LeCrud, leapi.lerelation._LeRelation):
_uid_fieldtype = { 'id_relation': EditorialModel.fieldtypes.pk.EmFieldType(**{'internal': 'automatic'}) }
_rel_fieldtypes = {
'rank': EditorialModel.fieldtypes.integer.EmFieldType(**{'internal': 'automatic'}),
'nature': EditorialModel.fieldtypes.naturerelation.EmFieldType(**{}),
'depth': EditorialModel.fieldtypes.integer.EmFieldType(**{'internal': 'automatic'})
}
_rel_attr_fieldtypes = dict()
class LeHierarch(LeRelation, leapi.lerelation._LeHierarch):
_rel_attr_fieldtypes = dict()
class LeRel2Type(LeRelation, leapi.lerelation._LeRel2Type):
pass
class LeClass(LeObject, _LeClass):
pass
class LeType(LeClass, _LeType):
pass
## @brief EmClass Textes LeClass child class
# @see leapi.leclass.LeClass
class Textes(LeClass, LeObject):
_class_id = 1
## @brief EmClass Personnes LeClass child class
# @see leapi.leclass.LeClass
class Personnes(LeClass, LeObject):
_class_id = 2
## @brief EmClass Publication LeClass child class
# @see leapi.leclass.LeClass
class Publication(LeClass, LeObject):
_class_id = 13
## @brief EmType Article LeType child class
# @see leobject::letype::LeType
class Article(LeType, Textes):
_type_id = 5
## @brief EmType Personne LeType child class
# @see leobject::letype::LeType
class Personne(LeType, Personnes):
_type_id = 6
## @brief EmType Rubrique LeType child class
# @see leobject::letype::LeType
class Rubrique(LeType, Publication):
_type_id = 14
## @brief EmType Numero LeType child class
# @see leobject::letype::LeType
class Numero(LeType, Publication):
_type_id = 19
class Rel_textes2personne(LeRel2Type):
_rel_attr_fieldtypes = {
'adresse': EditorialModel.fieldtypes.char.EmFieldType(**{'uniq': False, 'nullable': True, 'internal': False})
}
#Initialisation of Textes class attributes
Textes._fieldtypes = {
'modification_date': EditorialModel.fieldtypes.datetime.EmFieldType(**{'nullable': False, 'uniq': False, 'now_on_update': True, 'now_on_create': True, 'internal': 'automatic'}),
'soustitre': EditorialModel.fieldtypes.char.EmFieldType(**{'uniq': False, 'nullable': True, 'internal': False}),
'string': EditorialModel.fieldtypes.char.EmFieldType(**{'nullable': True, 'uniq': False, 'max_length': 128, 'internal': 'automatic'}),
'titre': EditorialModel.fieldtypes.char.EmFieldType(**{'uniq': False, 'nullable': True, 'internal': False}),
'bleu': EditorialModel.fieldtypes.char.EmFieldType(**{'uniq': False, 'nullable': True, 'internal': False}),
'lodel_id': EditorialModel.fieldtypes.pk.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'}),
'type_id': EditorialModel.fieldtypes.integer.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'}),
'class_id': EditorialModel.fieldtypes.integer.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'}),
'creation_date': EditorialModel.fieldtypes.datetime.EmFieldType(**{'uniq': False, 'nullable': False, 'now_on_create': True, 'internal': 'automatic'})
}
Textes._linked_types = [Personne]
Textes._classtype = 'entity'
#Initialisation of Personnes class attributes
Personnes._fieldtypes = {
'nom': EditorialModel.fieldtypes.char.EmFieldType(**{'uniq': False, 'nullable': True, 'internal': False}),
'age': EditorialModel.fieldtypes.char.EmFieldType(**{'uniq': False, 'nullable': True, 'internal': False}),
'prenom': EditorialModel.fieldtypes.char.EmFieldType(**{'uniq': False, 'nullable': True, 'internal': False}),
'string': EditorialModel.fieldtypes.char.EmFieldType(**{'nullable': True, 'uniq': False, 'max_length': 128, 'internal': 'automatic'}),
'lodel_id': EditorialModel.fieldtypes.pk.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'}),
'modification_date': EditorialModel.fieldtypes.datetime.EmFieldType(**{'nullable': False, 'uniq': False, 'now_on_update': True, 'now_on_create': True, 'internal': 'automatic'}),
'type_id': EditorialModel.fieldtypes.integer.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'}),
'class_id': EditorialModel.fieldtypes.integer.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'}),
'creation_date': EditorialModel.fieldtypes.datetime.EmFieldType(**{'uniq': False, 'nullable': False, 'now_on_create': True, 'internal': 'automatic'})
}
Personnes._linked_types = []
Personnes._classtype = 'person'
#Initialisation of Publication class attributes
Publication._fieldtypes = {
'modification_date': EditorialModel.fieldtypes.datetime.EmFieldType(**{'nullable': False, 'uniq': False, 'now_on_update': True, 'now_on_create': True, 'internal': 'automatic'}),
'creation_date': EditorialModel.fieldtypes.datetime.EmFieldType(**{'uniq': False, 'nullable': False, 'now_on_create': True, 'internal': 'automatic'}),
'string': EditorialModel.fieldtypes.char.EmFieldType(**{'nullable': True, 'uniq': False, 'max_length': 128, 'internal': 'automatic'}),
'lodel_id': EditorialModel.fieldtypes.pk.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'}),
'titre': EditorialModel.fieldtypes.char.EmFieldType(**{'uniq': False, 'nullable': True, 'internal': False}),
'type_id': EditorialModel.fieldtypes.integer.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'}),
'class_id': EditorialModel.fieldtypes.integer.EmFieldType(**{'uniq': False, 'nullable': False, 'internal': 'automatic'})
}
Publication._linked_types = []
Publication._classtype = 'entity'
#Initialisation of Article class attributes
Article._fields = ['titre', 'class_id', 'soustitre', 'string', 'type_id', 'lodel_id', 'modification_date', 'creation_date']
Article._superiors = {'parent': [Rubrique]}
Article._leclass = Textes
#Initialisation of Personne class attributes
Personne._fields = ['nom', 'class_id', 'prenom', 'string', 'type_id', 'lodel_id', 'modification_date', 'creation_date']
Personne._superiors = {}
Personne._leclass = Personnes
#Initialisation of Rubrique class attributes
Rubrique._fields = ['titre', 'class_id', 'string', 'type_id', 'lodel_id', 'modification_date', 'creation_date']
Rubrique._superiors = {'parent': [Rubrique, Numero]}
Rubrique._leclass = Publication
#Initialisation of Numero class attributes
Numero._fields = ['titre', 'class_id', 'string', 'type_id', 'lodel_id', 'modification_date', 'creation_date']
Numero._superiors = {}
Numero._leclass = Publication
## @brief Dict for getting LeClass and LeType child classes given an EM uid
LeObject._me_uid = {1: Textes, 2: Personnes, 19: Numero, 5: Article, 6: Personne, 13: Publication, 14: Rubrique}

579
install/em.json Normal file
View file

@ -0,0 +1,579 @@
{
"1": {
"component": "EmClass",
"date_create": "Fri Oct 16 11:05:04 2015",
"date_update": "Fri Oct 16 11:05:04 2015",
"sortcolumn": "rank",
"classtype": "entity",
"icon": "0",
"rank": 1,
"name": "textes",
"help_text": "",
"string": "{\"fre\": \"Texte\"}"
},
"2": {
"component": "EmClass",
"date_create": "Fri Oct 16 11:05:04 2015",
"date_update": "Fri Oct 16 11:05:04 2015",
"sortcolumn": "rank",
"classtype": "person",
"icon": "0",
"rank": 1,
"name": "personnes",
"help_text": "",
"string": "{\"fre\": \"Personnes\"}"
},
"4": {
"nullable": true,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": false,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "{\"fre\": \"Titre\"}",
"class_id": 1,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 1,
"fieldtype": "char",
"help_text": "",
"internal": false,
"rel_field_id": null,
"name": "titre"
},
"5": {
"date_update": "Fri Oct 16 11:05:04 2015",
"class_id": 1,
"icon": "0",
"name": "article",
"superiors_list": {
"parent": [
14
]
},
"fields_list": [
7
],
"string": "{\"fre\": \"Article\"}",
"date_create": "Fri Oct 16 11:05:04 2015",
"component": "EmType",
"rank": 1,
"help_text": "",
"sortcolumn": "rank"
},
"6": {
"date_update": "Fri Oct 16 11:05:04 2015",
"class_id": 2,
"icon": "0",
"name": "personne",
"superiors_list": {},
"fields_list": [
10
],
"string": "{\"fre\": \"Personne\"}",
"date_create": "Fri Oct 16 11:05:04 2015",
"component": "EmType",
"rank": 1,
"help_text": "",
"sortcolumn": "rank"
},
"7": {
"nullable": true,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": true,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "{\"fre\": \"Sous-titre\"}",
"class_id": 1,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 2,
"fieldtype": "char",
"help_text": "",
"internal": false,
"rel_field_id": null,
"name": "soustitre"
},
"9": {
"nullable": true,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": false,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "{\"fre\": \"Nom\"}",
"class_id": 2,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 1,
"fieldtype": "char",
"help_text": "",
"internal": false,
"rel_field_id": null,
"name": "nom"
},
"10": {
"nullable": true,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": true,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "{\"fre\": \"Pr\\u00e9nom\"}",
"class_id": 2,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 2,
"fieldtype": "char",
"help_text": "",
"internal": false,
"rel_field_id": null,
"name": "prenom"
},
"11": {
"nullable": true,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": false,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "{\"fre\": \"Auteur\"}",
"class_id": 1,
"date_create": "Fri Oct 16 11:05:04 2015",
"rel_to_type_id": 6,
"rank": 1,
"fieldtype": "rel2type",
"help_text": "",
"internal": false,
"rel_field_id": null,
"name": "auteur"
},
"12": {
"nullable": true,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": false,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "{\"fre\": \"Adresse\"}",
"class_id": 1,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 2,
"fieldtype": "char",
"help_text": "",
"internal": false,
"rel_field_id": 11,
"name": "adresse"
},
"13": {
"component": "EmClass",
"date_create": "Fri Oct 16 11:05:04 2015",
"date_update": "Fri Oct 16 11:05:04 2015",
"sortcolumn": "rank",
"classtype": "entity",
"icon": "0",
"rank": 2,
"name": "publication",
"help_text": "",
"string": "{\"fre\": \"Publication\"}"
},
"14": {
"date_update": "Fri Oct 16 11:05:04 2015",
"class_id": 13,
"icon": "0",
"name": "rubrique",
"superiors_list": {
"parent": [
14,
19
]
},
"fields_list": [],
"string": "{\"fre\": \"Rubrique\"}",
"date_create": "Fri Oct 16 11:05:04 2015",
"component": "EmType",
"rank": 1,
"help_text": "",
"sortcolumn": "rank"
},
"16": {
"nullable": true,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": false,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "{\"fre\": \"Titre\"}",
"class_id": 13,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 1,
"fieldtype": "char",
"help_text": "",
"internal": false,
"rel_field_id": null,
"name": "titre"
},
"18": {
"nullable": true,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": true,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "{\"fre\": \"Age\"}",
"class_id": 2,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 3,
"fieldtype": "char",
"help_text": "",
"internal": false,
"rel_field_id": null,
"name": "age"
},
"19": {
"date_update": "Fri Oct 16 11:05:04 2015",
"class_id": 13,
"icon": "0",
"name": "numero",
"superiors_list": {},
"fields_list": [],
"string": "{\"fre\": \"Num\\u00e9ro\"}",
"date_create": "Fri Oct 16 11:05:04 2015",
"component": "EmType",
"rank": 2,
"help_text": "",
"sortcolumn": "rank"
},
"21": {
"nullable": true,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": true,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "{\"fre\": \"Bleu\"}",
"class_id": 1,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 1,
"fieldtype": "char",
"help_text": "",
"internal": false,
"rel_field_id": null,
"name": "bleu"
},
"23": {
"nullable": false,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": false,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "",
"class_id": 1,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 1,
"fieldtype": "integer",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"name": "class_id"
},
"24": {
"nullable": true,
"date_update": "Fri Oct 16 11:05:04 2015",
"max_length": 128,
"icon": "0",
"optional": false,
"component": "EmField",
"string": "",
"class_id": 1,
"date_create": "Fri Oct 16 11:05:04 2015",
"name": "string",
"rank": 2,
"fieldtype": "char",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"uniq": false
},
"25": {
"nullable": false,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": false,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "",
"class_id": 1,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 3,
"fieldtype": "integer",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"name": "type_id"
},
"26": {
"nullable": false,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": false,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "",
"class_id": 1,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 4,
"fieldtype": "pk",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"name": "lodel_id"
},
"28": {
"nullable": false,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": false,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "",
"class_id": 2,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 1,
"fieldtype": "integer",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"name": "class_id"
},
"29": {
"nullable": true,
"date_update": "Fri Oct 16 11:05:04 2015",
"max_length": 128,
"icon": "0",
"optional": false,
"component": "EmField",
"string": "",
"class_id": 2,
"date_create": "Fri Oct 16 11:05:04 2015",
"name": "string",
"rank": 2,
"fieldtype": "char",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"uniq": false
},
"30": {
"nullable": false,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": false,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "",
"class_id": 2,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 3,
"fieldtype": "integer",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"name": "type_id"
},
"31": {
"nullable": false,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": false,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "",
"class_id": 2,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 4,
"fieldtype": "pk",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"name": "lodel_id"
},
"33": {
"nullable": false,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": false,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "",
"class_id": 13,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 1,
"fieldtype": "integer",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"name": "class_id"
},
"34": {
"nullable": true,
"date_update": "Fri Oct 16 11:05:04 2015",
"max_length": 128,
"icon": "0",
"optional": false,
"component": "EmField",
"string": "",
"class_id": 13,
"date_create": "Fri Oct 16 11:05:04 2015",
"name": "string",
"rank": 2,
"fieldtype": "char",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"uniq": false
},
"35": {
"nullable": false,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": false,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "",
"class_id": 13,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 3,
"fieldtype": "integer",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"name": "type_id"
},
"36": {
"nullable": false,
"date_update": "Fri Oct 16 11:05:04 2015",
"optional": false,
"icon": "0",
"uniq": false,
"component": "EmField",
"string": "",
"class_id": 13,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 4,
"fieldtype": "pk",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"name": "lodel_id"
},
"37": {
"nullable": false,
"date_update": "Wed Nov 4 10:52:13 2015",
"optional": false,
"rank": 5,
"icon": "0",
"name": "modification_date",
"component": "EmField",
"string": "",
"class_id": 1,
"date_create": "Wed Nov 4 10:52:13 2015",
"now_on_create": true,
"internal": "automatic",
"fieldtype": "datetime",
"now_on_update": true,
"help_text": "",
"rel_field_id": null,
"uniq": false
},
"38": {
"nullable": false,
"date_update": "Wed Nov 4 10:52:13 2015",
"rel_field_id": null,
"icon": "0",
"uniq": false,
"component": "EmField",
"now_on_create": true,
"class_id": 1,
"date_create": "Wed Nov 4 10:52:13 2015",
"rank": 6,
"string": "",
"help_text": "",
"internal": "automatic",
"optional": false,
"name": "creation_date",
"fieldtype": "datetime"
},
"39": {
"nullable": false,
"date_update": "Wed Nov 4 10:52:13 2015",
"rel_field_id": null,
"icon": "0",
"uniq": false,
"component": "EmField",
"now_on_create": true,
"class_id": 2,
"date_create": "Wed Nov 4 10:52:13 2015",
"rank": 5,
"string": "",
"now_on_update": true,
"help_text": "",
"internal": "automatic",
"optional": false,
"name": "modification_date",
"fieldtype": "datetime"
},
"40": {
"nullable": false,
"date_update": "Wed Nov 4 10:52:13 2015",
"rel_field_id": null,
"icon": "0",
"uniq": false,
"component": "EmField",
"now_on_create": true,
"class_id": 2,
"date_create": "Wed Nov 4 10:52:13 2015",
"rank": 6,
"string": "",
"help_text": "",
"internal": "automatic",
"optional": false,
"name": "creation_date",
"fieldtype": "datetime"
},
"41": {
"nullable": false,
"date_update": "Wed Nov 4 10:52:13 2015",
"rel_field_id": null,
"icon": "0",
"uniq": false,
"component": "EmField",
"now_on_create": true,
"class_id": 13,
"date_create": "Wed Nov 4 10:52:13 2015",
"rank": 5,
"string": "",
"now_on_update": true,
"help_text": "",
"internal": "automatic",
"optional": false,
"name": "modification_date",
"fieldtype": "datetime"
},
"42": {
"nullable": false,
"date_update": "Wed Nov 4 10:52:13 2015",
"rel_field_id": null,
"icon": "0",
"uniq": false,
"component": "EmField",
"now_on_create": true,
"class_id": 13,
"date_create": "Wed Nov 4 10:52:13 2015",
"rank": 6,
"string": "",
"help_text": "",
"internal": "automatic",
"optional": false,
"name": "creation_date",
"fieldtype": "datetime"
}
}

28
install/loader.py Normal file
View file

@ -0,0 +1,28 @@
import settings
import importlib
import sys
import os
sys.path.append(settings.lodel2_lib_path)
# Import dynamic code
if os.path.isfile(settings.dynamic_code):
from dynleapi import *
# Import wanted datasource objects
for db_modname in ['leapidatasource', 'migrationhandler']:
mod = importlib.import_module("DataSource.{pkg_name}.{mod_name}".format(
pkg_name=settings.ds_package,
mod_name=db_modname,
)
)
# Expose the module in globals
globals()[db_modname] = mod
if __name__ == '__main__':
import code
print("""
Running interactive python in Lodel2 %s instance environment
"""%settings.name)
code.interact(local=locals())

21
install/settings.py Normal file
View file

@ -0,0 +1,21 @@
#-*- coding:utf8 -*-
import pymysql
name = 'LODEL2_INSTANCE_NAME'
lodel2_lib_path = 'LODEL2_LIB_ABS_PATH'
emfile = 'em.json'
dynamic_code = 'dynleapi.py'
ds_package = 'MySQL'
mh_classname = 'MysqlMigrationHandler'
datasource = {
'default': {
'module': pymysql,
'host': '127.0.0.1',
'user': 'DBUSER',
'passwd': 'DBPASSWORD',
'db': 'DBNAME'
}
}

29
install/utils.py Executable file
View file

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
import settings
from settings import *
from loader import *
def refreshdyn():
import sys
from EditorialModel.model import Model
from leapi.lefactory import LeFactory
from EditorialModel.backend.json_backend import EmBackendJson
from DataSource.MySQL.leapidatasource import LeDataSourceSQL
OUTPUT = dynamic_code
EMJSON = emfile
# Load editorial model
em = Model(EmBackendJson(EMJSON))
# Generate dynamic code
fact = LeFactory(OUTPUT)
# Create the python file
fact.create_pyfile(em, LeDataSourceSQL, {})
def db_init():
from EditorialModel.backend.json_backend import EmBackendJson
from EditorialModel.model import Model
mh = getattr(migrationhandler,settings.mh_classname)(**(settings.datasource['default']))
em = Model(EmBackendJson(settings.emfile))
em.migrate_handler(mh)

47
lodel_init.sh Executable file
View file

@ -0,0 +1,47 @@
#!/bin/bash
usage() {
echo "Usage : $0 instance_name instance_dir [lodel_libdir]" 1>&2
exit 1
}
if [ $# -lt 2 ]
then
echo "Not enough arguments" 1>&2
usage
fi
name="$1"
instdir="$2"
libdir="$3"
libdir="${libdir:=$(realpath $(dirname $0))}"
emfilename="em.json"
settings="$instdir/settings.py"
em="$instdir/em.json"
dyncode="$instdir/${name}.py"
if [ -e "$instdir" ]
then
echo "Abording... "$instdir" exists" 1>&2
exit 1
fi
echo "Creating lodel instance directory '$instdir'"
mkdir -pv "$instdir"
cp -v $libdir/install/* $instdir
sed -i -e "s#LODEL2_LIB_ABS_PATH#$libdir#" "$settings"
sed -i -e "s#LODEL2_INSTANCE_NAME#$name#" "$settings"
echo "Generating dynamic code"
cd "$instdir"
make refreshdyn
echo "Cleaning instance directory"
make clean
echo -e "\nInstance successfully created in $instdir"
echo -e "============================\n"
echo "Now you should edit '$settings' and then run : cd $instdir && make dbinit"

View file

@ -1,15 +1,17 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import sys
from EditorialModel.model import Model from EditorialModel.model import Model
from leapi.lefactory import LeFactory from leapi.lefactory import LeFactory
from EditorialModel.backend.json_backend import EmBackendJson from EditorialModel.backend.json_backend import EmBackendJson
from leapi.datasources.ledatasourcesql import LeDataSourceSQL from leapi.datasources.ledatasourcesql import LeDataSourceSQL
OUTPUT = 'leapi/dyn.py' OUTPUT = 'leapi/dyn.py' if len(sys.argv) == 1 else sys.argv[1]
EMJSON = 'EditorialModel/test/me.json' if len(sys.argv) < 3 else sys.argv[2]
em = Model(EmBackendJson('EditorialModel/test/me.json')) em = Model(EmBackendJson(EMJSON))
fact = LeFactory('leapi/dyn.py') fact = LeFactory(OUTPUT)
fact.create_pyfile(em, LeDataSourceSQL, {}) fact.create_pyfile(em, LeDataSourceSQL, {})
print(fact.generate_python(em, LeDataSourceSQL, {})) print(fact.generate_python(em, LeDataSourceSQL, {}))