Browse Source

Rewrote some code from the migration handler

Roland Haroutiounian 8 years ago
parent
commit
888c671659
2 changed files with 109 additions and 90 deletions
  1. 91
    75
      lodel/datasource/mongodb/migration_handler.py
  2. 18
    15
      lodel/datasource/mongodb/utils.py

+ 91
- 75
lodel/datasource/mongodb/migration_handler.py View File

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

+ 18
- 15
lodel/datasource/mongodb/utils.py View File

@@ -3,6 +3,8 @@
3 3
 import pymongo
4 4
 from pymongo import MongoClient
5 5
 
6
+from lodel.settings.settings import Settings as settings
7
+
6 8
 common_collections = {
7 9
     'object': 'object',
8 10
     'relation': 'relation'
@@ -10,7 +12,7 @@ common_collections = {
10 12
 
11 13
 collection_prefix = {
12 14
     'relation': 'rel_',
13
-    'collection': 'class_'
15
+    'object': 'class_'
14 16
 }
15 17
 
16 18
 LODEL_OPERATORS_MAP = {
@@ -67,8 +69,8 @@ def mongodbconnect(connection_name):
67 69
 ## @brief gets the settings given a connection name
68 70
 # @param connection_name str
69 71
 # @return dict
70
-# @todo connect this method to the settings
71
-def get_connection_args(connnection_name):
72
+# @todo Use the settings module to store the connections parameters
73
+def get_connection_args(connnection_name='default'):
72 74
     return {'host': 'localhost', 'port': 28015, 'login': 'lodel_admin', 'password': 'lapwd', 'dbname': 'lodel'}
73 75
 
74 76
 
@@ -85,11 +87,17 @@ def check_connection_args(connection_args):
85 87
     return check_result
86 88
 
87 89
 
88
-## @brief Returns a collection name given a Emclass name
89
-# @param class_name str : The class name
90
+## @brief Returns a collection name given a Emclass
91
+# @param class_object EmClass
90 92
 # @return str
91
-def object_collection_name(class_name):
92
-    return ("%s%s" % (collection_prefix['object'], class_name)).lower()
93
+def object_collection_name(class_object):
94
+    if class_object.pure_abstract == False:
95
+        class_parent = class_object.parents[0].uid
96
+        collection_name = ("%s%s" % (collection_prefix['object'], class_parent)).lower()
97
+    else:
98
+        collection_name = ("%s%s" % (collection_prefix['object'], class_object.name)).lower()
99
+
100
+    return collection_name
93 101
 
94 102
 
95 103
 ## @brief converts the query filters into MongoDB filters
@@ -122,10 +130,12 @@ def parse_query_filters(query_filters, as_list=False):
122 130
 # @param filter_params tuple : (FIELD, OPERATOR, VALUE) representing the query filter to convert
123 131
 # @return dict : {KEY: {OPERATOR:VALUE}}
124 132
 # @todo Add an error management for the operator mismatch
125
-# @todo Add the checks for the type of values authorized in certain mongodb operators, such "$in" for example which takes a list
126 133
 def convert_filter(filter_params):
127 134
     key, operator, value = filter_params
128 135
     if operator not in ('like', 'not like'):
136
+        if operator == 'in' and not isinstance(value, list):
137
+            raise ValueError('A list should be used as value for an IN operator, %s given' % value.__class__)
138
+
129 139
         converted_operator = LODEL_OPERATORS_MAP[operator]['name']
130 140
         converted_filter = {key: {converted_operator: value}}
131 141
     else:
@@ -178,10 +188,3 @@ def escape_idname(idname):
178 188
         raise ValueError("Invalid name : '%s'" % idname)
179 189
     return '`%s`' % idname
180 190
 
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