Move field definition from Model to Field
issue8037 review74441002
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
* Move field definition from Model to Field
|
||||
* Use number of verbose flag as log level
|
||||
* Add coroutine concurrency option
|
||||
* Add increasing delay on database operational error retry
|
||||
|
||||
@@ -192,6 +192,14 @@ Instance methods:
|
||||
|
||||
Adds to `model` the default RPC instances required by the field.
|
||||
|
||||
.. method:: Field.definition(model, language)
|
||||
|
||||
Returns a dictionary with the definition of the field.
|
||||
|
||||
.. method:: Field.definition_translations(model, language)
|
||||
|
||||
Returns a list of translation sources used by :meth:`~Field.definition`.
|
||||
|
||||
Default value
|
||||
=============
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ class Binary(Field):
|
||||
domain=None, states=None, select=False, on_change=None,
|
||||
on_change_with=None, depends=None, context=None, loading='lazy',
|
||||
filename=None, file_id=None, store_prefix=None):
|
||||
self.filename = filename
|
||||
if filename is not None:
|
||||
self.filename = filename
|
||||
if depends is None:
|
||||
depends = [filename]
|
||||
else:
|
||||
@@ -116,3 +116,9 @@ class Binary(Field):
|
||||
values = [self.sql_format(value)]
|
||||
cursor.execute(*table.update(columns, values,
|
||||
where=reduce_ids(table.id, ids)))
|
||||
|
||||
def definition(self, model, language):
|
||||
definition = super().definition(model, language)
|
||||
definition['searchable'] = False
|
||||
definition['filename'] = self.filename
|
||||
return definition
|
||||
|
||||
@@ -78,3 +78,9 @@ class Char(FieldTranslate):
|
||||
database = Transaction().database
|
||||
value = database.unaccent(value)
|
||||
return value
|
||||
|
||||
def definition(self, model, language):
|
||||
definition = super().definition(model, language)
|
||||
definition['autocomplete'] = list(self.autocomplete)
|
||||
definition['size'] = self.size
|
||||
return definition
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import datetime
|
||||
from sql.functions import Function
|
||||
|
||||
from trytond.pyson import PYSONEncoder
|
||||
from ... import backend
|
||||
from .field import Field
|
||||
|
||||
@@ -52,7 +53,16 @@ class Date(Field):
|
||||
return super(Date, self).sql_cast(expression)
|
||||
|
||||
|
||||
class Timestamp(Field):
|
||||
class FormatMixin(Field):
|
||||
|
||||
def definition(self, model, language):
|
||||
encoder = PYSONEncoder()
|
||||
definition = super().definition(model, language)
|
||||
definition['format'] = encoder.encode(self.format)
|
||||
return definition
|
||||
|
||||
|
||||
class Timestamp(FormatMixin, Field):
|
||||
'''
|
||||
Define a timestamp field (``datetime``).
|
||||
'''
|
||||
@@ -184,3 +194,8 @@ class TimeDelta(Field):
|
||||
else:
|
||||
result[row['id']] = value
|
||||
return result
|
||||
|
||||
def definition(self, model, language):
|
||||
definition = super().definition(model, language)
|
||||
definition['converter'] = self.converter
|
||||
return definition
|
||||
|
||||
@@ -140,6 +140,11 @@ class Dict(Field):
|
||||
column = self.sql_column(table)
|
||||
return [database.json_get(column, key)]
|
||||
|
||||
def definition(self, model, language):
|
||||
definition = super().definition(model, language)
|
||||
definition['schema_model'] = self.schema_model
|
||||
return definition
|
||||
|
||||
|
||||
class TranslatedDict(object):
|
||||
'A descriptor for translated values of Dict field'
|
||||
|
||||
@@ -384,6 +384,40 @@ class Field(object):
|
||||
model.__rpc__.setdefault(
|
||||
func_name, RPC(instantiate=0, result=result))
|
||||
|
||||
def definition(self, model, language):
|
||||
pool = Pool()
|
||||
Translation = pool.get('ir.translation')
|
||||
encoder = PYSONEncoder()
|
||||
definition = {
|
||||
'context': encoder.encode(self.context),
|
||||
'loading': self.loading,
|
||||
'name': self.name,
|
||||
'on_change': list(self.on_change),
|
||||
'on_change_with': list(self.on_change_with),
|
||||
'readonly': self.readonly,
|
||||
'required': self.required,
|
||||
'states': encoder.encode(self.states),
|
||||
'type': self._type,
|
||||
'domain': encoder.encode(self.domain),
|
||||
'searchable': hasattr(model, 'search'),
|
||||
'sortable': hasattr(model, 'search'),
|
||||
}
|
||||
|
||||
name = '%s,%s' % (model.__class__, self.name)
|
||||
trans = Translation.get_source(name, 'field', language)
|
||||
definition['string'] = trans or self.string
|
||||
trans = Translation.get_source(name, 'help', language)
|
||||
definition['help'] = trans or self.help
|
||||
return definition
|
||||
|
||||
def definition_translations(self, model, language):
|
||||
"Returns sources used for definition"
|
||||
name = '%s,%s' % (model.__class__, self.name)
|
||||
return [
|
||||
(name, 'field', language, None),
|
||||
(name, 'help', language, None),
|
||||
]
|
||||
|
||||
|
||||
class FieldTranslate(Field):
|
||||
|
||||
@@ -493,3 +527,8 @@ class FieldTranslate(Field):
|
||||
language = get_parent_language(language)
|
||||
|
||||
return [Coalesce(column, self.sql_column(table))]
|
||||
|
||||
def definition(self, model, language):
|
||||
definition = super().definition(model, language)
|
||||
definition['translate'] = self.translate
|
||||
return definition
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
from trytond.pyson import PYSONEncoder
|
||||
from .field import Field
|
||||
from ...pyson import PYSON
|
||||
|
||||
@@ -52,3 +53,9 @@ class Float(Field):
|
||||
if value is None:
|
||||
return None
|
||||
return float(value)
|
||||
|
||||
def definition(self, model, language):
|
||||
encoder = PYSONEncoder()
|
||||
definition = super().definition(model, language)
|
||||
definition['digits'] = encoder.encode(self.digits)
|
||||
return definition
|
||||
|
||||
@@ -123,6 +123,14 @@ class Function(Field):
|
||||
def __set__(self, inst, value):
|
||||
self._field.__set__(inst, value)
|
||||
|
||||
def definition(self, model, language):
|
||||
definition = self._field.definition(model, language)
|
||||
definition.update(super().definition(model, language))
|
||||
definition['searchable'] &= (
|
||||
bool(self.searcher) or hasattr(model, 'domain_' + self.name))
|
||||
definition['sortable'] &= hasattr(model, 'order_' + self.name)
|
||||
return definition
|
||||
|
||||
|
||||
class MultiValue(Function):
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from sql import Cast, Literal, Null
|
||||
from sql.functions import Substring, Position
|
||||
from sql.conditionals import Coalesce
|
||||
|
||||
from trytond.pyson import PYSONEncoder
|
||||
from .field import (Field, size_validate, instanciate_values, domain_validate,
|
||||
search_order_validate, context_validate)
|
||||
from ...pool import Pool
|
||||
@@ -437,3 +438,18 @@ class Many2Many(Field):
|
||||
query_table = convert_from(None, relation_tables)
|
||||
query = query_table.select(origin, where=expression)
|
||||
return table.id.in_(query)
|
||||
|
||||
def definition(self, model, language):
|
||||
encoder = PYSONEncoder()
|
||||
definition = super().definition(model, language)
|
||||
definition['add_remove'] = encoder.encode(self.add_remove)
|
||||
definition['datetime_field'] = self.datetime_field
|
||||
if self.filter:
|
||||
definition['domain'] = encoder.encode(
|
||||
['AND', self.domain, self.filter])
|
||||
definition['relation'] = self.get_target().__name__
|
||||
definition['search_context'] = encoder.encode(self.search_context)
|
||||
definition['search_order'] = encoder.encode(self.search_order)
|
||||
definition['sortable'] &= hasattr(model, 'order_' + self.name)
|
||||
definition['size'] = encoder.encode(self.size)
|
||||
return definition
|
||||
|
||||
@@ -5,6 +5,7 @@ from sql.aggregate import Max
|
||||
from sql.conditionals import Coalesce
|
||||
from sql.operators import Or
|
||||
|
||||
from trytond.pyson import PYSONEncoder
|
||||
from .field import (Field, search_order_validate, context_validate,
|
||||
with_inactive_records)
|
||||
from ...pool import Pool
|
||||
@@ -289,3 +290,21 @@ class Many2One(Field):
|
||||
}
|
||||
tables[self.name] = target_tables
|
||||
return target_tables
|
||||
|
||||
def definition(self, model, language):
|
||||
encoder = PYSONEncoder()
|
||||
|
||||
target = self.get_target()
|
||||
relation_fields = [fname for fname, field in target._fields.items()
|
||||
if field._type == 'one2many'
|
||||
and field.model_name == model.__name__
|
||||
and field.field == self.name]
|
||||
|
||||
definition = super().definition(model, language)
|
||||
definition['datetime_field'] = self.datetime_field
|
||||
definition['relation'] = target.__name__
|
||||
if len(relation_fields) == 1:
|
||||
definition['relation_field'], = relation_fields
|
||||
definition['search_context'] = encoder.encode(self.search_context)
|
||||
definition['search_order'] = encoder.encode(self.search_order)
|
||||
return definition
|
||||
|
||||
@@ -5,6 +5,7 @@ from sql import Cast, Literal
|
||||
from sql.functions import Substring, Position
|
||||
from sql.conditionals import Coalesce
|
||||
|
||||
from trytond.pyson import PYSONEncoder
|
||||
from .field import (Field, size_validate, instanciate_values, domain_validate,
|
||||
search_order_validate, context_validate)
|
||||
from ...pool import Pool
|
||||
@@ -327,3 +328,19 @@ class One2Many(Field):
|
||||
if operator == 'not where':
|
||||
expression = ~expression
|
||||
return expression
|
||||
|
||||
def definition(self, model, language):
|
||||
encoder = PYSONEncoder()
|
||||
definition = super().definition(model, language)
|
||||
definition['add_remove'] = encoder.encode(self.add_remove)
|
||||
definition['datetime_field'] = self.datetime_field
|
||||
if self.filter:
|
||||
definition['domain'] = encoder.encode(
|
||||
['AND', self.domain, self.filter])
|
||||
definition['relation'] = self.model_name
|
||||
definition['relation_field'] = self.field
|
||||
definition['search_context'] = encoder.encode(self.search_context)
|
||||
definition['search_order'] = encoder.encode(self.search_order)
|
||||
definition['size'] = encoder.encode(self.size)
|
||||
definition['sortable'] &= hasattr(model, 'order_' + self.name)
|
||||
return definition
|
||||
|
||||
@@ -5,6 +5,7 @@ import warnings
|
||||
from sql import Cast, Literal, Query, Expression
|
||||
from sql.functions import Substring, Position
|
||||
|
||||
from trytond.pyson import PYSONEncoder
|
||||
from .field import (Field, search_order_validate, context_validate,
|
||||
with_inactive_records)
|
||||
from .selection import SelectionMixin
|
||||
@@ -13,7 +14,7 @@ from ...pool import Pool
|
||||
from ...rpc import RPC
|
||||
|
||||
|
||||
class Reference(Field, SelectionMixin):
|
||||
class Reference(SelectionMixin, Field):
|
||||
'''
|
||||
Define a reference field (``str``).
|
||||
'''
|
||||
@@ -177,3 +178,11 @@ class Reference(Field, SelectionMixin):
|
||||
Position(',', column) + Literal(1)),
|
||||
Model.id.sql_type().base).in_(query)
|
||||
& column.ilike(target + ',%'))
|
||||
|
||||
def definition(self, model, language):
|
||||
encoder = PYSONEncoder()
|
||||
definition = super().definition(model, language)
|
||||
definition['datetime_field'] = self.datetime_field
|
||||
definition['search_context'] = encoder.encode(self.search_context)
|
||||
definition['search_order'] = encoder.encode(self.search_order)
|
||||
return definition
|
||||
|
||||
@@ -4,13 +4,15 @@ import warnings
|
||||
|
||||
from sql.conditionals import Case
|
||||
|
||||
from trytond.pool import Pool
|
||||
from ...transaction import Transaction
|
||||
from ...tools import is_instance_method
|
||||
from .field import Field
|
||||
from ...rpc import RPC
|
||||
|
||||
|
||||
class SelectionMixin:
|
||||
class SelectionMixin(Field):
|
||||
translate_selection = True
|
||||
|
||||
def translated(self, name=None):
|
||||
"Return a descriptor for the translated value of the field"
|
||||
@@ -20,8 +22,36 @@ class SelectionMixin:
|
||||
raise ValueError('Missing name argument')
|
||||
return TranslatedSelection(name)
|
||||
|
||||
def definition(self, model, language):
|
||||
pool = Pool()
|
||||
Translation = pool.get('ir.translation')
|
||||
definition = super().definition(model, language)
|
||||
if not isinstance(self.selection, str) and self.translate_selection:
|
||||
name = '%s,%s' % (model.__class__, self.name)
|
||||
selection = []
|
||||
for key, source in self.selection:
|
||||
trans = Translation.get_source(
|
||||
name, 'selection', language, source)
|
||||
selection.append((key, trans or source))
|
||||
elif hasattr(self.selection, 'copy'):
|
||||
selection = self.selection.copy()
|
||||
else:
|
||||
selection = self.selection
|
||||
definition['selection'] = selection
|
||||
definition['selection_change_with'] = list(self.selection_change_with)
|
||||
return definition
|
||||
|
||||
class Selection(Field, SelectionMixin):
|
||||
def definition_translations(self, model, language):
|
||||
name = '%s,%s' % (model.__class__, self.name)
|
||||
selection = []
|
||||
if not isinstance(self.selection, str) and self.translate_selection:
|
||||
for key, source in self.selection:
|
||||
selection.append(
|
||||
(name, 'selection', language, source))
|
||||
return super().definition_translations(model, language) + selection
|
||||
|
||||
|
||||
class Selection(SelectionMixin, Field):
|
||||
'''
|
||||
Define a selection field (``str``).
|
||||
'''
|
||||
@@ -87,6 +117,11 @@ class Selection(Field, SelectionMixin):
|
||||
else:
|
||||
return [column]
|
||||
|
||||
def definition(self, model, language):
|
||||
definition = super().definition(model, language)
|
||||
definition['sort'] = self.sort
|
||||
return definition
|
||||
|
||||
|
||||
class TranslatedSelection(object):
|
||||
'A descriptor for translated value of Selection field'
|
||||
|
||||
+25
-173
@@ -6,7 +6,7 @@ from functools import total_ordering
|
||||
|
||||
from trytond.model import fields
|
||||
from trytond.pool import Pool, PoolBase, PoolMeta
|
||||
from trytond.pyson import PYSONEncoder
|
||||
from trytond.pyson import PYSONEncoder, PYSONDecoder
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.url import URLMixin
|
||||
from trytond.rpc import RPC
|
||||
@@ -158,7 +158,7 @@ class Model(URLMixin, PoolBase, metaclass=ModelMeta):
|
||||
"""
|
||||
Return the definition of each field on the model.
|
||||
"""
|
||||
res = {}
|
||||
definition = {}
|
||||
pool = Pool()
|
||||
Translation = pool.get('ir.translation')
|
||||
FieldAccess = pool.get('ir.model.field.access')
|
||||
@@ -167,186 +167,38 @@ class Model(URLMixin, PoolBase, metaclass=ModelMeta):
|
||||
# Add translation to cache
|
||||
language = Transaction().language
|
||||
trans_args = []
|
||||
for field in (x for x in cls._fields.keys()
|
||||
if ((not fields_names) or x in fields_names)):
|
||||
trans_args.append((cls.__name__ + ',' + field, 'field', language,
|
||||
None))
|
||||
trans_args.append((cls.__name__ + ',' + field, 'help', language,
|
||||
None))
|
||||
if hasattr(cls._fields[field], 'selection'):
|
||||
if (isinstance(cls._fields[field].selection, (tuple, list))
|
||||
and ((hasattr(cls._fields[field],
|
||||
'translate_selection')
|
||||
and cls._fields[field].translate_selection)
|
||||
or not hasattr(cls._fields[field],
|
||||
'translate_selection'))):
|
||||
sel = cls._fields[field].selection
|
||||
for (key, val) in sel:
|
||||
trans_args.append((cls.__name__ + ',' + field,
|
||||
'selection', language, val))
|
||||
for fname, field in cls._fields.items():
|
||||
if fields_names and fname not in fields_names:
|
||||
continue
|
||||
trans_args.extend(field.definition_translations(cls, language))
|
||||
Translation.get_sources(trans_args)
|
||||
|
||||
encoder = PYSONEncoder()
|
||||
decoder = PYSONDecoder(noeval=True)
|
||||
|
||||
accesses = FieldAccess.get_access([cls.__name__])[cls.__name__]
|
||||
for field in (x for x in cls._fields.keys()
|
||||
if ((not fields_names) or x in fields_names)):
|
||||
res[field] = {
|
||||
'type': cls._fields[field]._type,
|
||||
'name': field,
|
||||
}
|
||||
for arg in (
|
||||
'string',
|
||||
'readonly',
|
||||
'states',
|
||||
'size',
|
||||
'required',
|
||||
'translate',
|
||||
'help',
|
||||
'select',
|
||||
'on_change',
|
||||
'add_remove',
|
||||
'on_change_with',
|
||||
'autocomplete',
|
||||
'sort',
|
||||
'datetime_field',
|
||||
'loading',
|
||||
'filename',
|
||||
'selection_change_with',
|
||||
'domain',
|
||||
'converter',
|
||||
'search_order',
|
||||
'search_context',
|
||||
):
|
||||
if getattr(cls._fields[field], arg, None) is not None:
|
||||
value = getattr(cls._fields[field], arg)
|
||||
if isinstance(value, set):
|
||||
value = list(value)
|
||||
else:
|
||||
value = copy.copy(value)
|
||||
res[field][arg] = value
|
||||
for fname, field in cls._fields.items():
|
||||
if fields_names and fname not in fields_names:
|
||||
continue
|
||||
definition[fname] = field.definition(cls, language)
|
||||
if not accesses.get(field, {}).get('write', True):
|
||||
res[field]['readonly'] = True
|
||||
if res[field].get('states') and \
|
||||
'readonly' in res[field]['states']:
|
||||
del res[field]['states']['readonly']
|
||||
for arg in ('digits', 'invisible'):
|
||||
if hasattr(cls._fields[field], arg) \
|
||||
and getattr(cls._fields[field], arg):
|
||||
res[field][arg] = copy.copy(getattr(cls._fields[field],
|
||||
arg))
|
||||
if ((isinstance(cls._fields[field],
|
||||
(fields.Function, fields.One2Many,
|
||||
fields.Many2Many))
|
||||
and not getattr(cls, 'order_%s' % field, None))
|
||||
or not hasattr(cls, 'search')):
|
||||
res[field]['sortable'] = False
|
||||
if ((isinstance(cls._fields[field], fields.Function)
|
||||
and not (cls._fields[field].searcher
|
||||
or getattr(cls, 'domain_%s' % field, None)))
|
||||
or (cls._fields[field]._type in ('binary', 'sha'))
|
||||
or not hasattr(cls, 'search')):
|
||||
res[field]['searchable'] = False
|
||||
else:
|
||||
res[field]['searchable'] = True
|
||||
definition[fname]['readonly'] = True
|
||||
states = decoder.decode(definition[fname]['states'])
|
||||
states.pop('readonly', None)
|
||||
definition[fname]['states'] = encoder.encode(states)
|
||||
for right in ['create', 'delete']:
|
||||
definition[fname][right] = accesses.get(
|
||||
fname, {}).get(right, True)
|
||||
|
||||
if Transaction().context.get('language'):
|
||||
# translate the field label
|
||||
res_trans = Translation.get_source(
|
||||
cls.__name__ + ',' + field, 'field',
|
||||
Transaction().context['language'])
|
||||
if res_trans:
|
||||
res[field]['string'] = res_trans
|
||||
help_trans = Translation.get_source(
|
||||
cls.__name__ + ',' + field, 'help',
|
||||
Transaction().context['language'])
|
||||
if help_trans:
|
||||
res[field]['help'] = help_trans
|
||||
|
||||
if hasattr(cls._fields[field], 'selection'):
|
||||
if isinstance(cls._fields[field].selection, (tuple, list)):
|
||||
sel = copy.copy(cls._fields[field].selection)
|
||||
if (Transaction().context.get('language')
|
||||
and ((hasattr(cls._fields[field],
|
||||
'translate_selection')
|
||||
and cls._fields[field].translate_selection)
|
||||
or not hasattr(cls._fields[field],
|
||||
'translate_selection'))):
|
||||
# translate each selection option
|
||||
sel2 = []
|
||||
for (key, val) in sel:
|
||||
val2 = Translation.get_source(
|
||||
cls.__name__ + ',' + field, 'selection',
|
||||
language, val)
|
||||
sel2.append((key, val2 or val))
|
||||
sel = sel2
|
||||
res[field]['selection'] = sel
|
||||
else:
|
||||
# call the 'dynamic selection' function
|
||||
res[field]['selection'] = copy.copy(
|
||||
cls._fields[field].selection)
|
||||
if res[field]['type'] in (
|
||||
'one2many',
|
||||
'many2many',
|
||||
'many2one',
|
||||
'one2one',
|
||||
):
|
||||
if hasattr(cls._fields[field], 'model_name'):
|
||||
relation = copy.copy(cls._fields[field].model_name)
|
||||
else:
|
||||
relation = copy.copy(
|
||||
cls._fields[field].get_target().__name__)
|
||||
res[field]['relation'] = relation
|
||||
res[field]['context'] = copy.copy(cls._fields[field].context)
|
||||
res[field]['create'] = accesses.get(field, {}).get('create',
|
||||
True)
|
||||
res[field]['delete'] = accesses.get(field, {}).get('delete',
|
||||
True)
|
||||
if res[field]['type'] == 'one2many' \
|
||||
and getattr(cls._fields[field], 'field', None):
|
||||
res[field]['relation_field'] = copy.copy(
|
||||
cls._fields[field].field)
|
||||
if res[field]['type'] == 'many2one':
|
||||
target = cls._fields[field].get_target()
|
||||
relation_fields = []
|
||||
for target_name, target_field in target._fields.items():
|
||||
if (target_field._type == 'one2many'
|
||||
and target_field.model_name == cls.__name__
|
||||
and target_field.field == field):
|
||||
relation_fields.append(target_name)
|
||||
# Set relation_field only if there is no ambiguity
|
||||
if len(relation_fields) == 1:
|
||||
res[field]['relation_field'], = relation_fields
|
||||
if res[field]['type'] in ('datetime', 'time', 'timestamp'):
|
||||
res[field]['format'] = copy.copy(cls._fields[field].format)
|
||||
if res[field]['type'] == 'selection':
|
||||
res[field]['context'] = copy.copy(cls._fields[field].context)
|
||||
if res[field]['type'] == 'dict':
|
||||
res[field]['schema_model'] = cls._fields[field].schema_model
|
||||
res[field]['domain'] = copy.copy(cls._fields[field].domain)
|
||||
res[field]['context'] = copy.copy(cls._fields[field].context)
|
||||
res[field]['create'] = accesses.get(field, {}).get('create',
|
||||
True)
|
||||
res[field]['delete'] = accesses.get(field, {}).get('delete',
|
||||
True)
|
||||
filter_ = getattr(cls._fields[field], 'filter', None)
|
||||
if filter_:
|
||||
res[field]['domain'] = ['AND', res[field]['domain'], filter_]
|
||||
|
||||
# convert attributes into pyson
|
||||
for attr in ('states', 'domain', 'context', 'digits', 'size',
|
||||
'add_remove', 'format', 'search_order', 'search_context'):
|
||||
if attr in res[field]:
|
||||
res[field][attr] = encoder.encode(res[field][attr])
|
||||
|
||||
for i in list(res.keys()):
|
||||
for fname in list(definition.keys()):
|
||||
# filter out fields which aren't in the fields_names list
|
||||
if fields_names:
|
||||
if i not in fields_names:
|
||||
del res[i]
|
||||
elif not ModelAccess.check_relation(cls.__name__, i, mode='read'):
|
||||
del res[i]
|
||||
return res
|
||||
if fname not in fields_names:
|
||||
del definition[fname]
|
||||
elif not ModelAccess.check_relation(
|
||||
cls.__name__, fname, mode='read'):
|
||||
del definition[fname]
|
||||
return definition
|
||||
|
||||
def pre_validate(self):
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user