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.4KB

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