66import dateutil
77import time
88from collections import defaultdict
9- from odoo import models , api , fields
9+
10+ from odoo import api , fields , models , _
1011from odoo .tools .safe_eval import safe_eval
1112
13+ from ..exceptions import OauthScopeValidationException
14+
1215
1316class OauthProviderScope (models .Model ):
1417 _name = 'oauth.provider.scope'
@@ -41,9 +44,10 @@ class OauthProviderScope(models.Model):
4144 ('code_unique' , 'UNIQUE (code)' ,
4245 'The code of the scopes must be unique !' ),
4346 ]
44-
47+
48+ @property
4549 @api .model
46- def _get_ir_filter_eval_context (self ):
50+ def ir_filter_eval_context (self ):
4751 """ Returns the base eval context for ir.filter domains evaluation """
4852 return {
4953 'datetime' : datetime ,
@@ -54,23 +58,46 @@ def _get_ir_filter_eval_context(self):
5458 }
5559
5660 @api .multi
57- def get_data_for_model (self , model , res_id = None , all_scopes_match = False ):
58- """ Return the data matching the scopes from the requested model """
61+ def filter_by_model (self , model ):
62+ """ Return the current scopes that are associated to the model.
63+
64+ Args:
65+ model (str): Name of the model to operate on.
66+
67+ Returns:
68+ OauthProviderScope: Recordsets associated to the model.
69+ """
70+ return self .filtered (lambda record : record .model == model )
71+
72+ @api .multi
73+ def get_data (self , model , res_id = None , all_scopes_match = False ,
74+ domain = None ):
75+ """ Return the data matching the scopes from the requested model.
76+
77+ Args:
78+ model (str): Name of the model to operate on.
79+ res_id (int): ID of record to find. Will only return this record,
80+ if defined.
81+ all_scopes_match (bool): True to filter out records that do not
82+ match all of the scopes in the current recordset.
83+ domain (list of tuples, optional): Domain to append to the
84+ `filter_domain` that is defined in the scope.
85+
86+ Returns:
87+ dict: If `res_id` is defined, this will be the scoped data for the
88+ appropriate record (or empty dict if no match). Otherwise,
89+ this will be a dictionary of scoped record data, keyed by
90+ record ID.
91+ """
92+
5993 data = defaultdict (dict )
60- eval_context = self ._get_ir_filter_eval_context ()
94+ eval_context = self .ir_filter_eval_context
6195 all_scopes_records = self .env [model ]
62- for scope in self .filtered (lambda record : record .model == model ):
63- # Retrieve the scope's domain
64- filter_domain = [(1 , '=' , 1 )]
65- if scope .filter_id :
66- filter_domain = safe_eval (
67- scope .filter_id .sudo ().domain , eval_context )
68- if res_id is not None :
69- filter_domain .append (('id' , '=' , res_id ))
70-
71- # Retrieve data of the matching records, depending on the scope's
72- # fields
73- records = self .env [model ].search (filter_domain )
96+
97+ for scope in self .filter_by_model (model ):
98+
99+ records = scope ._get_scoped_records (eval_context , domain )
100+
74101 for record_data in records .read (scope .field_ids .mapped ('name' )):
75102 for field , value in record_data .items ():
76103 if isinstance (value , tuple ):
@@ -86,17 +113,174 @@ def get_data_for_model(self, model, res_id=None, all_scopes_match=False):
86113 all_scopes_records &= records
87114
88115 # If all scopes are required to match, filter the results to keep only
89- # those mathing all scopes
116+ # those matching all scopes
90117 if all_scopes_match :
91- data = dict (filter (
92- lambda record_data : record_data [0 ] in all_scopes_records .ids ,
93- data .items ()))
94-
95- # If a single record was requested, return only data coming from this
96- # record
97- # Return an empty dictionnary if this record didn't recieve data to
98- # return
118+ data = dict (
119+ filter (
120+ lambda _data : _data [0 ] in all_scopes_records .ids ,
121+ data .items (),
122+ ),
123+ )
124+
99125 if res_id is not None :
100126 data = data .get (res_id , {})
101127
102128 return data
129+
130+ @api .multi
131+ def create_record (self , model , vals ):
132+ """ Create a record, validate the scope, and return (if valid).
133+
134+ Args:
135+ model (str): Name of the model to operate on.
136+ vals (dict): Values to create record with, keyed by field name.
137+
138+ Returns:
139+ OauthProviderScope: Newly created record
140+
141+ Raises:
142+ OauthScopeValidationException: If fields are included in vals,
143+ but are not within the current scope.
144+ """
145+
146+ if not self ._validate_scope_field (model , vals ):
147+ raise OauthScopeValidationException ('field' )
148+
149+ record = self .env [model ].create (vals )
150+
151+ if not self ._validate_scope_record (record ):
152+ raise OauthScopeValidationException ('record' )
153+
154+ return record
155+
156+ @api .multi
157+ def write_record (self , records , vals ):
158+ """ Write to a recordset, adhering to the current scope.
159+
160+ Args:
161+ records (models.Model): Recordset to write to.
162+ vals (dict): Values to modify records with, keyed by field name.
163+
164+ Returns:
165+ OauthProviderScope: The same recordset that was provided as input.
166+
167+ Raises:
168+ OauthScopeValidationException: Raised in the following cases:
169+ * If records are attempted to be edited, but are not within
170+ the current scope.
171+ * If fields are included in vals, but are not within the
172+ current scope.
173+ * If the record no longer falls within scope after being
174+ """
175+
176+ if not self ._validate_scope_field (records ._name , vals ):
177+ raise OauthScopeValidationException ('field' )
178+
179+ if not self ._validate_scope_record (records ):
180+ raise OauthScopeValidationException ('record' )
181+
182+ records .write (vals )
183+
184+ if not self ._validate_scope_record (records ):
185+ raise OauthScopeValidationException ('record' )
186+
187+ return records
188+
189+ @api .multi
190+ def delete_record (self , records ):
191+ """ Delete a recordset that is within the current scope.
192+
193+ Args:
194+ records (models.Model): Recordset to delete.
195+
196+ Raises:
197+ OauthScopeValidationException: If records are not within the
198+ current scope.
199+ """
200+
201+ if not self ._validate_scope_record (records ):
202+ raise OauthScopeValidationException ('record' )
203+
204+ records .unlink ()
205+
206+ @api .multi
207+ def _get_filter_domain (self , eval_context ):
208+ """ Return the scope's domain.
209+
210+ Args:
211+ eval_context (dict): Base eval context, such as provided by
212+ `ir_filter_eval_context`
213+
214+ Returns:
215+ list of tuples: Domain of the scope, in standard Odoo format.
216+ """
217+ self .ensure_one ()
218+ filter_domain = [(1 , '=' , 1 )]
219+ if self .filter_id :
220+ filter_domain = safe_eval (
221+ self .filter_id .sudo ().domain ,
222+ eval_context ,
223+ )
224+ if res_id is not None :
225+ filter_domain .append (
226+ ('id' , '=' , res_id ),
227+ )
228+ return filter_domain
229+
230+ @api .multi
231+ def _get_scoped_records (self , model , eval_context = None , add_domain = None ):
232+ """ Return records that are within the scopes in the recordset.
233+
234+ Args:
235+ model (str): Name of the model to operate on.
236+ eval_context (dict, optional): Base eval context, such as provided
237+ by `ir_filter_eval_context`.
238+ add_domain (list of tuples, optional): Domain to append to the
239+ `filter_domain` that is defined in the scope.
240+
241+ Returns:
242+ models.Model: Recordset matching the scope.
243+ """
244+ self .ensure_one ()
245+ if eval_context is None :
246+ eval_context = self .ir_filter_eval_context
247+ if add_domain is None :
248+ add_domain = []
249+ filter_domain = self ._get_filter_domain (eval_context )
250+ return self .env [model ].search (filter_domain + add_domain )
251+
252+ @api .multi
253+ def _validate_scope_record (self , records ):
254+ """ Validate that the recordset is within the current scope.
255+
256+ Args:
257+ records (models.Model): Recordset to validate.
258+
259+ Returns:
260+ bool: Indicating whether the records are within scope.
261+ """
262+
263+ scoped_records = self ._get_scoped_records (
264+ self .env [records ._name ],
265+ )
266+ return all ([
267+ r .id in scoped_records for r in records
268+ ])
269+
270+ @api .multi
271+ def _validate_scope_field (self , model , vals ):
272+ """ Validate that the input vals do not violate the current scope.
273+
274+ Args:
275+ model (str): Name of the model to operate on.
276+ vals (dict): Values that should be checked against the current
277+ scope, keyed by field name.
278+
279+ Returns:
280+ bool: Whether the values are within the scope.
281+ """
282+ scopes = self .filter_by_model (model )
283+ field_names = scopes .field_ids .mapped ('name' )
284+ return all ([
285+ f in field_names for f in vals .keys ()
286+ ])
0 commit comments