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

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