No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

migration_handler.py 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. # -*- coding: utf-8 -*-
  2. import datetime
  3. from lodel.editorial_model.components import EmClass, EmField
  4. from .utils import get_connection_args, connect, collection_prefix, object_collection_name, mongo_fieldname
  5. from lodel.leapi.datahandlers.base_classes import DataHandler
  6. from lodel.plugin import LodelHook
  7. class MigrationHandlerChangeError(Exception):
  8. pass
  9. class MigrationHandlerError(Exception):
  10. pass
  11. @LodelHook('mongodb_mh_init_db')
  12. def mongodb_mh_init_db(conn_args=None):
  13. connection_args = get_connection_args('default') if conn_args is None else get_connection_args(conn_args['name'])
  14. migration_handler = MongoDbMigrationHandler(connection_args)
  15. migration_handler._install_collections()
  16. migration_handler.database.close()
  17. class MongoDbMigrationHandler(object):
  18. COMMANDS_IFEXISTS_DROP = 'drop'
  19. COMMANDS_IFEXISTS_NOTHING = 'nothing'
  20. INIT_COLLECTIONS_NAMES = ['object', 'relation', 'entitie', 'person', 'text', 'entry']
  21. MIGRATION_HANDLER_DEFAULT_SETTINGS = {'dry_run': False, 'foreign_keys': True, 'drop_if_exists': False}
  22. ## @brief Constructs a MongoDbMigrationHandler
  23. # @param conn_args dict : a dictionary containing the connection options
  24. # @param **kwargs : extra arguments
  25. def __init__(self, conn_args=None, **kwargs):
  26. conn_args = get_connection_args() if conn_args is None else conn_args
  27. if len(conn_args.keys()) == 0:
  28. raise MigrationHandlerError("No connection arguments were given")
  29. if 'host' not in conn_args.keys() or 'port' not in conn_args.keys() or 'db_name' not in conn_args.keys() \
  30. or 'username' not in conn_args.keys() or 'password' not in conn_args.keys():
  31. raise MigrationHandlerError("Missing connection arguments")
  32. #self.connection_name = conn_args['name']
  33. self.database = connect(host=conn_args['host'], port=conn_args['port'], db_name=conn_args['db_name'],
  34. username=conn_args['username'], password=conn_args['password'])
  35. self.dry_run = kwargs['dry_run'] if 'dry_run' in kwargs else \
  36. MongoDbMigrationHandler.MIGRATION_HANDLER_DEFAULT_SETTINGS['dry_run']
  37. self.foreign_keys = kwargs['foreign_keys'] if 'foreign_keys' in kwargs else \
  38. MongoDbMigrationHandler.MIGRATION_HANDLER_DEFAULT_SETTINGS['foreign_keys']
  39. self.drop_if_exists = kwargs['drop_if_exists'] if 'drop_is_exists' in kwargs else \
  40. MongoDbMigrationHandler.MIGRATION_HANDLER_DEFAULT_SETTINGS['drop_if_exists']
  41. #self._install_collections()
  42. ## @brief Installs the basis collections of the database
  43. def _install_collections(self):
  44. for collection_name in MongoDbMigrationHandler.INIT_COLLECTIONS_NAMES:
  45. prefix = collection_prefix['object'] if collection_name != 'relation' else collection_prefix['relation']
  46. collection_to_create = "%s%s" % (prefix, collection_name)
  47. self._create_collection(collection_name=collection_to_create)
  48. ## @brief Creates a collection in the database
  49. # @param collection_name str
  50. # @param charset str : default value is "utf8"
  51. # @param if_exists str : defines the behavior to have if the collection to create already exists (default value : "nothing")
  52. def _create_collection(self, collection_name, charset='utf8', if_exists=COMMANDS_IFEXISTS_NOTHING):
  53. if collection_name in self.database.collection_names(include_system_collections=False):
  54. if if_exists == MongoDbMigrationHandler.COMMANDS_IFEXISTS_DROP:
  55. self._delete_collection(collection_name)
  56. self.database.create_collection(name=collection_name)
  57. else:
  58. self.database.create_collection(name=collection_name)
  59. ## @brief Deletes a collection in the database
  60. # @param collection_name str
  61. def _delete_collection(self, collection_name):
  62. collection = self.database[collection_name]
  63. collection.drop_indexes()
  64. collection.drop()
  65. ## @brief Performs a change in the Database, corresponding to an Editorial Model change
  66. # @param model EditorialModel
  67. # @param uid str : the uid of the changing component
  68. # @param initial_state dict|None : dictionnary of the initial state of the component, None means it's a creation
  69. # @param new_state dict|None: dictionnary of the new state of the component, None means it's a deletion
  70. # @note Only the changing properties are added in these state dictionaries
  71. # @throw ValueError if no state has been precised or if the component considered in the change is neither an EmClass nor an EmField instance
  72. def register_change(self, model, uid, initial_state, new_state):
  73. if initial_state is None and new_state is None:
  74. raise ValueError('An Editorial Model change should have at least one state precised (initial or new), '
  75. 'none given here')
  76. if initial_state is None:
  77. state_change = 'new'
  78. elif new_state is None:
  79. state_change = 'del'
  80. else:
  81. state_change = 'upgrade'
  82. component_class_name = None
  83. if isinstance(model.classes(uid), EmClass):
  84. component_class_name = 'emclass'
  85. elif isinstance(model.classes(uid), EmField):
  86. component_class_name = 'emfield'
  87. if component_class_name:
  88. handler_func('_'+component_class_name.lower()+'_'+state_change)
  89. if hasattr(self, handler_func):
  90. getattr(self, handler_func)(model, uid, initial_state, new_state)
  91. else:
  92. raise ValueError("The component concerned should be an EmClass or EmField instance, %s given",
  93. model.classes(uid).__class__)
  94. def register_model_state(self, em, state_hash):
  95. pass
  96. ## @brief creates a new collection corresponding to a given uid
  97. # @see register_change()
  98. def _emclass_new(self, model, uid, initial_state, new_state):
  99. collection_name = object_collection_name(model.classes(uid))
  100. self._create_collection(collection_name)
  101. ## @brief deletes a collection corresponding to a given uid
  102. # @see register_change()
  103. def _emclass_delete(self, model, uid, initial_state, new_state):
  104. if uid not in MongoDbMigrationHandler.INIT_COLLECTIONS_NAMES:
  105. collection_name = object_collection_name(model.classes(uid))
  106. self._delete_collection(collection_name)
  107. ## @brief creates a new field in a collection
  108. # @see register_change()
  109. def _emfield_new(self, model, uid, initial_state, new_state):
  110. if new_state['data_handler'] == 'relation':
  111. class_name = self.class_collection_name_from_field(model, new_state)
  112. self._create_field_in_collection(class_name, uid, new_state)
  113. else:
  114. collection_name = self._class_collection_name_from_field(model, new_state)
  115. field_definition = self._field_definition(new_state['data_handler'], new_state)
  116. self._create_field_in_collection(collection_name, uid, field_definition)
  117. ## @brief deletes a field in a collection
  118. # @see register_change()
  119. def _emfield_del(self, model, uid, initial_state, new_state):
  120. collection_name = self._class_collection_name_from_field(model, initial_state)
  121. field_name = mongo_fieldname(model.field(uid).name)
  122. self._delete_field_in_collection(collection_name, field_name)
  123. ## @brief upgrades a field
  124. def _emfield_upgrade(self, model, uid, initial_state, new_state):
  125. collection_name = self._class_collection_name_from_field(model, initial_state)
  126. field_name = mongo_fieldname(model.field(uid).name)
  127. self._check_field_in_collection(collection_name, field_name, initial_state, new_state)
  128. def _check_field_in_collection(self,collection_name, field_name, initial_sate, new_state):
  129. collection = self.database[collection_name]
  130. field_name = mongo_fieldname(field_name)
  131. cursor = collection.find({field_name: {'$exists': True}}, {field_name: 1})
  132. for document in cursor:
  133. # TODO vérifier que le champ contient une donnée compatible (document[field_name])
  134. pass
  135. ## @brief Defines the default value when a new field is added to a collection's items
  136. # @param fieldtype str : name of the field's type
  137. # @param options dict : dictionary giving the options to use to initiate the field's value.
  138. # @return dict (containing a 'default' key with the default value)
  139. def _field_definition(self, fieldtype, options):
  140. basic_type = DataHandler.from_name(fieldtype).ftype
  141. if basic_type == 'datetime':
  142. if 'now_on_create' in options and options['now_on_create']:
  143. return {'default': datetime.datetime.utcnow()}
  144. if basic_type == 'relation':
  145. return {'default': []}
  146. return {'default': ''}
  147. def _class_collection_name_from_field(self, model, field):
  148. class_id = field['class_id']
  149. component_class = model.classes(class_id)
  150. component_collection = object_collection_name(component_class)
  151. return component_collection
  152. ## @brief Creates a new field in a collection
  153. # @param collection_name str
  154. # @param field str
  155. # @param options dict
  156. def _create_field_in_collection(self, collection_name, field, options):
  157. emfield = EmField(field)
  158. field_name = mongo_fieldname(field)
  159. self.database[collection_name].update_many({'uid': emfield.get_emclass_uid(), field_name: {'$exists': False}},
  160. {'$set': {field_name: options['default']}}, False)
  161. ## @brief Deletes a field in a collection
  162. # @param collection_name str
  163. # @param field_name str
  164. def _delete_field_in_collection(self, collection_name, field_name):
  165. if field_name != '_id':
  166. field_name = mongo_fieldname(field_name)
  167. self.database[collection_name].update_many({field_name: {'$exists': True}},
  168. {'$unset': {field_name:1}}, False)