Browse Source

Merge branch 'component_db_instance_attr'

Conflicts:
	EditorialModel/classes.py
	EditorialModel/types.py
Yann Weber 9 years ago
parent
commit
9cec5af1fa

+ 7
- 11
Database/sqlutils.py View File

@@ -5,6 +5,7 @@ import logging as logger
5 5
 import sqlalchemy as sqla
6 6
 from django.conf import settings
7 7
 
8
+import EditorialModel
8 9
 
9 10
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Lodel.settings")
10 11
 
@@ -76,17 +77,12 @@ def meta(engine):
76 77
 # @param cls : An EmComponent child class
77 78
 # @return An sqlalchemy table
78 79
 # @throw TypeError if em_instance is an EmComponent  or not an EmComponent child class (or an instance)
79
-def get_table(cls):
80
-    from EditorialModel.components import EmComponent  # dirty circula inclusion hack
81
-    if not issubclass(cls, EmComponent) or cls.table is None:
82
-        raise TypeError("Excepting an EmComponent child class not an " + str(cls))
83
-    engine = cls.db_engine()
84
-    return sqla.Table(cls.table, meta(engine))
85
-
86
-
87
-def getTable(cls):
88
-    return get_table(cls)
89
-
80
+# @todo Move this function as an instance method ?
81
+def get_table(self):
82
+    if not issubclass(self.__class__, EditorialModel.components.EmComponent) or self.table is None:
83
+        raise TypeError("Excepting an EmComponent child class not an " + str(self.__class__))
84
+    engine = self.db_engine
85
+    return sqla.Table(self.table, meta(engine))
90 86
 
91 87
 ## This function is intended to execute ddl defined in sqlalter
92 88
 # @warning There is a dirty workaround here, DDL should returns only one query, but DropColumn for sqlite has to return 4 queries (rename, create, insert, drop). There is a split on the compiled SQL to extract and execute one query at a time

+ 6
- 6
EditorialModel/classes.py View File

@@ -44,7 +44,7 @@ class EmClass(EmComponent):
44 44
         #Create a new entry in the em_class table
45 45
         result = super(EmClass, cls).create(name=name, classtype=classtype, icon=icon, sortcolumn=sortcolumn, **em_component_args)
46 46
 
47
-        dbe = result.db_engine()
47
+        dbe = result.db_engine
48 48
         conn = dbe.connect()
49 49
 
50 50
         #Create a new table storing LodelObjects of this EmClass
@@ -69,7 +69,7 @@ class EmClass(EmComponent):
69 69
         if len(fieldgroups) > 0:
70 70
             return False
71 71
 
72
-        dbe = self.__class__.db_engine()
72
+        dbe = self.db_engine
73 73
         meta = sqlutils.meta(dbe)
74 74
         #Here we have to give a connection
75 75
         class_table = sql.Table(self.name, meta)
@@ -88,7 +88,7 @@ class EmClass(EmComponent):
88 88
     ## Isolate SQL for EmClass::fieldgroups
89 89
     # @return An array of dict (sqlalchemy fetchall)
90 90
     def _fieldgroups_db(self):
91
-        dbe = self.__class__.db_engine()
91
+        dbe = self.db_engine
92 92
         emfg = sql.Table(EditorialModel.fieldgroups.EmFieldGroup.table, sqlutils.meta(dbe))
93 93
         req = emfg.select().where(emfg.c.class_id == self.uid)
94 94
 
@@ -116,7 +116,7 @@ class EmClass(EmComponent):
116 116
     ## Isolate SQL for EmCLass::types
117 117
     # @return An array of dict (sqlalchemy fetchall)
118 118
     def _types_db(self):
119
-        dbe = self.__class__.db_engine()
119
+        dbe = self.db_engine
120 120
         emtype = sql.Table(EditorialModel.types.EmType.table, sqlutils.meta(dbe))
121 121
         req = emtype.select().where(emtype.c.class_id == self.uid)
122 122
         conn = dbe.connect()
@@ -134,7 +134,7 @@ class EmClass(EmComponent):
134 134
 
135 135
     def _link_type_db(self, table_name):
136 136
         #  Create a new table storing additionnal fields for the relation between the linked type and this EmClass
137
-        conn = self.__class__.db_engine().connect()
137
+        conn = self.db_engine.connect()
138 138
         meta = sql.MetaData()
139 139
         emlinketable = sql.Table(table_name, meta, sql.Column('uid', sql.VARCHAR(50), primary_key=True))
140 140
         emlinketable.create(conn)
@@ -146,7 +146,7 @@ class EmClass(EmComponent):
146 146
         return self._linked_types_db()
147 147
 
148 148
     def _linked_types_db(self):
149
-        dbe = self.__class__.db_engine()
149
+        dbe = self.db_engine
150 150
         meta = sql.MetaData()
151 151
         meta.reflect(dbe)
152 152
 

+ 38
- 21
EditorialModel/components.py View File

@@ -53,7 +53,14 @@ class EmComponent(object):
53 53
     # @param id_or_name int|str: name or id of the object
54 54
     # @throw TypeError if id_or_name is not an integer nor a string
55 55
     # @throw NotImplementedError if called with EmComponent
56
-    def __init__(self, id_or_name):
56
+    def __init__(self, id_or_name, dbconf = 'default'):
57
+        
58
+        self.dbconf = dbconf
59
+        if self.dbconf:
60
+            self.db_engine = sqlutils.get_engine(dbconf)
61
+        else:
62
+            self.db_engine = False
63
+
57 64
         if type(self) == EmComponent:
58 65
             raise NotImplementedError('Abstract class')
59 66
 
@@ -129,15 +136,15 @@ class EmComponent(object):
129 136
 
130 137
         super(EmComponent, self).__setattr__('deleted', False)
131 138
 
132
-    @classmethod
139
+    #@classmethod
133 140
     ## Shortcut that return the sqlAlchemy engine
134
-    def db_engine(cls):
135
-        return sqlutils.get_engine(cls.dbconf)
141
+    #def db_engine(cls):
142
+    #    return sqlutils.get_engine(cls.dbconf)
136 143
 
137 144
     ## Do the query on the database for EmComponent::populate()
138 145
     # @throw EmComponentNotExistError if the instance is not anymore stored in database
139 146
     def _populate_db(self):
140
-        dbe = self.__class__.db_engine()
147
+        dbe = self.db_engine
141 148
         component = sql.Table(self.table, sqlutils.meta(dbe))
142 149
         req = sql.sql.select([component])
143 150
 
@@ -159,7 +166,7 @@ class EmComponent(object):
159 166
     ## Insert a new component in the database
160 167
     #
161 168
     # This function create and assign a new UID and handle the date_create and date_update values
162
-    #
169
+    # @warning There is a mandatory argument dbconf that indicate wich database configuration to use
163 170
     # @param **kwargs : Names arguments representing object properties
164 171
     # @return An instance of the created component
165 172
     # @throw TypeError if an element of kwargs isn't a valid object propertie or if a mandatory argument is missing
@@ -194,21 +201,27 @@ class EmComponent(object):
194 201
         #    if cls._fields[name].notNull and cls._fields[name].default == None:
195 202
         #        raise TypeError("Missing argument : "+name)
196 203
 
204
+        if 'dbconf' in kwargs:
205
+            if not kwargs['db_engine']:
206
+                raise NotImplementedError("Its a nonsense to call create with no database")
207
+            dbconf = kwargs['dbconf']
208
+        else:
209
+            dbconf = 'default'
210
+        dbe = sqlutils.get_engine(dbconf)
197 211
 
198
-        kwargs['uid'] = cls.new_uid()
212
+        kwargs['uid'] = cls.new_uid(dbe)
199 213
         kwargs['date_update'] = kwargs['date_create'] = datetime.datetime.utcnow()
200 214
 
201
-        dbe = cls.db_engine()
202 215
         conn = dbe.connect()
203 216
 
204
-        kwargs['rank'] = cls.get_max_rank( kwargs[cls.ranked_in] )+1
217
+        kwargs['rank'] = cls._get_max_rank( kwargs[cls.ranked_in], dbe )+1
205 218
 
206 219
         table = sql.Table(cls.table, sqlutils.meta(dbe))
207 220
         req = table.insert(kwargs)
208 221
         if not conn.execute(req):
209 222
             raise RuntimeError("Unable to create the "+cls.__class__.__name__+" EmComponent ")
210 223
         conn.close()
211
-        return cls(kwargs['name'])
224
+        return cls(kwargs['name'], dbconf)
212 225
 
213 226
     ## Write the representation of the component in the database
214 227
     # @return bool
@@ -232,7 +245,7 @@ class EmComponent(object):
232 245
     # @throw RunTimeError if it was unable to do the Db update
233 246
     def _save_db(self, values):
234 247
         """ Do the query on the db """
235
-        dbe = self.__class__.db_engine()
248
+        dbe = self.db_engine
236 249
         component = sql.Table(self.table, sqlutils.meta(dbe))
237 250
         req = sql.update(component, values=values).where(component.c.uid == self.uid)
238 251
 
@@ -247,7 +260,7 @@ class EmComponent(object):
247 260
     # @throw RunTimeError if it was unable to do the deletion
248 261
     def delete(self):
249 262
         #<SQL>
250
-        dbe = self.__class__.db_engine()
263
+        dbe = self.db_engine
251 264
         component = sql.Table(self.table, sqlutils.meta(dbe))
252 265
         req = component.delete().where(component.c.uid == self.uid)
253 266
         conn = dbe.connect()
@@ -260,13 +273,11 @@ class EmComponent(object):
260 273
         super(EmComponent, self).__setattr__('deleted', True)
261 274
         return True
262 275
 
263
-    ## get_max_rank
264
-    # Retourne le rank le plus élevé pour le groupe de component au quel apartient l'objet actuelle
276
+    ## @brief Get the maximum rank given an EmComponent child class and a ranked_in filter
265 277
     # @param ranked_in_value mixed: The rank "family"
266
-    # @param return -1 if no EmComponent found else return an integer >= 0
278
+    # @return -1 if no EmComponent found else return an integer >= 0
267 279
     @classmethod
268
-    def get_max_rank(cls, ranked_in_value):
269
-        dbe = cls.db_engine()
280
+    def _get_max_rank(cls, ranked_in_value, dbe):
270 281
         component = sql.Table(cls.table, sqlutils.meta(dbe))
271 282
         req = sql.sql.select([component.c.rank]).where(getattr(component.c, cls.ranked_in) == ranked_in_value).order_by(component.c.rank.desc())
272 283
         c = dbe.connect()
@@ -278,6 +289,12 @@ class EmComponent(object):
278 289
         else:
279 290
             return -1
280 291
 
292
+    ## Only make a call to the class method
293
+    # @return A positive integer or -1 if no components
294
+    # @see EmComponent::_get_max_rank()
295
+    def get_max_rank(self, ranked_in_value):
296
+        return self.__class__._get_max_rank(ranked_in_value, self.db_engine)
297
+
281 298
     ## Set a new rank for this component
282 299
     # @note This function assume that ranks are properly set from 1 to x with no gap
283 300
     # @param new_rank int: The new rank
@@ -298,9 +315,9 @@ class EmComponent(object):
298 315
         limits = [ self.rank + ( 1 if mod > 0 else -1), new_rank ] #The range of modified ranks
299 316
         limits.sort()
300 317
 
301
-        dbe = self.db_engine()
318
+        dbe = self.db_engine
302 319
         conn = dbe.connect()
303
-        table = sqlutils.get_table(self.__class__)
320
+        table = sqlutils.get_table(self)
304 321
 
305 322
         #Selecting the components that will be modified
306 323
         req = table.select().where( getattr(table.c, self.ranked_in) == getattr(self, self.ranked_in)).where(table.c.rank >= limits[0]).where(table.c.rank <= limits[1])
@@ -358,11 +375,11 @@ class EmComponent(object):
358 375
     #
359 376
     # Use the class property table
360 377
     # @return A new uid (an integer)
361
-    def new_uid(cls):
378
+    def new_uid(cls, db_engine):
362 379
         if cls.table is None:
363 380
             raise NotImplementedError("Abstract method")
364 381
 
365
-        dbe = cls.db_engine()
382
+        dbe = db_engine
366 383
 
367 384
         uidtable = sql.Table('uids', sqlutils.meta(dbe))
368 385
         conn = dbe.connect()

+ 4
- 2
EditorialModel/fieldgroups.py View File

@@ -5,6 +5,7 @@ from EditorialModel.classes import EmClass
5 5
 import EditorialModel.fieldtypes as ftypes
6 6
 
7 7
 from Database import sqlutils
8
+import sqlalchemy as sql
8 9
 
9 10
 import EditorialModel
10 11
 
@@ -43,9 +44,10 @@ class EmFieldGroup(EmComponent):
43 44
     ## Get the list of associated fields
44 45
     # @return A list of EmField instance
45 46
     def fields(self):
46
-        field_table = sqlutils.getTable(EditorialModel.fields.EmField)
47
+        meta = sqlutils.meta(self.db_engine)
48
+        field_table = sql.Table(EditorialModel.fields.EmField.table, meta)
47 49
         req = field_table.select(field_table.c.uid).where(field_table.c.fieldgroup_id == self.uid)
48
-        conn = self.__class__.db_engine().connect()
50
+        conn = self.db_engine.connect()
49 51
         res = conn.execute(req)
50 52
         rows = res.fetchall()
51 53
         conn.close()

+ 6
- 5
EditorialModel/fields.py View File

@@ -71,11 +71,11 @@ class EmField(EmComponent):
71 71
     # @return bool : True if deleted False if deletion aborded
72 72
     # @todo Check if unconditionnal deletion is correct
73 73
     def delete(self):
74
-        dbe = self.__class__.db_engine()
74
+        dbe = self.db_engine
75 75
         class_table = sql.Table(self.get_class_table(), sqlutils.meta(dbe))
76 76
         field_col = sql.Column(self.name)
77 77
         ddl = DropColumn(class_table, field_col)
78
-        sqlutils.ddl_execute(ddl, self.__class__.db_engine())
78
+        sqlutils.ddl_execute(ddl, self.db_engine)
79 79
         return super(EmField, self).delete()
80 80
 
81 81
     ## add_field_column_to_class_table (Function)
@@ -84,7 +84,7 @@ class EmField(EmComponent):
84 84
     #
85 85
     # @return True in case of success, False if not
86 86
     def add_field_column_to_class_table(self):
87
-        dbe = self.db_engine()
87
+        dbe = self.db_engine
88 88
         fieldtype = get_field_type(self.fieldtype)
89 89
         new_column = sql.Column(name=self.name, **(fieldtype.sqlalchemy_args()))
90 90
         class_table = sql.Table(self.get_class_table(), sqlutils.meta(dbe))
@@ -103,9 +103,10 @@ class EmField(EmComponent):
103 103
     # @return An EmClass instance
104 104
     def get_class(self):
105 105
         #<SQL>
106
-        dbe = self.db_engine()
106
+        dbe = self.db_engine
107
+        meta = sqlutils.meta(dbe)
107 108
         conn = dbe.connect()
108
-        fieldgroup_table = sqlutils.getTable(EmFieldGroup)
109
+        fieldgroup_table = sql.Table(EmFieldGroup.table, meta)
109 110
         req = fieldgroup_table.select().where(fieldgroup_table.c.uid == self.fieldgroup_id)
110 111
         res = conn.execute(req)
111 112
         row = res.fetchone()

+ 5
- 5
EditorialModel/test/test_component.py View File

@@ -240,7 +240,7 @@ class TestUid(ComponentTestCase):
240 240
     def test_newuid(self):
241 241
         """ Test valid calls for new_uid method """
242 242
         for _ in range(10):
243
-            nuid = EmTestComp.new_uid()
243
+            nuid = EmTestComp.new_uid(self.dber)
244 244
         
245 245
             conn = self.dber.connect()
246 246
             tuid = sqla.Table('uids', sqlutils.meta(self.dber))
@@ -257,7 +257,7 @@ class TestUid(ComponentTestCase):
257 257
     def test_newuid_abstract(self):
258 258
         """ Test not valit call for new_uid method """
259 259
         with self.assertRaises(NotImplementedError):
260
-            EmComponent.new_uid()
260
+            EmComponent.new_uid(self.dber)
261 261
         pass
262 262
         
263 263
 #=======================#
@@ -432,11 +432,11 @@ class TestCreate(ComponentTestCase):
432 432
         pass
433 433
 
434 434
     def testGetMaxRank(self):
435
-        old = EmTestComp.get_max_rank('f')
435
+        old = EmTestComp._get_max_rank('f', self.dber)
436 436
         EmTestComp.create(name="foobartest", rank_fam = 'f')
437
-        n = EmTestComp.get_max_rank('f')
437
+        n = EmTestComp._get_max_rank('f', self.dber)
438 438
         self.assertEqual(old+1, n, "Excepted value was "+str(old+1)+" but got "+str(n))
439
-        self.assertEqual(EmTestComp.get_max_rank('z'), -1)
439
+        self.assertEqual(EmTestComp._get_max_rank('z', self.dber), -1)
440 440
         pass
441 441
 
442 442
 #====================#

+ 3
- 2
EditorialModel/test/test_field.py View File

@@ -79,7 +79,7 @@ class FieldTestCase(TestCase):
79 79
     # @param field EmField: EmField object
80 80
     # @return Number of found records
81 81
     def _get_field_records_Db(self,field):
82
-        dbe = EmComponent.db_engine()
82
+        dbe = field.db_engine
83 83
         fieldtable = sqla.Table(EmField.table, sqlutils.meta(dbe))
84 84
         conn = dbe.connect()
85 85
         req = fieldtable.select().where(fieldtable.c.uid==field.uid).where(fieldtable.c.name==field.name)
@@ -103,7 +103,8 @@ class FieldTestCase(TestCase):
103 103
     # @param table_name str: Name of the table
104 104
     # @return list of columns
105 105
     def _get_table_columns_Db(self, table_name):
106
-        table = sqla.Table(table_name, sqlutils.meta(EmComponent.db_engine()))
106
+        dbe = self.testClass.db_engine
107
+        table = sqla.Table(table_name, sqlutils.meta(dbe))
107 108
         return table.c
108 109
 
109 110
 ## TestField (Class)

+ 4
- 3
EditorialModel/test/test_fieldgroups.py View File

@@ -67,6 +67,7 @@ class TestInit(FieldGroupTestCase):
67 67
 
68 68
     def setUp(self):
69 69
         super(TestInit, self).setUp()
70
+        dbe = sqlutils.get_engine()
70 71
         conn = sqlutils.get_engine().connect()
71 72
 
72 73
         ent1 = EmClass('entity1')
@@ -76,9 +77,9 @@ class TestInit(FieldGroupTestCase):
76 77
         self.creadate = datetime.datetime.utcnow()
77 78
         #Test fieldgroup
78 79
         self.tfg = [
79
-            { 'uid': EmFieldGroup.new_uid(), 'name': 'fg1', 'string': '{"fr":"Super Fieldgroup"}', 'help': '{"en":"help"}', 'rank': 0 , 'class_id': ent1.uid, 'date_create' : self.creadate, 'date_update': self.creadate},
80
-            { 'uid': EmFieldGroup.new_uid(), 'name': 'fg2', 'string': '{"fr":"Super Fieldgroup"}', 'help': '{"en":"help"}', 'rank': 1 , 'class_id': ent1.uid, 'date_create': self.creadate, 'date_update': self.creadate},
81
-            { 'uid': EmFieldGroup.new_uid(), 'name': 'fg3', 'string': '{"fr":"Super Fieldgroup"}', 'help': '{"en":"help"}', 'rank': 2 , 'class_id': idx1.uid, 'date_create': self.creadate, 'date_update': self.creadate},
80
+            { 'uid': EmFieldGroup.new_uid(dbe), 'name': 'fg1', 'string': '{"fr":"Super Fieldgroup"}', 'help': '{"en":"help"}', 'rank': 0 , 'class_id': ent1.uid, 'date_create' : self.creadate, 'date_update': self.creadate},
81
+            { 'uid': EmFieldGroup.new_uid(dbe), 'name': 'fg2', 'string': '{"fr":"Super Fieldgroup"}', 'help': '{"en":"help"}', 'rank': 1 , 'class_id': ent1.uid, 'date_create': self.creadate, 'date_update': self.creadate},
82
+            { 'uid': EmFieldGroup.new_uid(dbe), 'name': 'fg3', 'string': '{"fr":"Super Fieldgroup"}', 'help': '{"en":"help"}', 'rank': 2 , 'class_id': idx1.uid, 'date_create': self.creadate, 'date_update': self.creadate},
82 83
         ]
83 84
 
84 85
         req = sqla.Table('em_fieldgroup', sqlutils.meta(sqlutils.get_engine())).insert(self.tfg)

+ 11
- 9
EditorialModel/types.py View File

@@ -49,7 +49,7 @@ class EmType(EmComponent):
49 49
     # @return sqlalchemy em_type_hierarchy table object
50 50
     # @todo Don't hardcode table name
51 51
     def _table_hierarchy(self):
52
-        return sql.Table(self.__class__.table_hierarchy, sqlutils.meta(self.db_engine()))
52
+        return sql.Table(self.__class__.table_hierarchy, sqlutils.meta(self.db_engine))
53 53
 
54 54
     @property
55 55
     ## Return the EmClassType of the type
@@ -76,9 +76,10 @@ class EmType(EmComponent):
76 76
     ## Get the list of associated fieldgroups
77 77
     # @return A list of EmFieldGroup instance
78 78
     def field_groups(self):
79
-        fg_table = sqlutils.getTable(EmFieldGroup)
79
+        meta = sqlutils.meta(self.db_engine)
80
+        fg_table = sql.Table(EmFieldGroup.table, meta)
80 81
         req = fg_table.select(fg_table.c.uid).where(fg_table.c.class_id == self.class_id)
81
-        conn = self.__class__.db_engine().connect()
82
+        conn = self.db_engine.connect()
82 83
         res = conn.execute(req)
83 84
         rows = res.fetchall()
84 85
         conn.close()
@@ -96,7 +97,7 @@ class EmType(EmComponent):
96 97
     ## Return selected optional field
97 98
     # @return A list of EmField instance
98 99
     def selected_fields(self):
99
-        dbe = self.db_engine()
100
+        dbe = self.db_engine
100 101
         meta = sqlutils.meta(dbe)
101 102
         conn = dbe.connect()
102 103
 
@@ -155,7 +156,7 @@ class EmType(EmComponent):
155 156
         if not field.optional:
156 157
             raise ValueError("This field is not optional")
157 158
 
158
-        dbe = self.db_engine()
159
+        dbe = self.db_engine
159 160
         meta = sqlutils.meta(dbe)
160 161
         conn = dbe.connect()
161 162
 
@@ -216,8 +217,9 @@ class EmType(EmComponent):
216 217
     # @throw RunTimeError if a nature fetched from db is not valid
217 218
     # @see EmType::subordinates(), EmType::superiors()
218 219
     def _sub_or_sup(self, sup=True):
219
-        conn = self.db_engine().connect()
220
+        conn = self.db_engine.connect()
220 221
         htable = self._table_hierarchy
222
+        type_table = sqlutils.get_table(self)
221 223
 
222 224
         req = htable.select()
223 225
         if sup:
@@ -265,7 +267,7 @@ class EmType(EmComponent):
265 267
         elif self.name != em_type.name:
266 268
             raise ValueError("Not allowed to put a different em_type as superior in a relation of nature '" + relation_nature + "'")
267 269
 
268
-        conn = self.db_engine().connect()
270
+        conn = self.db_engine.connect()
269 271
         htable = self._table_hierarchy
270 272
         values = {'subordinate_id': self.uid, 'superior_id': em_type.uid, 'nature': relation_nature}
271 273
         req = htable.insert(values=values)
@@ -290,7 +292,7 @@ class EmType(EmComponent):
290 292
         if relation_nature not in EmClassType.natures(self.classtype['name']):
291 293
             raise ValueError("Invalid nature for add_superior : '" + relation_nature + "'. Allowed relations for this type are " + str(EmClassType.natures(self.classtype['name'])))
292 294
 
293
-        conn = self.db_engine().connect()
295
+        conn = self.db_engine.connect()
294 296
         htable = self._table_hierarchy
295 297
         req = htable.delete(htable.c.superior_id == em_type.uid and htable.c.nature == relation_nature)
296 298
         conn.execute(req)
@@ -306,7 +308,7 @@ class EmType(EmComponent):
306 308
     ## @brief Return the list of all the types linked to this type, should they be superiors or subordinates
307 309
     # @return A list of EmType objects
308 310
     def _linked_types_db(self):
309
-        conn = self.db_engine().connect()
311
+        conn = self.db_engine.connect()
310 312
         htable = self._table_hierarchy
311 313
         req = htable.select(htable.c.superior_id, htable.c.subordinate_id)
312 314
         req = req.where(sql.or_(htable.c.subordinate_id == self.uid, htable.c.superior_id == self.uid))

Loading…
Cancel
Save