123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- # -*- coding: utf-8 -*-
- import datetime
-
- from lodel.editorial_model.components import EmClass, EmField
-
- from .utils import get_connection_args, connect, collection_prefix, object_collection_name, mongo_fieldname
- from lodel.leapi.datahandlers.base_classes import DataHandler
- from lodel.plugin import LodelHook
-
-
- class MigrationHandlerChangeError(Exception):
- pass
-
-
- class MigrationHandlerError(Exception):
- pass
-
- @LodelHook('mongodb_mh_init_db')
- def mongodb_mh_init_db(conn_args=None):
- connection_args = get_connection_args('default') if conn_args is None else get_connection_args(conn_args['name'])
- migration_handler = MongoDbMigrationHandler(connection_args)
- migration_handler._install_collections()
- migration_handler.database.close()
-
- class MongoDbMigrationHandler(object):
-
- COMMANDS_IFEXISTS_DROP = 'drop'
- COMMANDS_IFEXISTS_NOTHING = 'nothing'
- INIT_COLLECTIONS_NAMES = ['object', 'relation', 'entitie', 'person', 'text', 'entry']
- MIGRATION_HANDLER_DEFAULT_SETTINGS = {'dry_run': False, 'foreign_keys': True, 'drop_if_exists': False}
-
- ## @brief Constructs a MongoDbMigrationHandler
- # @param conn_args dict : a dictionary containing the connection options
- # @param **kwargs : extra arguments
- def __init__(self, conn_args=None, **kwargs):
- conn_args = get_connection_args() if conn_args is None else conn_args
-
- if len(conn_args.keys()) == 0:
- raise MigrationHandlerError("No connection arguments were given")
-
- if 'host' not in conn_args.keys() or 'port' not in conn_args.keys() or 'db_name' not in conn_args.keys() \
- or 'username' not in conn_args.keys() or 'password' not in conn_args.keys():
- raise MigrationHandlerError("Missing connection arguments")
-
- #self.connection_name = conn_args['name']
- self.database = connect(host=conn_args['host'], port=conn_args['port'], db_name=conn_args['db_name'],
- username=conn_args['username'], password=conn_args['password'])
-
- self.dry_run = kwargs['dry_run'] if 'dry_run' in kwargs else \
- MongoDbMigrationHandler.MIGRATION_HANDLER_DEFAULT_SETTINGS['dry_run']
-
- self.foreign_keys = kwargs['foreign_keys'] if 'foreign_keys' in kwargs else \
- MongoDbMigrationHandler.MIGRATION_HANDLER_DEFAULT_SETTINGS['foreign_keys']
-
- self.drop_if_exists = kwargs['drop_if_exists'] if 'drop_is_exists' in kwargs else \
- MongoDbMigrationHandler.MIGRATION_HANDLER_DEFAULT_SETTINGS['drop_if_exists']
-
- #self._install_collections()
-
- ## @brief Installs the basis collections of the database
- def _install_collections(self):
- for collection_name in MongoDbMigrationHandler.INIT_COLLECTIONS_NAMES:
- prefix = collection_prefix['object'] if collection_name != 'relation' else collection_prefix['relation']
- collection_to_create = "%s%s" % (prefix, collection_name)
- self._create_collection(collection_name=collection_to_create)
-
- ## @brief Creates a collection in the database
- # @param collection_name str
- # @param charset str : default value is "utf8"
- # @param if_exists str : defines the behavior to have if the collection to create already exists (default value : "nothing")
- def _create_collection(self, collection_name, charset='utf8', if_exists=COMMANDS_IFEXISTS_NOTHING):
- if collection_name in self.database.collection_names(include_system_collections=False):
- if if_exists == MongoDbMigrationHandler.COMMANDS_IFEXISTS_DROP:
- self._delete_collection(collection_name)
- self.database.create_collection(name=collection_name)
- else:
- self.database.create_collection(name=collection_name)
-
- ## @brief Deletes a collection in the database
- # @param collection_name str
- def _delete_collection(self, collection_name):
- collection = self.database[collection_name]
- collection.drop_indexes()
- collection.drop()
-
- ## @brief Performs a change in the Database, corresponding to an Editorial Model change
- # @param model EditorialModel
- # @param uid str : the uid of the changing component
- # @param initial_state dict|None : dictionnary of the initial state of the component, None means it's a creation
- # @param new_state dict|None: dictionnary of the new state of the component, None means it's a deletion
- # @note Only the changing properties are added in these state dictionaries
- # @throw ValueError if no state has been precised or if the component considered in the change is neither an EmClass nor an EmField instance
- def register_change(self, model, uid, initial_state, new_state):
-
- if initial_state is None and new_state is None:
- raise ValueError('An Editorial Model change should have at least one state precised (initial or new), '
- 'none given here')
-
- if initial_state is None:
- state_change = 'new'
- elif new_state is None:
- state_change = 'del'
- else:
- state_change = 'upgrade'
-
- component_class_name = None
- if isinstance(model.classes(uid), EmClass):
- component_class_name = 'emclass'
- elif isinstance(model.classes(uid), EmField):
- component_class_name = 'emfield'
-
- if component_class_name:
- handler_func('_'+component_class_name.lower()+'_'+state_change)
- if hasattr(self, handler_func):
- getattr(self, handler_func)(model, uid, initial_state, new_state)
- else:
- raise ValueError("The component concerned should be an EmClass or EmField instance, %s given",
- model.classes(uid).__class__)
-
- def register_model_state(self, em, state_hash):
- pass
-
- ## @brief creates a new collection corresponding to a given uid
- # @see register_change()
- def _emclass_new(self, model, uid, initial_state, new_state):
- collection_name = object_collection_name(model.classes(uid))
- self._create_collection(collection_name)
-
- ## @brief deletes a collection corresponding to a given uid
- # @see register_change()
- def _emclass_delete(self, model, uid, initial_state, new_state):
- if uid not in MongoDbMigrationHandler.INIT_COLLECTIONS_NAMES:
- collection_name = object_collection_name(model.classes(uid))
- self._delete_collection(collection_name)
-
- ## @brief creates a new field in a collection
- # @see register_change()
- def _emfield_new(self, model, uid, initial_state, new_state):
- if new_state['data_handler'] == 'relation':
- class_name = self.class_collection_name_from_field(model, new_state)
- self._create_field_in_collection(class_name, uid, new_state)
- else:
- collection_name = self._class_collection_name_from_field(model, new_state)
- field_definition = self._field_definition(new_state['data_handler'], new_state)
- self._create_field_in_collection(collection_name, uid, field_definition)
-
- ## @brief deletes a field in a collection
- # @see register_change()
- def _emfield_del(self, model, uid, initial_state, new_state):
- collection_name = self._class_collection_name_from_field(model, initial_state)
- field_name = mongo_fieldname(model.field(uid).name)
- self._delete_field_in_collection(collection_name, field_name)
-
- ## @brief upgrades a field
- def _emfield_upgrade(self, model, uid, initial_state, new_state):
- collection_name = self._class_collection_name_from_field(model, initial_state)
- field_name = mongo_fieldname(model.field(uid).name)
- self._check_field_in_collection(collection_name, field_name, initial_state, new_state)
-
- def _check_field_in_collection(self,collection_name, field_name, initial_sate, new_state):
- collection = self.database[collection_name]
- field_name = mongo_fieldname(field_name)
- cursor = collection.find({field_name: {'$exists': True}}, {field_name: 1})
- for document in cursor:
- # TODO vérifier que le champ contient une donnée compatible (document[field_name])
- pass
-
- ## @brief Defines the default value when a new field is added to a collection's items
- # @param fieldtype str : name of the field's type
- # @param options dict : dictionary giving the options to use to initiate the field's value.
- # @return dict (containing a 'default' key with the default value)
- def _field_definition(self, fieldtype, options):
- basic_type = DataHandler.from_name(fieldtype).ftype
- if basic_type == 'datetime':
- if 'now_on_create' in options and options['now_on_create']:
- return {'default': datetime.datetime.utcnow()}
- if basic_type == 'relation':
- return {'default': []}
-
- return {'default': ''}
-
- def _class_collection_name_from_field(self, model, field):
- class_id = field['class_id']
- component_class = model.classes(class_id)
- component_collection = object_collection_name(component_class)
- return component_collection
-
-
- ## @brief Creates a new field in a collection
- # @param collection_name str
- # @param field str
- # @param options dict
- def _create_field_in_collection(self, collection_name, field, options):
- emfield = EmField(field)
- field_name = mongo_fieldname(field)
- self.database[collection_name].update_many({'uid': emfield.get_emclass_uid(), field_name: {'$exists': False}},
- {'$set': {field_name: options['default']}}, False)
-
- ## @brief Deletes a field in a collection
- # @param collection_name str
- # @param field_name str
- def _delete_field_in_collection(self, collection_name, field_name):
- if field_name != '_id':
- field_name = mongo_fieldname(field_name)
- self.database[collection_name].update_many({field_name: {'$exists': True}},
- {'$unset': {field_name:1}}, False)
|