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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. #
  2. # This file is part of Lodel 2 (https://github.com/OpenEdition)
  3. #
  4. # Copyright (C) 2015-2017 Cléo UMS-3287
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU Affero General Public License as published
  8. # by the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU Affero General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU Affero General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. import datetime
  20. from lodel.context import LodelContext
  21. LodelContext.expose_modules(globals(), {
  22. 'lodel.editorial_model.components': ['EmClass', 'EmField'],
  23. 'lodel.editorial_model.model': ['EditorialModel'],
  24. 'lodel.leapi.datahandlers.base_classes': ['DataHandler'],
  25. 'lodel.plugin': ['LodelHook'],
  26. 'lodel.logger': 'logger'})
  27. from leapi_dyncode import * #<-- TODO : handle this !!!
  28. from .utils import connect, object_collection_name, mongo_fieldname
  29. from .datasource import MongoDbDatasource
  30. from .exceptions import *
  31. class MigrationHandler(object):
  32. ## @brief Constructs a MongoDbMigrationHandler
  33. # @param host str
  34. # @param port str
  35. # @param db_name str
  36. # @param username str
  37. # @param password str
  38. # @param charset str
  39. # @param dry_run bool
  40. # @param drop_if_exists bool : drops the table if it already exists
  41. def __init__(self, host, port, db_name, username, password,
  42. charset='utf-8', dry_run = False, drop_if_exists = False):
  43. self.database = connect(host, port, db_name, username, password)
  44. self.dry_run = dry_run
  45. self.drop_if_exists = drop_if_exists
  46. self.charset = charset # Useless ?
  47. logger.debug("MongoDb migration handler instanciated on db : \
  48. %s@%s:%s" % (db_name, host, port))
  49. ## @brief Installs the basis collections of the database
  50. def init_db(self, emclass_list):
  51. for collection_name in [ object_collection_name(cls)
  52. for cls in emclass_list]:
  53. self._create_collection(collection_name)
  54. ## @brief Creates a collection in the database
  55. # @param collection_name str
  56. def _create_collection(self, collection_name):
  57. existing = self.database.collection_names(
  58. include_system_collections=False)
  59. if collection_name in existing:
  60. if self.drop_if_exists:
  61. self._delete_collection(collection_name)
  62. logger.debug("Collection %s deleted before creating \
  63. it again" % collection_name)
  64. self.database.create_collection(name=collection_name)
  65. else:
  66. logger.info("Collection %s allready exists. \
  67. Doing nothing..." % collection_name)
  68. else:
  69. self.database.create_collection(name=collection_name)
  70. logger.debug("Collection %s created" % collection_name)
  71. ## @brief Deletes a collection in the database
  72. # @param collection_name str
  73. def _delete_collection(self, collection_name):
  74. collection = self.database[collection_name]
  75. collection.drop_indexes()
  76. collection.drop()
  77. ## @brief Performs a change in the Database, corresponding to an Editorial Model change
  78. # @param model EditorialModel
  79. # @param uid str : the uid of the changing component
  80. # @param initial_state dict|None : dictionnary of the initial state of the component, None means it's a creation
  81. # @param new_state dict|None: dictionnary of the new state of the component, None means it's a deletion
  82. # @note Only the changing properties are added in these state dictionaries
  83. # @throw ValueError if no state has been precised or if the component considered in the change is neither an EmClass nor an EmField instance
  84. def register_change(self, model, uid, initial_state, new_state):
  85. if initial_state is None and new_state is None:
  86. raise ValueError('An Editorial Model change should have at least one state precised (initial or new), '
  87. 'none given here')
  88. if initial_state is None:
  89. state_change = 'new'
  90. elif new_state is None:
  91. state_change = 'del'
  92. else:
  93. state_change = 'upgrade'
  94. component_class_name = None
  95. if isinstance(model.classes(uid), EmClass):
  96. component_class_name = 'emclass'
  97. elif isinstance(model.classes(uid), EmField):
  98. component_class_name = 'emfield'
  99. if component_class_name:
  100. handler_func = '_'+component_class_name.lower()+'_'+state_change
  101. if hasattr(self, handler_func):
  102. getattr(self, handler_func)(model, uid, initial_state, new_state)
  103. else:
  104. raise ValueError("The component concerned should be an EmClass or EmField instance, %s given",
  105. model.classes(uid).__class__)
  106. def register_model_state(self, em, state_hash):
  107. pass
  108. ## @brief creates a new collection corresponding to a given uid
  109. # @see register_change()
  110. def _emclass_new(self, model, uid, initial_state, new_state):
  111. collection_name = object_collection_name(model.classes(uid))
  112. self._create_collection(collection_name)
  113. ## @brief deletes a collection corresponding to a given uid
  114. # @see register_change()
  115. def _emclass_delete(self, model, uid, initial_state, new_state):
  116. collection_name = object_collection_name(model.classes(uid))
  117. self._delete_collection(collection_name)
  118. ## @brief creates a new field in a collection
  119. # @see register_change()
  120. def _emfield_new(self, model, uid, initial_state, new_state):
  121. if new_state['data_handler'] == 'relation':
  122. class_name = self.class_collection_name_from_field(model, new_state)
  123. self._create_field_in_collection(class_name, uid, new_state)
  124. else:
  125. collection_name = self._class_collection_name_from_field(model, new_state)
  126. field_definition = self._field_definition(new_state['data_handler'], new_state)
  127. self._create_field_in_collection(collection_name, uid, field_definition)
  128. ## @brief deletes a field in a collection
  129. # @see register_change()
  130. def _emfield_del(self, model, uid, initial_state, new_state):
  131. collection_name = self._class_collection_name_from_field(model, initial_state)
  132. field_name = mongo_fieldname(model.field(uid).name)
  133. self._delete_field_in_collection(collection_name, field_name)
  134. ## @brief upgrades a field
  135. def _emfield_upgrade(self, model, uid, initial_state, new_state):
  136. collection_name = self._class_collection_name_from_field(model, initial_state)
  137. field_name = mongo_fieldname(model.field(uid).name)
  138. self._check_field_in_collection(collection_name, field_name, initial_state, new_state)
  139. def _check_field_in_collection(self,collection_name, field_name, initial_sate, new_state):
  140. collection = self.database[collection_name]
  141. field_name = mongo_fieldname(field_name)
  142. cursor = collection.find({field_name: {'$exists': True}}, {field_name: 1})
  143. for document in cursor:
  144. # TODO vérifier que le champ contient une donnée compatible (document[field_name])
  145. pass
  146. ## @brief Defines the default value when a new field is added to a collection's items
  147. # @param fieldtype str : name of the field's type
  148. # @param options dict : dictionary giving the options to use to initiate the field's value.
  149. # @return dict (containing a 'default' key with the default value)
  150. def _field_definition(self, fieldtype, options):
  151. basic_type = DataHandler.from_name(fieldtype).ftype
  152. if basic_type == 'datetime':
  153. if 'now_on_create' in options and options['now_on_create']:
  154. return {'default': datetime.datetime.utcnow()}
  155. if basic_type == 'relation':
  156. return {'default': []}
  157. return {'default': ''}
  158. def _class_collection_name_from_field(self, model, field):
  159. class_id = field['class_id']
  160. component_class = model.classes(class_id)
  161. component_collection = object_collection_name(component_class)
  162. return component_collection
  163. ## @brief Creates a new field in a collection
  164. # @param collection_name str
  165. # @param field str
  166. # @param options dict
  167. def _create_field_in_collection(self, collection_name, field, options):
  168. emfield = EmField(field)
  169. field_name = mongo_fieldname(field)
  170. self.database[collection_name].update_many({'uid': emfield.get_emclass_uid(), field_name: {'$exists': False}},
  171. {'$set': {field_name: options['default']}}, False)
  172. ## @brief Deletes a field in a collection
  173. # @param collection_name str
  174. # @param field_name str
  175. def _delete_field_in_collection(self, collection_name, field_name):
  176. if field_name != '_id':
  177. field_name = mongo_fieldname(field_name)
  178. self.database[collection_name].update_many({field_name: {'$exists': True}},
  179. {'$unset': {field_name:1}}, False)