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 8.5KB

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