|
@@ -11,138 +11,154 @@ from lodel.editorial_model.model import EditorialModel
|
11
|
11
|
class MigrationHandlerChangeError(Exception):
|
12
|
12
|
pass
|
13
|
13
|
|
14
|
|
-
|
15
|
14
|
class MongoDbMigrationHandler(GenericMigrationHandler):
|
16
|
15
|
|
17
|
16
|
COMMANDS_IFEXISTS_DROP = 'drop'
|
18
|
17
|
COMMANDS_IFEXISTS_NOTHING = 'nothing'
|
19
|
18
|
|
|
19
|
+ INIT_COLLECTIONS_NAMES = ['object', 'relation', 'entitie', 'person', 'text', 'entry']
|
|
20
|
+
|
20
|
21
|
## @brief constructs a MongoDbMigrationHandler
|
21
|
|
- # @param conn_args dict : a dictionary containing connection options
|
22
|
|
- # @param **kwargs : extra arguments given to the connection method
|
|
22
|
+ # @param conn_args dict : a dictionary containing the connection options
|
|
23
|
+ # @param **kwargs : extra arguments
|
23
|
24
|
def __init__(self, conn_args=None, **kwargs):
|
24
|
25
|
|
25
|
26
|
if conn_args is None:
|
26
|
|
- conn_args = {} # TODO : get the connection parameters in the settings
|
|
27
|
+ conn_args = utils.get_connection_args()
|
27
|
28
|
|
28
|
29
|
self.connection_name = conn_args['name']
|
29
|
|
-
|
30
|
30
|
self.database = utils.mongodbconnect(self.connection_name)
|
31
|
31
|
|
32
|
|
- # === Migration settings ===
|
33
|
|
- # TODO reimplement the settings management here
|
34
|
|
- mh_settings = {'dry_run': False, 'foreign_keys': True, 'drop_if_exists': False}
|
35
|
|
- self.dryrun = kwargs['dryrun'] if 'dryrun' in kwargs else mh_settings['dryrun']
|
36
|
|
- self.foreign_keys = kwargs['foreign_keys'] if 'foreign_keys' in kwargs else mh_settings['forein_keys']
|
37
|
|
- self.drop_if_exists = kwargs['drop_if_exists'] if 'drop_if_exists' in kwargs else mh_settings['drop_if_exists']
|
|
32
|
+ # TODO : get the following parameters in the settings ?
|
|
33
|
+ migrationhandler_settings = {'dry_run': False, 'foreign_keys': True, 'drop_if_exists': False}
|
38
|
34
|
|
39
|
|
- self._main_collection_name = 'object'
|
40
|
|
- self._relation_collection_name = 'relations'
|
|
35
|
+ self.dryrun = kwargs['dryrun'] if 'dryrun' in kwargs else migrationhandler_settings['dry_run']
|
|
36
|
+ self.foreign_keys = kwargs['foreign_keys'] if 'foreign_keys' in kwargs else migrationhandler_settings['foreign_keys']
|
|
37
|
+ self.drop_if_exists = kwargs['drop_if_exists'] if 'drop_if_exists' in kwargs else migrationhandler_settings['drop_if_exists']
|
41
|
38
|
|
42
|
|
- self._install_tables()
|
|
39
|
+ self._install_collections()
|
43
|
40
|
|
44
|
|
- def _install_tables(self):
|
45
|
|
- self._create_collection(self._main_collection_name)
|
46
|
|
- self._create_collection(self._relation_collection_name)
|
|
41
|
+ def _install_collections(self):
|
|
42
|
+ for collection_name in MongoDbMigrationHandler.INIT_COLLECTIONS_NAMES:
|
|
43
|
+ collection_to_create = "%s%s" % ('class_', collection_name)
|
|
44
|
+ self._create_collection(collection_name=collection_to_create)
|
47
|
45
|
|
48
|
|
- ## @brief Records a change in the EditorialModel and indicates whether or not it is possible to commit it in the database
|
49
|
|
- # @note The states contains only the changing fields
|
50
|
|
- # @param model EditorialModel : The EditorialModel object providing the global context
|
|
46
|
+ ## @brief Performs a change in the EditorialModel and indicates
|
|
47
|
+ # @note The states contains only the changing fields in the form of a dict : {field_name1: fieldvalue1, ...}
|
|
48
|
+ # @param model EditorialModel
|
51
|
49
|
# @param uid str : the uid of the changing component
|
52
|
|
- # @param initial_state dict|None: dict representing the original state ({field_name: field_value, ...}). None means the component is created
|
53
|
|
- # @param new_state dict|None: dict representing the new state ({field_name: field_value, ...}). None means the component is deleted
|
|
50
|
+ # @param initial_state dict|None: dict representing the original state, None means the component will be created
|
|
51
|
+ # @param new_state dict|None: dict representing the new state, None means the component will be deleted
|
54
|
52
|
def register_change(self, model, uid, initial_state, new_state):
|
55
|
|
- if initial_state is None:
|
56
|
|
- state_change = 'new'
|
57
|
|
- elif new_state is None:
|
58
|
|
- state_change = 'del'
|
|
53
|
+ if initial_state is not None and new_state is not None:
|
|
54
|
+ if initial_state is None:
|
|
55
|
+ state_change = 'new'
|
|
56
|
+ elif new_state is None:
|
|
57
|
+ state_change = 'del'
|
|
58
|
+ else:
|
|
59
|
+ state_change = 'upgrade'
|
|
60
|
+
|
|
61
|
+ component_class_name = model.classes(uid).__class__.name
|
|
62
|
+ handler_func(component_class_name.lower() + '_' + state_change)
|
|
63
|
+ if hasattr(self, handler_func):
|
|
64
|
+ getattr(self, handler_func)(model, uid, initial_state, new_state)
|
59
|
65
|
else:
|
60
|
|
- state_change = 'upgrade'
|
61
|
|
-
|
62
|
|
- component_class_name = model.classes(uid).__class__.name
|
63
|
|
- handler_func(component_class_name.lower() + '_' + state_change)
|
64
|
|
- if hasattr(self, handler_func):
|
65
|
|
- getattr(self, handler_func)(model, uid, initial_state, new_state)
|
|
66
|
+ pass # TODO manage the case where no state at all was given
|
66
|
67
|
|
67
|
68
|
def register_model_state(self, em, state_hash):
|
68
|
69
|
pass
|
69
|
70
|
|
|
71
|
+ ## @brief creates a new collection corresponding to a given uid
|
70
|
72
|
def emclass_new(self, model, uid, initial_state, new_state):
|
71
|
|
- class_collection_name = model.classes(uid).name
|
72
|
|
- self._create_collection(class_collection_name)
|
|
73
|
+ emclass = model.classes(uid)
|
|
74
|
+ if not isinstance(emclass, EmClass):
|
|
75
|
+ raise ValueError("The given uid is not an EmClass uid")
|
|
76
|
+ collection_name = utils.object_collection_name(emclass)
|
|
77
|
+ self._create_collection(collection_name)
|
73
|
78
|
|
|
79
|
+ ## @brief deletes a collection corresponding to a given uid
|
74
|
80
|
def emclass_del(self, model, uid, initial_state, new_state):
|
75
|
81
|
emclass = model.classes(uid)
|
76
|
82
|
if not isinstance(emclass, EmClass):
|
77
|
83
|
raise ValueError("The given uid is not an EmClass uid")
|
|
84
|
+ collection_name = utils.object.collection_name(emclass)
|
|
85
|
+ self._delete_collection(collection_name)
|
78
|
86
|
|
79
|
|
- class_collection_name = utils.object.collection_name(emclass.uid)
|
80
|
|
- self._delete_collection(class_collection_name)
|
81
|
|
-
|
82
|
|
- ## @brief creates a new collection for a new class
|
|
87
|
+ ## @brief creates a new field in a collection
|
83
|
88
|
# @param model EditorialModel
|
84
|
89
|
# @param uid str
|
85
|
|
- # @param initial_state dict|None: dict with field name as key and field value as value. Represents the original state.
|
86
|
|
- # @param new_state dict|None : dict with field name as key and field value as value. Represents the new state.
|
|
90
|
+ # @param initial_state dict|None: dict representing the original state
|
|
91
|
+ # @param new_state dict|None: dict representing the new state
|
87
|
92
|
def emfield_new(self, model, uid, initial_state, new_state):
|
88
|
93
|
if new_state['data_handler'] == 'relation':
|
89
|
94
|
# find relational_type name, and class_name of the field
|
90
|
95
|
class_name = self._class_collection_name_from_field(model, new_state)
|
91
|
96
|
self._create_field_in_collection(class_name, uid, new_state)
|
92
|
|
- return True
|
93
|
|
-
|
94
|
|
- collection_name = self._class_collection_name_from_field(model, new_state)
|
95
|
|
-
|
96
|
|
- field_definition = self._field_definition(new_state['data_handler'], new_state)
|
97
|
|
- self._create_field_in_collection(collection_name, uid, field_definition)
|
|
97
|
+ else:
|
|
98
|
+ collection_name = self._class_collection_name_from_field(model, new_state)
|
|
99
|
+ field_definition = self._field_defition(new_state['data_handler'], new_state)
|
|
100
|
+ self._create_field_in_collection(collection_name, uid, field_definition)
|
98
|
101
|
|
|
102
|
+ ## @brief deletes a field in a collection
|
|
103
|
+ # @param model EditorialModel
|
|
104
|
+ # @param uid str
|
|
105
|
+ # @param initial_state dict|None: dict representing the original state
|
|
106
|
+ # @param new_state dict|None: dict representing the new state
|
99
|
107
|
def emfield_del(self, model, uid, initial_state, new_state):
|
100
|
|
- if uid != '_id':
|
101
|
|
- collection_name = self._class_collection_name_from_field(model, initial_state)
|
102
|
|
- self.database[collection_name].update_many({field:{'$exists':True}}, {'$unset':{field:1}}, False)
|
103
|
|
-
|
|
108
|
+ collection_name = self._class_collection_name_from_field(model, initial_state)
|
|
109
|
+ field_name = model.field(uid).name
|
|
110
|
+ self._delete_field_in_collection(collection_name, field_name)
|
|
111
|
+
|
|
112
|
+ ## @brief Defines the default value when a new field is added to a collection's items
|
|
113
|
+ # @param fieldtype str : name of the field's type
|
|
114
|
+ # @param options dict : dictionary giving the options to use to initiate the field's value.
|
|
115
|
+ # @return dict (containing a 'default' key with the default value)
|
104
|
116
|
def _field_definition(self, fieldtype, options):
|
105
|
117
|
basic_type = DataHandler.from_name(fieldtype).ftype
|
106
|
|
-
|
107
|
|
- field_definition = {'default_value':None}
|
108
|
|
-
|
109
|
118
|
if basic_type == 'datetime':
|
110
|
119
|
if 'now_on_create' in options and options['now_on_create']:
|
111
|
120
|
return {'default': datetime.datetime.utcnow()}
|
112
|
121
|
if basic_type == 'relation':
|
113
|
|
- return {'default' : []}
|
|
122
|
+ return {'default': []}
|
114
|
123
|
|
115
|
124
|
return {'default': ''}
|
116
|
125
|
|
117
|
126
|
def _class_collection_name_from_field(self, model, field):
|
118
|
127
|
class_id = field['class_id']
|
119
|
|
- class_name = model.classes(class_id).name
|
120
|
|
- class_collection_name = utils.object_collection_name(class_name)
|
121
|
|
- return class_collection_name
|
122
|
|
-
|
|
128
|
+ component_class = model.classes(class_id)
|
|
129
|
+ component_collection = utils.object_collection_name(component_class)
|
|
130
|
+ return component_collection
|
|
131
|
+
|
|
132
|
+ ## @brief Creates a new collection in MongoDb Database
|
|
133
|
+ # @param collection_name str
|
|
134
|
+ # @param charset str
|
|
135
|
+ # @param if_exists str : defines the behavior when the collection already exists (default : 'nothing')
|
123
|
136
|
def _create_collection(self, collection_name, charset='utf8', if_exists=MongoDbMigrationHandler.COMMANDS_IFEXISTS_NOTHING):
|
124
|
|
- if if_exists == self.__class__.COMMANDS_IFEXISTS_DROP:
|
125
|
|
- if collection_name in self.database.collection_names(include_system_collections = False):
|
|
137
|
+ if collection_name in self.database.collection_names(include_system_collections=False):
|
|
138
|
+ # The collection already exists
|
|
139
|
+ if if_exists == MongoDbMigrationHandler.COMMANDS_IFEXISTS_DROP:
|
126
|
140
|
self._delete_collection(collection_name)
|
127
|
|
- self.database.create_collection(name=collection_name)
|
|
141
|
+ self.database.create_collection(name=collection_name)
|
|
142
|
+ else:
|
|
143
|
+ self.database.create_collection(name=collection_name)
|
128
|
144
|
|
|
145
|
+ ## @brief Delete an existing collection in MongoDb Database
|
|
146
|
+ # @param collection_name str
|
129
|
147
|
def _delete_collection(self, collection_name):
|
130
|
148
|
collection = self.database[collection_name]
|
131
|
149
|
collection.drop_indexes()
|
132
|
150
|
collection.drop()
|
133
|
151
|
|
|
152
|
+ ## @brief Creates a new field in a collection
|
|
153
|
+ # @param collection_name str
|
|
154
|
+ # @param field str
|
|
155
|
+ # @param options dict
|
134
|
156
|
def _create_field_in_collection(self, collection_name, field, options):
|
135
|
|
- self.database[collection_name].update_many({field: {'$exists': False}}, {'$set': {field: options['default']}},
|
136
|
|
- False)
|
137
|
|
-
|
138
|
|
- def _add_fk(self, src_collection_name, dst_collection_name, src_field_name, dst_field_name, fk_name=None):
|
139
|
|
- if fk_name is None:
|
140
|
|
- fk_name = utils.get_fk_name(src_collection_name, dst_collection_name)
|
141
|
|
- self._del_fk(src_collection_name, dst_collection_name, fk_name)
|
142
|
|
-
|
143
|
|
- self.database[src_collection_name].update_many({fk_name: {'$exists': False}}, {'$set': {fk_name: []}}, False)
|
144
|
|
-
|
145
|
|
- def del_fk(self, src_collection_name, dst_collection_name, fk_name=None):
|
146
|
|
- if fk_name is None:
|
147
|
|
- fk_name = utils.get_fk_name(src_collection_name, dst_collection_name)
|
148
|
|
- self.database[src_collection_name].update_many({}, {'$unset': {fk_name:1}}, False)
|
|
157
|
+ self.database[collection_name].update_many({field: {'$exists': False}}, {'$set': {field: options['default']}}, False)
|
|
158
|
+
|
|
159
|
+ ## @brief Deletes a field in a collection
|
|
160
|
+ # @param collection_name str
|
|
161
|
+ # @param field_name str
|
|
162
|
+ def _delete_field_in_collection(self, collection_name, field_name):
|
|
163
|
+ if field_name != '_id':
|
|
164
|
+ self.database[collection_name].update_many({field_name:{'$exists': True}}, {'$unset':{field_name:1}}, False)
|