|
@@ -1,14 +1,153 @@
|
1
|
1
|
# -*- coding: utf-8 -*-
|
2
|
2
|
|
|
3
|
+import datetime
|
|
4
|
+
|
|
5
|
+from lodel.leapi.datahandlers.base_classes import DataHandler
|
3
|
6
|
from lodel.datasource.generic.migrationhandler import GenericMigrationHandler
|
4
|
|
-from lodel.datasource.mongodb.datasource import MongoDbDataSource
|
5
|
7
|
import lodel.datasource.mongodb.utils as utils
|
6
|
8
|
from lodel.editorial_model.components import EmClass, EmField
|
7
|
|
-
|
|
9
|
+from lodel.editorial_model.model import EditorialModel
|
8
|
10
|
|
9
|
11
|
class MigrationHandlerChangeError(Exception):
|
10
|
12
|
pass
|
11
|
13
|
|
12
|
14
|
|
13
|
15
|
class MongoDbMigrationHandler(GenericMigrationHandler):
|
14
|
|
- pass
|
|
16
|
+
|
|
17
|
+ COMMANDS_IFEXISTS_DROP = 'drop'
|
|
18
|
+ COMMANDS_IFEXISTS_NOTHING = 'nothing'
|
|
19
|
+
|
|
20
|
+ ## @brief constructs a MongoDbMigrationHandler
|
|
21
|
+ # @param conn_args dict : a dictionary containing connection options
|
|
22
|
+ # @param **kwargs : extra arguments given to the connection method
|
|
23
|
+ def __init__(self, conn_args=None, **kwargs):
|
|
24
|
+
|
|
25
|
+ if conn_args is None:
|
|
26
|
+ conn_args = {} # TODO : get the connection parameters in the settings
|
|
27
|
+
|
|
28
|
+ self.connection_name = conn_args['name']
|
|
29
|
+
|
|
30
|
+ self.database = utils.mongodbconnect(self.connection_name)
|
|
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']
|
|
38
|
+
|
|
39
|
+ self._main_collection_name = 'object'
|
|
40
|
+ self._relation_collection_name = 'relations'
|
|
41
|
+
|
|
42
|
+ self._install_tables()
|
|
43
|
+
|
|
44
|
+ def _install_tables(self):
|
|
45
|
+ self._create_collection(self._main_collection_name)
|
|
46
|
+ self._create_collection(self._relation_collection_name)
|
|
47
|
+
|
|
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
|
|
51
|
+ # @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
|
|
54
|
+ 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'
|
|
59
|
+ 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
|
+
|
|
67
|
+ def register_model_state(self, em, state_hash):
|
|
68
|
+ pass
|
|
69
|
+
|
|
70
|
+ 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
|
+
|
|
74
|
+ def emclass_del(self, model, uid, initial_state, new_state):
|
|
75
|
+ emclass = model.classes(uid)
|
|
76
|
+ if not isinstance(emclass, EmClass):
|
|
77
|
+ raise ValueError("The given uid is not an EmClass uid")
|
|
78
|
+
|
|
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
|
|
83
|
+ # @param model EditorialModel
|
|
84
|
+ # @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.
|
|
87
|
+ def emfield_new(self, model, uid, initial_state, new_state):
|
|
88
|
+ if new_state['data_handler'] == 'relation':
|
|
89
|
+ # find relational_type name, and class_name of the field
|
|
90
|
+ class_name = self._class_collection_name_from_field(model, new_state)
|
|
91
|
+ self._create_field_in_collection(class_name, uid, new_state)
|
|
92
|
+ return True
|
|
93
|
+
|
|
94
|
+ if new_state['internal']:
|
|
95
|
+ collection_name = ''
|
|
96
|
+ # TODO ?
|
|
97
|
+ elif new_state['rel_field_id']:
|
|
98
|
+ class_name = self._class_collection_name_from_field(model, new_state)
|
|
99
|
+ # TODO deal this case
|
|
100
|
+ else:
|
|
101
|
+ collection_name = self._class_collection_name_from_field(model, new_state)
|
|
102
|
+
|
|
103
|
+ field_definition = self._field_definition(new_state['data_handler'], new_state)
|
|
104
|
+ self._create_field_in_collection(collection_name, uid, field_definition)
|
|
105
|
+
|
|
106
|
+ def emfield_del(self, model, uid, initial_state, new_state):
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+ def _field_definition(self, fieldtype, options):
|
|
110
|
+ basic_type = DataHandler.from_name(fieldtype).ftype
|
|
111
|
+
|
|
112
|
+ field_definition = {'default_value':None}
|
|
113
|
+
|
|
114
|
+ if basic_type == 'datetime':
|
|
115
|
+ if 'now_on_create' in options and options['now_on_create']:
|
|
116
|
+ return {'default': datetime.datetime.utcnow()} # TODO format the datetime to a MongoDB friendly format
|
|
117
|
+ if basic_type == 'relation':
|
|
118
|
+ return {'default' : []}
|
|
119
|
+
|
|
120
|
+ return {'default': ''}
|
|
121
|
+
|
|
122
|
+ def _class_collection_name_from_field(self, model, field):
|
|
123
|
+ class_id = field['class_id']
|
|
124
|
+ class_name = model.classes(class_id).name
|
|
125
|
+ class_collection_name = utils.object_collection_name(class_name)
|
|
126
|
+ return class_collection_name
|
|
127
|
+
|
|
128
|
+ def _create_collection(self, collection_name, charset='utf8', if_exists=self.__class__.COMMANDS_IFEXISTS_NOTHING):
|
|
129
|
+ if if_exists == self.__class__.COMMANDS_IFEXISTS_DROP:
|
|
130
|
+ if collection_name in self.database.collection_names(include_system_collections = False):
|
|
131
|
+ self._delete_collection(collection_name)
|
|
132
|
+ self.database.create_collection(name=collection_name)
|
|
133
|
+
|
|
134
|
+ def _delete_collection(self, collection_name):
|
|
135
|
+ collection = self.database[collection_name]
|
|
136
|
+ collection.drop_indexes()
|
|
137
|
+ collection.drop()
|
|
138
|
+
|
|
139
|
+ def _create_field_in_collection(self, collection_name, field, options):
|
|
140
|
+ self.database[collection_name].update_many({field: {'$exists': False}}, {'$set': {field: options['default']}},
|
|
141
|
+ False)
|
|
142
|
+
|
|
143
|
+ def _add_fk(self, src_collection_name, dst_collection_name, src_field_name, dst_field_name, fk_name=None):
|
|
144
|
+ if fk_name is None:
|
|
145
|
+ fk_name = utils.get_fk_name(src_collection_name, dst_collection_name)
|
|
146
|
+ self._del_fk(src_collection_name, dst_collection_name, fk_name)
|
|
147
|
+
|
|
148
|
+ self.database[src_collection_name].update_many({fk_name: {'$exists': False}}, {'$set': {fk_name: []}}, False)
|
|
149
|
+
|
|
150
|
+ def del_fk(self, src_collection_name, dst_collection_name, fk_name=None):
|
|
151
|
+ if fk_name is None:
|
|
152
|
+ fk_name = utils.get_fk_name(src_collection_name, dst_collection_name)
|
|
153
|
+ self.database[src_collection_name].update_many({}, {'$unset': {fk_name:1}}, False)
|