Browse Source

Put the database configuration asinstance attribute of EmComponent

Yann Weber 9 years ago
parent
commit
889abc8547

+ 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

@@ -46,7 +46,7 @@ class EmClass(EmComponent):
46 46
         #Create a new entry in the em_class table
47 47
         result = super(EmClass, cls).create(name=name, classtype=classtype, icon=icon, sortcolumn=sortcolumn, **em_component_args)
48 48
 
49
-        dbe = result.db_engine()
49
+        dbe = result.db_engine
50 50
         conn = dbe.connect()
51 51
 
52 52
         #Create a new table storing LodelObjects of this EmClass
@@ -73,7 +73,7 @@ class EmClass(EmComponent):
73 73
             do_delete = False
74 74
             return False
75 75
 
76
-        dbe = self.__class__.db_engine()
76
+        dbe = self.db_engine
77 77
         meta = sqlutils.meta(dbe)
78 78
         #Here we have to give a connection
79 79
         class_table = sql.Table(self.name, meta)
@@ -92,7 +92,7 @@ class EmClass(EmComponent):
92 92
     ## Isolate SQL for EmClass::fieldgroups
93 93
     # @return An array of dict (sqlalchemy fetchall)
94 94
     def _fieldgroups_db(self):
95
-        dbe = self.__class__.db_engine()
95
+        dbe = self.db_engine
96 96
         emfg = sql.Table(EditorialModel.fieldgroups.EmFieldGroup.table, sqlutils.meta(dbe))
97 97
         req = emfg.select().where(emfg.c.class_id == self.uid)
98 98
 
@@ -120,7 +120,7 @@ class EmClass(EmComponent):
120 120
     ## Isolate SQL for EmCLass::types
121 121
     # @return An array of dict (sqlalchemy fetchall)
122 122
     def _types_db(self):
123
-        dbe = self.__class__.db_engine()
123
+        dbe = self.db_engine
124 124
         emtype = sql.Table(EditorialModel.types.EmType.table, sqlutils.meta(dbe))
125 125
         req = emtype.select().where(emtype.c.class_id == self.uid)
126 126
         conn = dbe.connect()
@@ -138,7 +138,7 @@ class EmClass(EmComponent):
138 138
 
139 139
     def _link_type_db(self, table_name):
140 140
         #Create a new table storing LodelObjects that are linked to this EmClass
141
-        conn = self.__class__.db_engine().connect()
141
+        conn = self.db_engine.connect()
142 142
         meta = sql.MetaData()
143 143
         emlinketable = sql.Table(table_name, meta, sql.Column('uid', sql.VARCHAR(50), primary_key=True))
144 144
         emlinketable.create(conn)
@@ -150,7 +150,7 @@ class EmClass(EmComponent):
150 150
         return self._linked_types_db()
151 151
 
152 152
     def _linked_types_db(self):
153
-        dbe = self.__class__.db_engine()
153
+        dbe = self.db_engine
154 154
         meta = sql.MetaData()
155 155
         meta.reflect(dbe)
156 156
 

+ 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
 
@@ -50,9 +51,10 @@ class EmFieldGroup(EmComponent):
50 51
     ## Get the list of associated fields
51 52
     # @return A list of EmField instance
52 53
     def fields(self):
53
-        field_table = sqlutils.getTable(EditorialModel.fields.EmField)
54
+        meta = sqlutils.meta(self.db_engine)
55
+        field_table = sql.Table(EditorialModel.fields.EmField.table, meta)
54 56
         req = field_table.select(field_table.c.uid).where(field_table.c.fieldgroup_id == self.uid)
55
-        conn = self.__class__.db_engine().connect()
57
+        conn = self.db_engine.connect()
56 58
         res = conn.execute(req)
57 59
         rows = res.fetchall()
58 60
         conn.close()

+ 6
- 5
EditorialModel/fields.py View File

@@ -76,11 +76,11 @@ class EmField(EmComponent):
76 76
     # @return bool : True if deleted False if deletion aborded
77 77
     # @todo Check if unconditionnal deletion is correct
78 78
     def delete(self):
79
-        dbe = self.__class__.db_engine()
79
+        dbe = self.db_engine
80 80
         class_table = sql.Table(self.get_class_table(), sqlutils.meta(dbe))
81 81
         field_col = sql.Column(self.name)
82 82
         ddl = DropColumn(class_table, field_col)
83
-        sqlutils.ddl_execute(ddl, self.__class__.db_engine())
83
+        sqlutils.ddl_execute(ddl, self.db_engine)
84 84
         return super(EmField, self).delete()
85 85
 
86 86
     ## add_field_column_to_class_table (Function)
@@ -90,7 +90,7 @@ class EmField(EmComponent):
90 90
     # @param emField EmField: the object representing the field
91 91
     # @return True in case of success, False if not
92 92
     def add_field_column_to_class_table(self):
93
-        dbe = self.db_engine()
93
+        dbe = self.db_engine
94 94
         fieldtype = get_field_type(self.fieldtype)
95 95
         new_column = sql.Column(name=self.name, **(fieldtype.sqlalchemy_args()) )
96 96
         class_table = sql.Table(self.get_class_table(), sqlutils.meta(dbe))
@@ -110,9 +110,10 @@ class EmField(EmComponent):
110 110
     # @return An EmClass instance
111 111
     def get_class(self):
112 112
         #<SQL>
113
-        dbe = self.db_engine()
113
+        dbe = self.db_engine
114
+        meta = sqlutils.meta(dbe)
114 115
         conn = dbe.connect()
115
-        fieldgroup_table = sqlutils.getTable(EmFieldGroup)
116
+        fieldgroup_table = sql.Table(EmFieldGroup.table, meta)
116 117
         req = fieldgroup_table.select().where(fieldgroup_table.c.uid == self.fieldgroup_id)
117 118
         res = conn.execute(req)
118 119
         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
- 10
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
@@ -77,9 +77,10 @@ class EmType(EmComponent):
77 77
     ## Get the list of associated fieldgroups
78 78
     # @return A list of EmFieldGroup instance
79 79
     def field_groups(self):
80
-        fg_table = sqlutils.getTable(EmFieldGroup)
80
+        meta = sqlutils.meta(self.db_engine)
81
+        fg_table = sql.Table(EmFieldGroup.table, meta)
81 82
         req = fg_table.select(fg_table.c.uid).where(fg_table.c.class_id == self.class_id)
82
-        conn = self.__class__.db_engine().connect()
83
+        conn = self.db_engine.connect()
83 84
         res = conn.execute(req)
84 85
         rows = res.fetchall()
85 86
         conn.close()
@@ -97,7 +98,7 @@ class EmType(EmComponent):
97 98
     ## Return selected optional field
98 99
     # @return A list of EmField instance
99 100
     def selected_fields(self):
100
-        dbe = self.db_engine()
101
+        dbe = self.db_engine
101 102
         meta = sqlutils.meta(dbe)
102 103
         conn = dbe.connect()
103 104
 
@@ -157,7 +158,7 @@ class EmType(EmComponent):
157 158
         if not field.optional:
158 159
             raise ValueError("This field is not optional")
159 160
 
160
-        dbe = self.db_engine()
161
+        dbe = self.db_engine
161 162
         meta = sqlutils.meta(dbe)
162 163
         conn = dbe.connect()
163 164
 
@@ -219,9 +220,9 @@ class EmType(EmComponent):
219 220
     # @throw RunTimeError if a nature fetched from db is not valid
220 221
     # @see EmType::subordinates(), EmType::superiors()
221 222
     def _subOrSup(self, sup = True):
222
-        conn = self.db_engine().connect()
223
+        conn = self.db_engine.connect()
223 224
         htable = self._table_hierarchy
224
-        type_table = sqlutils.get_table(self.__class__)
225
+        type_table = sqlutils.get_table(self)
225 226
 
226 227
         req = htable.select()
227 228
         if sup:
@@ -271,7 +272,7 @@ class EmType(EmComponent):
271 272
         elif self.name != em_type.name:
272 273
             raise ValueError("Not allowed to put a different em_type as superior in a relation of nature '"+relation_nature+"'")
273 274
 
274
-        conn = self.db_engine().connect()
275
+        conn = self.db_engine.connect()
275 276
         htable = self._table_hierarchy
276 277
         values = { 'subordinate_id': self.uid, 'superior_id': em_type.uid, 'nature': relation_nature }
277 278
         req = htable.insert(values=values)
@@ -296,7 +297,7 @@ class EmType(EmComponent):
296 297
         if relation_nature not in EmClassType.natures(self.classtype['name']):
297 298
             raise ValueError("Invalid nature for add_superior : '"+relation_nature+"'. Allowed relations for this type are "+str(EmClassType.natures(self.classtype['name'])))
298 299
 
299
-        conn = self.db_engine().connect()
300
+        conn = self.db_engine.connect()
300 301
         htable = self._table_hierarchy
301 302
         req = htable.delete(htable.c.superior_id == em_type.uid and htable.c.nature == relation_nature)
302 303
         conn.execute(req)
@@ -312,7 +313,7 @@ class EmType(EmComponent):
312 313
     ## @brief Return the list of all the types linked to this type, should they be superiors or subordinates
313 314
     # @return A list of EmType objects
314 315
     def _linked_types_Db(self):
315
-        conn = self.db_engine().connect()
316
+        conn = self.db_engine.connect()
316 317
         htable = self._table_hierarchy
317 318
         req = htable.select(htable.c.superior_id, htable.c.subordinate_id)
318 319
         req = req.where(sql.or_(htable.c.subordinate_id == self.uid, htable.c.superior_id == self.uid))

Loading…
Cancel
Save