Browse Source

Added more methods to the mongodb migration handler

Roland Haroutiounian 8 years ago
parent
commit
a05d4cfbec
2 changed files with 152 additions and 4 deletions
  1. 142
    3
      lodel/datasource/mongodb/migration_handler.py
  2. 10
    1
      lodel/datasource/mongodb/utils.py

+ 142
- 3
lodel/datasource/mongodb/migration_handler.py View File

@@ -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)

+ 10
- 1
lodel/datasource/mongodb/utils.py View File

@@ -4,7 +4,8 @@ import pymongo
4 4
 from pymongo import MongoClient
5 5
 
6 6
 common_collections = {
7
-    'object': 'object'
7
+    'object': 'object',
8
+    'relation': 'relation'
8 9
 }
9 10
 
10 11
 collection_prefix = {
@@ -176,3 +177,11 @@ def escape_idname(idname):
176 177
     if '`' in idname:
177 178
         raise ValueError("Invalid name : '%s'" % idname)
178 179
     return '`%s`' % idname
180
+
181
+
182
+## @brief gets the fk name between two collections
183
+# @param src_collection_name str
184
+# @param dst_collection_name str
185
+# @return str
186
+def get_fk_name(src_collection_name, dst_collection_name):
187
+    return ("fk_%s_%s" % (src_collection_name, dst_collection_name)).lower()

Loading…
Cancel
Save