~camptocamp/openobject-server/trunk-geoengine

« back to all changes in this revision

Viewing changes to openerp/osv/orm.py

  • Committer: nicolas.bessi at camptocamp
  • Date: 2011-09-29 08:00:55 UTC
  • mfrom: (3575.1.91 openobject-server)
  • Revision ID: nicolas.bessi@camptocamp.com-20110929080055-7b3viwngd46ruwy5
[MRG] from trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
45
45
import copy
46
46
import datetime
47
47
import logging
48
 
import warnings
49
48
import operator
50
49
import pickle
51
50
import re
 
51
import simplejson
52
52
import time
53
53
import traceback
54
54
import types
55
 
import simplejson
 
55
import warnings
 
56
from lxml import etree
56
57
 
 
58
import fields
 
59
import openerp
57
60
import openerp.netsvc as netsvc
58
 
from lxml import etree
 
61
import openerp.tools as tools
59
62
from openerp.tools.config import config
 
63
from openerp.tools.safe_eval import safe_eval as eval
60
64
from openerp.tools.translate import _
61
 
 
62
 
import fields
 
65
from openerp import SUPERUSER_ID
63
66
from query import Query
64
 
import openerp.tools as tools
65
 
from openerp.tools.safe_eval import safe_eval as eval
66
67
 
67
68
# List of etree._Element subclasses that we choose to ignore when parsing XML.
68
69
from openerp.tools import SKIPPED_ELEMENT_TYPES
70
71
regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
71
72
regex_object_name = re.compile(r'^[a-z0-9_.]+$')
72
73
 
73
 
# Super-user identifier (aka Administrator aka root)
74
 
ROOT_USER_ID = 1
75
 
 
76
74
def transfer_field_to_modifiers(field, modifiers):
77
75
    default_values = {}
78
76
    state_exceptions = {}
585
583
        self.module_to_models.setdefault(self._module, []).append(self)
586
584
 
587
585
 
588
 
class orm_template(object):
 
586
# Definition of log access columns, automatically added to models if
 
587
# self._log_access is True
 
588
LOG_ACCESS_COLUMNS = {
 
589
    'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
 
590
    'create_date': 'TIMESTAMP',
 
591
    'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
 
592
    'write_date': 'TIMESTAMP'
 
593
}
 
594
# special columns automatically created by the ORM
 
595
MAGIC_COLUMNS =  ['id'] + LOG_ACCESS_COLUMNS.keys()
 
596
 
 
597
class BaseModel(object):
589
598
    """ Base class for OpenERP models.
590
599
 
591
 
    OpenERP models are created by inheriting from this class (although
592
 
    not directly; more specifically by inheriting from osv or
593
 
    osv_memory). The constructor is called once, usually directly
594
 
    after the class definition, e.g.:
595
 
 
596
 
        class user(osv):
597
 
            ...
598
 
        user()
599
 
 
600
 
    The system will later instanciate the class once per database (on
 
600
    OpenERP models are created by inheriting from this class' subclasses:
 
601
 
 
602
        * Model: for regular database-persisted models
 
603
        * TransientModel: for temporary data, stored in the database but automatically
 
604
                          vaccuumed every so often
 
605
        * AbstractModel: for abstract super classes meant to be shared by multiple
 
606
                        _inheriting classes (usually Models or TransientModels)
 
607
 
 
608
    The system will later instantiate the class once per database (on
601
609
    which the class' module is installed).
602
610
 
 
611
    To create a class that should not be instantiated, the _register class attribute
 
612
    may be set to False.
603
613
    """
 
614
    __metaclass__ = MetaModel
 
615
    _register = False # Set to false if the model shouldn't be automatically discovered.
604
616
    _name = None
605
617
    _columns = {}
606
618
    _constraints = []
614
626
    _sequence = None
615
627
    _description = None
616
628
 
 
629
    # Transience
 
630
    _transient = False # True in a TransientModel
 
631
    _transient_max_count = None
 
632
    _transient_max_hours = None
 
633
    _transient_check_time = 20
 
634
 
617
635
    # structure:
618
636
    #  { 'parent_model': 'm2o_field', ... }
619
637
    _inherits = {}
636
654
    _table = None
637
655
    _invalids = set()
638
656
    _log_create = False
 
657
    _sql_constraints = []
 
658
    _protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count', 'exists']
 
659
    __logger = logging.getLogger('orm')
 
660
    __schema = logging.getLogger('orm.schema')
639
661
 
640
662
    CONCURRENCY_CHECK_FIELD = '__last_update'
641
663
 
656
678
        """Override this method to do specific things when a view on the object is opened."""
657
679
        pass
658
680
 
659
 
    def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
660
 
        raise NotImplementedError(_('The read_group method is not implemented on this object !'))
661
 
 
662
681
    def _field_create(self, cr, context=None):
663
682
        """ Create entries in ir_model_fields for all the model's fields.
664
683
 
761
780
                        break
762
781
        cr.commit()
763
782
 
764
 
    def _auto_init(self, cr, context=None):
765
 
        raise_on_invalid_object_name(self._name)
766
 
        self._field_create(cr, context=context)
767
 
 
768
 
    def _auto_end(self, cr, context=None):
769
 
        pass
770
 
 
771
783
    #
772
784
    # Goal: try to apply inheritance at the instanciation level and
773
785
    #       put objects in the pool var
774
786
    #
775
787
    @classmethod
776
 
    def makeInstance(cls, pool, cr, attributes):
 
788
    def create_instance(cls, pool, cr):
777
789
        """ Instanciate a given model.
778
790
 
779
791
        This class method instanciates the class of some model (i.e. a class
788
800
        this method. This is probably unnecessary.
789
801
 
790
802
        """
 
803
        attributes = ['_columns', '_defaults', '_inherits', '_constraints',
 
804
            '_sql_constraints']
 
805
 
791
806
        parent_names = getattr(cls, '_inherit', None)
792
807
        if parent_names:
793
808
            if isinstance(parent_names, (str, unicode)):
843
858
 
844
859
        This doesn't create an instance but simply register the model
845
860
        as being part of the module where it is defined.
 
861
 
846
862
        """
 
863
 
 
864
 
847
865
        # Set the module name (e.g. base, sale, accounting, ...) on the class.
848
866
        module = cls.__module__.split('.')[0]
849
867
        if not hasattr(cls, '_module'):
860
878
        return None
861
879
 
862
880
    def __init__(self, pool, cr):
863
 
        """ Initialize a model and make it part of the given registry."""
 
881
        """ Initialize a model and make it part of the given registry.
 
882
 
 
883
        - copy the stored fields' functions in the osv_pool,
 
884
        - update the _columns with the fields found in ir_model_fields,
 
885
        - ensure there is a many2one for each _inherits'd parent,
 
886
        - update the children's _columns,
 
887
        - give a chance to each field to initialize itself.
 
888
 
 
889
        """
864
890
        pool.add(self._name, self)
865
891
        self.pool = pool
866
892
 
877
903
        if not self._table:
878
904
            self._table = self._name.replace('.', '_')
879
905
 
880
 
    def browse(self, cr, uid, select, context=None, list_class=None, fields_process=None):
881
 
        """Fetch records as objects allowing to use dot notation to browse fields and relations
882
 
 
883
 
        :param cr: database cursor
884
 
        :param user: current user id
885
 
        :param select: id or list of ids.
886
 
        :param context: context arguments, like lang, time zone
887
 
        :rtype: object or list of objects requested
888
 
 
889
 
        """
890
 
        self._list_class = list_class or browse_record_list
891
 
        cache = {}
892
 
        # need to accepts ints and longs because ids coming from a method
893
 
        # launched by button in the interface have a type long...
894
 
        if isinstance(select, (int, long)):
895
 
            return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
896
 
        elif isinstance(select, list):
897
 
            return self._list_class([browse_record(cr, uid, id, self, cache, context=context, list_class=self._list_class, fields_process=fields_process) for id in select], context=context)
898
 
        else:
899
 
            return browse_null()
 
906
        if not hasattr(self, '_log_access'):
 
907
            # If _log_access is not specified, it is the same value as _auto.
 
908
            self._log_access = getattr(self, "_auto", True)
 
909
 
 
910
        self._columns = self._columns.copy()
 
911
        for store_field in self._columns:
 
912
            f = self._columns[store_field]
 
913
            if hasattr(f, 'digits_change'):
 
914
                f.digits_change(cr)
 
915
            if not isinstance(f, fields.function):
 
916
                continue
 
917
            if not f.store:
 
918
                continue
 
919
            if self._columns[store_field].store is True:
 
920
                sm = {self._name: (lambda self, cr, uid, ids, c={}: ids, None, 10, None)}
 
921
            else:
 
922
                sm = self._columns[store_field].store
 
923
            for object, aa in sm.items():
 
924
                if len(aa) == 4:
 
925
                    (fnct, fields2, order, length) = aa
 
926
                elif len(aa) == 3:
 
927
                    (fnct, fields2, order) = aa
 
928
                    length = None
 
929
                else:
 
930
                    raise except_orm('Error',
 
931
                        ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
 
932
                self.pool._store_function.setdefault(object, [])
 
933
                ok = True
 
934
                for x, y, z, e, f, l in self.pool._store_function[object]:
 
935
                    if (x==self._name) and (y==store_field) and (e==fields2):
 
936
                        if f == order:
 
937
                            ok = False
 
938
                if ok:
 
939
                    self.pool._store_function[object].append( (self._name, store_field, fnct, tuple(fields2) if fields2 else None, order, length))
 
940
                    self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
 
941
 
 
942
        for (key, _, msg) in self._sql_constraints:
 
943
            self.pool._sql_error[self._table+'_'+key] = msg
 
944
 
 
945
        # Load manual fields
 
946
 
 
947
        cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
 
948
        if cr.fetchone():
 
949
            cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
 
950
            for field in cr.dictfetchall():
 
951
                if field['name'] in self._columns:
 
952
                    continue
 
953
                attrs = {
 
954
                    'string': field['field_description'],
 
955
                    'required': bool(field['required']),
 
956
                    'readonly': bool(field['readonly']),
 
957
                    'domain': eval(field['domain']) if field['domain'] else None,
 
958
                    'size': field['size'],
 
959
                    'ondelete': field['on_delete'],
 
960
                    'translate': (field['translate']),
 
961
                    'manual': True,
 
962
                    #'select': int(field['select_level'])
 
963
                }
 
964
 
 
965
                if field['ttype'] == 'selection':
 
966
                    self._columns[field['name']] = fields.selection(eval(field['selection']), **attrs)
 
967
                elif field['ttype'] == 'reference':
 
968
                    self._columns[field['name']] = fields.reference(selection=eval(field['selection']), **attrs)
 
969
                elif field['ttype'] == 'many2one':
 
970
                    self._columns[field['name']] = fields.many2one(field['relation'], **attrs)
 
971
                elif field['ttype'] == 'one2many':
 
972
                    self._columns[field['name']] = fields.one2many(field['relation'], field['relation_field'], **attrs)
 
973
                elif field['ttype'] == 'many2many':
 
974
                    _rel1 = field['relation'].replace('.', '_')
 
975
                    _rel2 = field['model'].replace('.', '_')
 
976
                    _rel_name = 'x_%s_%s_%s_rel' % (_rel1, _rel2, field['name'])
 
977
                    self._columns[field['name']] = fields.many2many(field['relation'], _rel_name, 'id1', 'id2', **attrs)
 
978
                else:
 
979
                    self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
 
980
        self._inherits_check()
 
981
        self._inherits_reload()
 
982
        if not self._sequence:
 
983
            self._sequence = self._table + '_id_seq'
 
984
        for k in self._defaults:
 
985
            assert (k in self._columns) or (k in self._inherit_fields), 'Default function defined in %s but field %s does not exist !' % (self._name, k,)
 
986
        for f in self._columns:
 
987
            self._columns[f].restart()
 
988
 
 
989
        # Transience
 
990
        if self.is_transient():
 
991
            self._transient_check_count = 0
 
992
            self._transient_max_count = config.get('osv_memory_count_limit')
 
993
            self._transient_max_hours = config.get('osv_memory_age_limit')
 
994
            assert self._log_access, "TransientModels must have log_access turned on, "\
 
995
                                     "in order to implement their access rights policy"
900
996
 
901
997
    def __export_row(self, cr, uid, row, fields, context=None):
902
998
        if context is None:
941
1037
                            else:
942
1038
                                r = d['name']
943
1039
                        else:
944
 
                            break
 
1040
                            postfix = 0
 
1041
                            while True:
 
1042
                                n = self._table+'_'+str(r['id']) + (postfix and ('_'+str(postfix)) or '' )
 
1043
                                if not model_data.search(cr, uid, [('name', '=', n)]):
 
1044
                                    break
 
1045
                                postfix += 1
 
1046
                            model_data.create(cr, uid, {
 
1047
                                'name': n,
 
1048
                                'model': self._name,
 
1049
                                'res_id': r['id'],
 
1050
                            })
 
1051
                            r = n
945
1052
                    else:
946
1053
                        r = r[f[i]]
947
1054
                        # To display external name of selection field when its exported
1249
1356
            self._parent_store_compute(cr)
1250
1357
        return (position, 0, 0, 0)
1251
1358
 
1252
 
    def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
1253
 
        """
1254
 
        Read records with given ids with the given fields
1255
 
 
1256
 
        :param cr: database cursor
1257
 
        :param user: current user id
1258
 
        :param ids: id or list of the ids of the records to read
1259
 
        :param fields: optional list of field names to return (default: all fields would be returned)
1260
 
        :type fields: list (example ['field_name_1', ...])
1261
 
        :param context: optional context dictionary - it may contains keys for specifying certain options
1262
 
                        like ``context_lang``, ``context_tz`` to alter the results of the call.
1263
 
                        A special ``bin_size`` boolean flag may also be passed in the context to request the
1264
 
                        value of all fields.binary columns to be returned as the size of the binary instead of its
1265
 
                        contents. This can also be selectively overriden by passing a field-specific flag
1266
 
                        in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
1267
 
                        Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
1268
 
        :return: list of dictionaries((dictionary per record asked)) with requested field values
1269
 
        :rtype: [{‘name_of_the_field’: value, ...}, ...]
1270
 
        :raise AccessError: * if user has no read rights on the requested object
1271
 
                            * if user tries to bypass access rules for read on the requested object
1272
 
 
1273
 
        """
1274
 
        raise NotImplementedError(_('The read method is not implemented on this object !'))
1275
 
 
1276
1359
    def get_invalid_fields(self, cr, uid):
1277
1360
        return list(self._invalids)
1278
1361
 
1405
1488
                defaults[key[8:]] = context[key]
1406
1489
        return defaults
1407
1490
 
1408
 
 
1409
 
    def perm_read(self, cr, user, ids, context=None, details=True):
1410
 
        raise NotImplementedError(_('The perm_read method is not implemented on this object !'))
1411
 
 
1412
 
    def unlink(self, cr, uid, ids, context=None):
1413
 
        raise NotImplementedError(_('The unlink method is not implemented on this object !'))
1414
 
 
1415
 
    def write(self, cr, user, ids, vals, context=None):
1416
 
        raise NotImplementedError(_('The write method is not implemented on this object !'))
1417
 
 
1418
 
    def create(self, cr, user, vals, context=None):
1419
 
        raise NotImplementedError(_('The create method is not implemented on this object !'))
1420
 
 
1421
1491
    def fields_get_keys(self, cr, user, context=None):
1422
1492
        res = self._columns.keys()
1423
1493
        # TODO I believe this loop can be replace by
1426
1496
            res.extend(self.pool.get(parent).fields_get_keys(cr, user, context))
1427
1497
        return res
1428
1498
 
1429
 
 
1430
 
    def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
1431
 
        """ Return the definition of each field.
1432
 
 
1433
 
            The returned value is a dictionary (indiced by field name) of
1434
 
            dictionaries. The _inherits'd fields are included. The string,
1435
 
            help, and selection (if present) attributes are translated.
1436
 
 
1437
 
        """
1438
 
        if context is None:
1439
 
            context = {}
1440
 
 
1441
 
        res = {}
1442
 
 
1443
 
        translation_obj = self.pool.get('ir.translation')
1444
 
        for parent in self._inherits:
1445
 
            res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
1446
 
 
1447
 
        for f, field in self._columns.iteritems():
1448
 
            if allfields and f not in allfields:
1449
 
                continue
1450
 
 
1451
 
            res[f] = fields.field_to_dict(self, cr, user, context, field)
1452
 
 
1453
 
            if not write_access:
1454
 
                res[f]['readonly'] = True
1455
 
                res[f]['states'] = {}
1456
 
 
1457
 
            if 'string' in res[f]:
1458
 
                res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
1459
 
                if res_trans:
1460
 
                    res[f]['string'] = res_trans
1461
 
            if 'help' in res[f]:
1462
 
                help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
1463
 
                if help_trans:
1464
 
                    res[f]['help'] = help_trans
1465
 
            if 'selection' in res[f]:
1466
 
                if isinstance(field.selection, (tuple, list)):
1467
 
                    sel = field.selection
1468
 
                    sel2 = []
1469
 
                    for key, val in sel:
1470
 
                        val2 = None
1471
 
                        if val:
1472
 
                            val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
1473
 
                        sel2.append((key, val2 or val))
1474
 
                    res[f]['selection'] = sel2
1475
 
 
1476
 
        return res
1477
 
 
1478
 
 
1479
1499
    #
1480
1500
    # Overload this method if you need a window title which depends on the context
1481
1501
    #
2117
2137
        """
2118
2138
        return self._search(cr, user, args, offset=offset, limit=limit, order=order, context=context, count=count)
2119
2139
 
2120
 
    def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
2121
 
        """
2122
 
        Private implementation of search() method, allowing specifying the uid to use for the access right check.
2123
 
        This is useful for example when filling in the selection list for a drop-down and avoiding access rights errors,
2124
 
        by specifying ``access_rights_uid=1`` to bypass access rights check, but not ir.rules!
2125
 
 
2126
 
        :param access_rights_uid: optional user ID to use when checking access rights
2127
 
                                  (not for ir.rules, this is only for ir.model.access)
2128
 
        """
2129
 
        raise NotImplementedError(_('The search method is not implemented on this object !'))
2130
 
 
2131
2140
    def name_get(self, cr, user, ids, context=None):
2132
2141
        """Returns the preferred display value (text representation) for the records with the
2133
2142
           given ``ids``. By default this will be the value of the ``name`` column, unless
2198
2207
        res = self.name_get(cr, access_rights_uid, ids, context)
2199
2208
        return res
2200
2209
 
2201
 
    def copy(self, cr, uid, id, default=None, context=None):
2202
 
        raise NotImplementedError(_('The copy method is not implemented on this object !'))
2203
 
 
2204
 
    def exists(self, cr, uid, ids, context=None):
2205
 
        """Checks whether the given id or ids exist in this model,
2206
 
           and return the list of ids that do. This is simple to use for
2207
 
           a truth test on a browse_record::
2208
 
 
2209
 
               if record.exists():
2210
 
                   pass
2211
 
 
2212
 
           :param ids: id or list of ids to check for existence
2213
 
           :type ids: int or [int]
2214
 
           :return: the list of ids that currently exist, out of
2215
 
                    the given `ids`
2216
 
        """
2217
 
        raise NotImplementedError(_('The exists method is not implemented on this object !'))
2218
 
 
2219
2210
    def read_string(self, cr, uid, id, langs, fields=None, context=None):
2220
2211
        res = {}
2221
2212
        res2 = {}
2256
2247
                self.pool.get(table).write_string(cr, uid, id, langs, vals, context)
2257
2248
        return True
2258
2249
 
2259
 
    def _check_removed_columns(self, cr, log=False):
2260
 
        raise NotImplementedError()
2261
 
 
2262
2250
    def _add_missing_default_values(self, cr, uid, values, context=None):
2263
2251
        missing_defaults = []
2264
2252
        avoid_tables = [] # avoid overriding inherited values when parent is set
2300
2288
        except AttributeError:
2301
2289
            pass
2302
2290
 
2303
 
    def check_access_rule(self, cr, uid, ids, operation, context=None):
2304
 
        """Verifies that the operation given by ``operation`` is allowed for the user
2305
 
           according to ir.rules.
2306
 
 
2307
 
           :param operation: one of ``write``, ``unlink``
2308
 
           :raise except_orm: * if current ir.rules do not permit this operation.
2309
 
           :return: None if the operation is allowed
2310
 
        """
2311
 
        raise NotImplementedError(_('The check_access_rule method is not implemented on this object !'))
2312
 
 
2313
 
class orm_memory(orm_template):
2314
 
 
2315
 
    _protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count', 'exists']
2316
 
    _inherit_fields = {}
2317
 
    _max_count = None
2318
 
    _max_hours = None
2319
 
    _check_time = 20
2320
 
 
2321
 
    @classmethod
2322
 
    def createInstance(cls, pool, cr):
2323
 
        return cls.makeInstance(pool, cr, ['_columns', '_defaults'])
2324
 
 
2325
 
    def __init__(self, pool, cr):
2326
 
        super(orm_memory, self).__init__(pool, cr)
2327
 
        self.datas = {}
2328
 
        self.next_id = 0
2329
 
        self.check_id = 0
2330
 
        self._max_count = config.get('osv_memory_count_limit')
2331
 
        self._max_hours = config.get('osv_memory_age_limit')
2332
 
        cr.execute('delete from wkf_instance where res_type=%s', (self._name,))
2333
 
 
2334
 
    def _check_access(self, uid, object_id, mode):
2335
 
        if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid:
2336
 
            raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects except for the super-user' % mode.capitalize())
2337
 
 
2338
 
    def vaccum(self, cr, uid, force=False):
2339
 
        """Run the vaccuum cleaning system, expiring and removing old records from the
2340
 
        virtual osv_memory tables if the "max count" or "max age" conditions are enabled
2341
 
        and have been reached. This method can be called very often (e.g. everytime a record
2342
 
        is created), but will only actually trigger the cleanup process once out of
2343
 
        "_check_time" times (by default once out of 20 calls)."""
2344
 
        self.check_id += 1
2345
 
        if (not force) and (self.check_id % self._check_time):
2346
 
            return True
2347
 
        tounlink = []
2348
 
 
2349
 
        # Age-based expiration
2350
 
        if self._max_hours:
2351
 
            max = time.time() - self._max_hours * 60 * 60
2352
 
            for k,v in self.datas.iteritems():
2353
 
                if v['internal.date_access'] < max:
2354
 
                    tounlink.append(k)
2355
 
            self.unlink(cr, ROOT_USER_ID, tounlink)
2356
 
 
2357
 
        # Count-based expiration
2358
 
        if self._max_count and len(self.datas) > self._max_count:
2359
 
            # sort by access time to remove only the first/oldest ones in LRU fashion
2360
 
            records = self.datas.items()
2361
 
            records.sort(key=lambda x:x[1]['internal.date_access'])
2362
 
            self.unlink(cr, ROOT_USER_ID, [x[0] for x in records[:len(self.datas)-self._max_count]])
2363
 
 
2364
 
        return True
2365
 
 
2366
 
    def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'):
2367
 
        if not context:
2368
 
            context = {}
2369
 
        if not fields_to_read:
2370
 
            fields_to_read = self._columns.keys()
2371
 
        result = []
2372
 
        if self.datas:
2373
 
            ids_orig = ids
2374
 
            if isinstance(ids, (int, long)):
2375
 
                ids = [ids]
2376
 
            for id in ids:
2377
 
                r = {'id': id}
2378
 
                for f in fields_to_read:
2379
 
                    record = self.datas.get(id)
2380
 
                    if record:
2381
 
                        self._check_access(user, id, 'read')
2382
 
                        r[f] = record.get(f, False)
2383
 
                        if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False):
2384
 
                            r[f] = len(r[f])
2385
 
                result.append(r)
2386
 
                if id in self.datas:
2387
 
                    self.datas[id]['internal.date_access'] = time.time()
2388
 
            fields_post = filter(lambda x: x in self._columns and not getattr(self._columns[x], load), fields_to_read)
2389
 
            for f in fields_post:
2390
 
                res2 = self._columns[f].get_memory(cr, self, ids, f, user, context=context, values=result)
2391
 
                for record in result:
2392
 
                    record[f] = res2[record['id']]
2393
 
            if isinstance(ids_orig, (int, long)):
2394
 
                return result[0]
2395
 
        return result
2396
 
 
2397
 
    def write(self, cr, user, ids, vals, context=None):
2398
 
        if not ids:
2399
 
            return True
2400
 
        vals2 = {}
2401
 
        upd_todo = []
2402
 
        for field in vals:
2403
 
            if self._columns[field]._classic_write:
2404
 
                vals2[field] = vals[field]
2405
 
            else:
2406
 
                upd_todo.append(field)
2407
 
        for object_id in ids:
2408
 
            self._check_access(user, object_id, mode='write')
2409
 
            self.datas[object_id].update(vals2)
2410
 
            self.datas[object_id]['internal.date_access'] = time.time()
2411
 
            for field in upd_todo:
2412
 
                self._columns[field].set_memory(cr, self, object_id, field, vals[field], user, context)
2413
 
        self._validate(cr, user, [object_id], context)
2414
 
        wf_service = netsvc.LocalService("workflow")
2415
 
        wf_service.trg_write(user, self._name, object_id, cr)
2416
 
        return object_id
2417
 
 
2418
 
    def create(self, cr, user, vals, context=None):
2419
 
        self.vaccum(cr, user)
2420
 
        self.next_id += 1
2421
 
        id_new = self.next_id
2422
 
 
2423
 
        vals = self._add_missing_default_values(cr, user, vals, context)
2424
 
 
2425
 
        vals2 = {}
2426
 
        upd_todo = []
2427
 
        for field in vals:
2428
 
            if self._columns[field]._classic_write:
2429
 
                vals2[field] = vals[field]
2430
 
            else:
2431
 
                upd_todo.append(field)
2432
 
        self.datas[id_new] = vals2
2433
 
        self.datas[id_new]['internal.date_access'] = time.time()
2434
 
        self.datas[id_new]['internal.create_uid'] = user
2435
 
 
2436
 
        for field in upd_todo:
2437
 
            self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context)
2438
 
        self._validate(cr, user, [id_new], context)
2439
 
        if self._log_create and not (context and context.get('no_store_function', False)):
2440
 
            message = self._description + \
2441
 
                " '" + \
2442
 
                self.name_get(cr, user, [id_new], context=context)[0][1] + \
2443
 
                "' "+ _("created.")
2444
 
            self.log(cr, user, id_new, message, True, context=context)
2445
 
        wf_service = netsvc.LocalService("workflow")
2446
 
        wf_service.trg_create(user, self._name, id_new, cr)
2447
 
        return id_new
2448
 
 
2449
 
    def _where_calc(self, cr, user, args, active_test=True, context=None):
2450
 
        if not context:
2451
 
            context = {}
2452
 
        args = args[:]
2453
 
        res = []
2454
 
        # if the object has a field named 'active', filter out all inactive
2455
 
        # records unless they were explicitely asked for
2456
 
        if 'active' in self._columns and (active_test and context.get('active_test', True)):
2457
 
            if args:
2458
 
                active_in_args = False
2459
 
                for a in args:
2460
 
                    if a[0] == 'active':
2461
 
                        active_in_args = True
2462
 
                if not active_in_args:
2463
 
                    args.insert(0, ('active', '=', 1))
2464
 
            else:
2465
 
                args = [('active', '=', 1)]
2466
 
        if args:
2467
 
            import expression
2468
 
            e = expression.expression(cr, user, args, self, context)
2469
 
            res = e.exp
2470
 
        return res or []
2471
 
 
2472
 
    def _search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False, access_rights_uid=None):
2473
 
        if not context:
2474
 
            context = {}
2475
 
 
2476
 
        # implicit filter on current user except for superuser
2477
 
        if user != 1:
2478
 
            if not args:
2479
 
                args = []
2480
 
            args.insert(0, ('internal.create_uid', '=', user))
2481
 
 
2482
 
        result = self._where_calc(cr, user, args, context=context)
2483
 
        if result == []:
2484
 
            return self.datas.keys()
2485
 
 
2486
 
        res = []
2487
 
        counter = 0
2488
 
        #Find the value of dict
2489
 
        f = False
2490
 
        if result:
2491
 
            for id, data in self.datas.items():
2492
 
                counter = counter + 1
2493
 
                data['id'] = id
2494
 
                if limit and (counter > int(limit)):
2495
 
                    break
2496
 
                f = True
2497
 
                for arg in result:
2498
 
                    if len(arg) != 3:
2499
 
                       # Amazing hack: orm_memory handles only simple domains.
2500
 
                       continue
2501
 
                    if arg[1] == '=':
2502
 
                        val = eval('data[arg[0]]'+'==' +' arg[2]', locals())
2503
 
                    elif arg[1] in ['<', '>', 'in', 'not in', '<=', '>=', '<>']:
2504
 
                        val = eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
2505
 
                    elif arg[1] in ['ilike']:
2506
 
                        val = (str(data[arg[0]]).find(str(arg[2]))!=-1)
2507
 
 
2508
 
                    f = f and val
2509
 
 
2510
 
                if f:
2511
 
                    res.append(id)
2512
 
        if count:
2513
 
            return len(res)
2514
 
        return res or []
2515
 
 
2516
 
    def unlink(self, cr, uid, ids, context=None):
2517
 
        for id in ids:
2518
 
            self._check_access(uid, id, 'unlink')
2519
 
            self.datas.pop(id, None)
2520
 
        if len(ids):
2521
 
            cr.execute('delete from wkf_instance where res_type=%s and res_id IN %s', (self._name, tuple(ids)))
2522
 
        return True
2523
 
 
2524
 
    def perm_read(self, cr, user, ids, context=None, details=True):
2525
 
        result = []
2526
 
        credentials = self.pool.get('res.users').name_get(cr, user, [user])[0]
2527
 
        create_date = time.strftime('%Y-%m-%d %H:%M:%S')
2528
 
        for id in ids:
2529
 
            self._check_access(user, id, 'read')
2530
 
            result.append({
2531
 
                'create_uid': credentials,
2532
 
                'create_date': create_date,
2533
 
                'write_uid': False,
2534
 
                'write_date': False,
2535
 
                'id': id,
2536
 
                'xmlid' : False,
2537
 
            })
2538
 
        return result
2539
 
 
2540
 
    def _check_removed_columns(self, cr, log=False):
2541
 
        # nothing to check in memory...
2542
 
        pass
2543
 
 
2544
 
    def exists(self, cr, uid, ids, context=None):
2545
 
        if isinstance(ids, (long,int)):
2546
 
            ids = [ids]
2547
 
        return [id for id in ids if id in self.datas]
2548
 
 
2549
 
    def check_access_rule(self, cr, uid, ids, operation, context=None):
2550
 
        # ir.rules do not currently apply for orm.memory instances, 
2551
 
        # only the implicit visibility=owner one.
2552
 
        for id in ids:
2553
 
            self._check_access(uid, id, operation)
2554
 
 
2555
 
# Definition of log access columns, automatically added to models if
2556
 
# self._log_access is True
2557
 
LOG_ACCESS_COLUMNS = {
2558
 
    'create_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2559
 
    'create_date': 'TIMESTAMP',
2560
 
    'write_uid': 'INTEGER REFERENCES res_users ON DELETE SET NULL',
2561
 
    'write_date': 'TIMESTAMP'
2562
 
}
2563
 
# special columns automatically created by the ORM
2564
 
MAGIC_COLUMNS =  ['id'] + LOG_ACCESS_COLUMNS.keys() + \
2565
 
                 ['internal.create_uid', 'internal.date_access'] # for osv_memory only
2566
 
 
2567
 
class orm(orm_template):
2568
 
    _sql_constraints = []
2569
 
    _table = None
2570
 
    _protected = ['read', 'write', 'create', 'default_get', 'perm_read', 'unlink', 'fields_get', 'fields_view_get', 'search', 'name_get', 'distinct_field_get', 'name_search', 'copy', 'import_data', 'search_count', 'exists']
2571
 
    __logger = logging.getLogger('orm')
2572
 
    __schema = logging.getLogger('orm.schema')
2573
 
 
2574
2291
    def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
2575
2292
        """
2576
2293
        Get the list of records in list view grouped by the given ``groupby`` fields
2762
2479
        while ids_lst:
2763
2480
            iids = ids_lst[:40]
2764
2481
            ids_lst = ids_lst[40:]
2765
 
            res = f.get(cr, self, iids, k, ROOT_USER_ID, {})
 
2482
            res = f.get(cr, self, iids, k, SUPERUSER_ID, {})
2766
2483
            for key, val in res.items():
2767
2484
                if f._multi:
2768
2485
                    val = val[k]
2817
2534
                self.__schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
2818
2535
                                    self._table, column['attname'])
2819
2536
 
 
2537
    # checked version: for direct m2o starting from `self`
 
2538
    def _m2o_add_foreign_key_checked(self, source_field, dest_model, ondelete):
 
2539
        assert self.is_transient() or not dest_model.is_transient(), \
 
2540
            'Many2One relationships from non-transient Model to TransientModel are forbidden'
 
2541
        if self.is_transient() and not dest_model.is_transient():
 
2542
            # TransientModel relationships to regular Models are annoying
 
2543
            # usually because they could block deletion due to the FKs.
 
2544
            # So unless stated otherwise we default them to ondelete=cascade.
 
2545
            ondelete = ondelete or 'cascade'
 
2546
        self._foreign_keys.append((self._table, source_field, dest_model._table, ondelete or 'set null'))
 
2547
        self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s",
 
2548
                                        self._table, source_field, dest_model._table, ondelete)
 
2549
 
 
2550
    # unchecked version: for custom cases, such as m2m relationships
 
2551
    def _m2o_add_foreign_key_unchecked(self, source_table, source_field, dest_model, ondelete):
 
2552
        self._foreign_keys.append((source_table, source_field, dest_model._table, ondelete or 'set null'))
 
2553
        self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s",
 
2554
                                        source_table, source_field, dest_model._table, ondelete)
 
2555
 
2820
2556
    def _auto_init(self, cr, context=None):
2821
2557
        """
2822
2558
 
2968
2704
                                # set the field to the default value if any
2969
2705
                                if k in self._defaults:
2970
2706
                                    if callable(self._defaults[k]):
2971
 
                                        default = self._defaults[k](self, cr, ROOT_USER_ID, context)
 
2707
                                        default = self._defaults[k](self, cr, SUPERUSER_ID, context)
2972
2708
                                    else:
2973
2709
                                        default = self._defaults[k]
2974
2710
 
3015
2751
                                self.__schema.debug(msg, self._table, k, f._type)
3016
2752
 
3017
2753
                            if isinstance(f, fields.many2one):
3018
 
                                ref = self.pool.get(f._obj)._table
 
2754
                                dest_model = self.pool.get(f._obj)
 
2755
                                ref = dest_model._table
3019
2756
                                if ref != 'ir_actions':
3020
2757
                                    cr.execute('SELECT confdeltype, conname FROM pg_constraint as con, pg_class as cl1, pg_class as cl2, '
3021
2758
                                                'pg_attribute as att1, pg_attribute as att2 '
3034
2771
                                                "AND con.contype = 'f'", (self._table, ref, k, 'id'))
3035
2772
                                    res2 = cr.dictfetchall()
3036
2773
                                    if res2:
3037
 
                                        if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get(f.ondelete.upper(), 'a'):
 
2774
                                        if res2[0]['confdeltype'] != POSTGRES_CONFDELTYPES.get((f.ondelete or 'set null').upper(), 'a'):
3038
2775
                                            cr.execute('ALTER TABLE "' + self._table + '" DROP CONSTRAINT "' + res2[0]['conname'] + '"')
3039
 
                                            self._foreign_keys.append((self._table, k, ref, f.ondelete))
 
2776
                                            self._m2o_add_foreign_key_checked(k, dest_model, f.ondelete)
3040
2777
                                            cr.commit()
3041
2778
                                            self.__schema.debug("Table '%s': column '%s': XXX",
3042
2779
                                                self._table, k)
3053
2790
                            # initialize it
3054
2791
                            if not create and k in self._defaults:
3055
2792
                                if callable(self._defaults[k]):
3056
 
                                    default = self._defaults[k](self, cr, ROOT_USER_ID, context)
 
2793
                                    default = self._defaults[k](self, cr, SUPERUSER_ID, context)
3057
2794
                                else:
3058
2795
                                    default = self._defaults[k]
3059
2796
 
3074
2811
                            if isinstance(f, fields.many2one):
3075
2812
                                if not self.pool.get(f._obj):
3076
2813
                                    raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
3077
 
                                ref = self.pool.get(f._obj)._table
 
2814
                                dest_model = self.pool.get(f._obj)
 
2815
                                ref = dest_model._table
3078
2816
                                # ir_actions is inherited so foreign key doesn't work on it
3079
2817
                                if ref != 'ir_actions':
3080
 
                                    self._foreign_keys.append((self._table, k, ref, f.ondelete))
3081
 
                                    self.__schema.debug("Table '%s': added foreign key '%s' with definition=REFERENCES \"%s\" ON DELETE %s",
3082
 
                                        self._table, k, ref, f.ondelete)
 
2818
                                    self._m2o_add_foreign_key_checked(k, dest_model, f.ondelete)
3083
2819
                            if f.select:
3084
2820
                                cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (self._table, k, self._table, k))
3085
2821
                            if f.required:
3201
2937
 
3202
2938
 
3203
2939
    def _m2m_raise_or_create_relation(self, cr, f):
3204
 
        cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (f._rel,))
 
2940
        m2m_tbl, col1, col2 = f._sql_names(self)
 
2941
        cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (m2m_tbl,))
3205
2942
        if not cr.dictfetchall():
3206
2943
            if not self.pool.get(f._obj):
3207
 
                raise except_orm('Programming Error', ('There is no reference available for %s') % (f._obj,))
3208
 
            ref = self.pool.get(f._obj)._table
3209
 
            cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL, "%s" INTEGER NOT NULL, UNIQUE("%s","%s")) WITH OIDS' % (f._rel, f._id1, f._id2, f._id1, f._id2))
3210
 
            self._foreign_keys.append((f._rel, f._id1, self._table, 'CASCADE'))
3211
 
            self._foreign_keys.append((f._rel, f._id2, ref, 'CASCADE'))
3212
 
            cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id1, f._rel, f._id1))
3213
 
            cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (f._rel, f._id2, f._rel, f._id2))
3214
 
            cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (f._rel, self._table, ref))
 
2944
                raise except_orm('Programming Error', ('Many2Many destination model does not exist: `%s`') % (f._obj,))
 
2945
            dest_model = self.pool.get(f._obj)
 
2946
            ref = dest_model._table
 
2947
            cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL, "%s" INTEGER NOT NULL, UNIQUE("%s","%s")) WITH OIDS' % (m2m_tbl, col1, col2, col1, col2))
 
2948
 
 
2949
            # create foreign key references with ondelete=cascade, unless the targets are SQL views
 
2950
            cr.execute("SELECT relkind FROM pg_class WHERE relkind IN ('v') AND relname=%s", (ref,))
 
2951
            if not cr.fetchall():
 
2952
                self._m2o_add_foreign_key_unchecked(m2m_tbl, col2, dest_model, 'cascade')
 
2953
            cr.execute("SELECT relkind FROM pg_class WHERE relkind IN ('v') AND relname=%s", (self._table,))
 
2954
            if not cr.fetchall():
 
2955
                self._m2o_add_foreign_key_unchecked(m2m_tbl, col1, self, 'cascade')
 
2956
 
 
2957
            cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (m2m_tbl, col1, m2m_tbl, col1))
 
2958
            cr.execute('CREATE INDEX "%s_%s_index" ON "%s" ("%s")' % (m2m_tbl, col2, m2m_tbl, col2))
 
2959
            cr.execute("COMMENT ON TABLE \"%s\" IS 'RELATION BETWEEN %s AND %s'" % (m2m_tbl, self._table, ref))
3215
2960
            cr.commit()
3216
 
            self.__schema.debug("Create table '%s': relation between '%s' and '%s'",
3217
 
                                f._rel, self._table, ref)
 
2961
            self.__schema.debug("Create table '%s': m2m relation between '%s' and '%s'", m2m_tbl, self._table, ref)
3218
2962
 
3219
2963
 
3220
2964
    def _add_sql_constraints(self, cr):
3282
3026
                    cr.execute(line2)
3283
3027
                    cr.commit()
3284
3028
 
3285
 
 
3286
 
    @classmethod
3287
 
    def createInstance(cls, pool, cr):
3288
 
        return cls.makeInstance(pool, cr, ['_columns', '_defaults',
3289
 
            '_inherits', '_constraints', '_sql_constraints'])
3290
 
 
3291
 
    def __init__(self, pool, cr):
3292
 
        """
3293
 
 
3294
 
        - copy the stored fields' functions in the osv_pool,
3295
 
        - update the _columns with the fields found in ir_model_fields,
3296
 
        - ensure there is a many2one for each _inherits'd parent,
3297
 
        - update the children's _columns,
3298
 
        - give a chance to each field to initialize itself.
3299
 
 
3300
 
        """
3301
 
        super(orm, self).__init__(pool, cr)
3302
 
 
3303
 
        if not hasattr(self, '_log_access'):
3304
 
            # if not access is not specify, it is the same value as _auto
3305
 
            self._log_access = getattr(self, "_auto", True)
3306
 
 
3307
 
        self._columns = self._columns.copy()
3308
 
        for store_field in self._columns:
3309
 
            f = self._columns[store_field]
3310
 
            if hasattr(f, 'digits_change'):
3311
 
                f.digits_change(cr)
3312
 
            if not isinstance(f, fields.function):
3313
 
                continue
3314
 
            if not f.store:
3315
 
                continue
3316
 
            if self._columns[store_field].store is True:
3317
 
                sm = {self._name: (lambda self, cr, uid, ids, c={}: ids, None, 10, None)}
3318
 
            else:
3319
 
                sm = self._columns[store_field].store
3320
 
            for object, aa in sm.items():
3321
 
                if len(aa) == 4:
3322
 
                    (fnct, fields2, order, length) = aa
3323
 
                elif len(aa) == 3:
3324
 
                    (fnct, fields2, order) = aa
3325
 
                    length = None
3326
 
                else:
3327
 
                    raise except_orm('Error',
3328
 
                        ('Invalid function definition %s in object %s !\nYou must use the definition: store={object:(fnct, fields, priority, time length)}.' % (store_field, self._name)))
3329
 
                self.pool._store_function.setdefault(object, [])
3330
 
                ok = True
3331
 
                for x, y, z, e, f, l in self.pool._store_function[object]:
3332
 
                    if (x==self._name) and (y==store_field) and (e==fields2):
3333
 
                        if f == order:
3334
 
                            ok = False
3335
 
                if ok:
3336
 
                    self.pool._store_function[object].append((self._name, store_field, fnct, tuple(fields2) if fields2 else None, order, length))
3337
 
                    self.pool._store_function[object].sort(lambda x, y: cmp(x[4], y[4]))
3338
 
 
3339
 
        for (key, _, msg) in self._sql_constraints:
3340
 
            self.pool._sql_error[self._table+'_'+key] = msg
3341
 
 
3342
 
        # Load manual fields
3343
 
 
3344
 
        cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", ('state', 'ir.model.fields'))
3345
 
        if cr.fetchone():
3346
 
            cr.execute('SELECT * FROM ir_model_fields WHERE model=%s AND state=%s', (self._name, 'manual'))
3347
 
            for field in cr.dictfetchall():
3348
 
                if field['name'] in self._columns:
3349
 
                    continue
3350
 
                attrs = {
3351
 
                    'string': field['field_description'],
3352
 
                    'required': bool(field['required']),
3353
 
                    'readonly': bool(field['readonly']),
3354
 
                    'domain': eval(field['domain']) if field['domain'] else None,
3355
 
                    'size': field['size'],
3356
 
                    'ondelete': field['on_delete'],
3357
 
                    'translate': (field['translate']),
3358
 
                    'manual': True,
3359
 
                    #'select': int(field['select_level'])
3360
 
                }
3361
 
 
3362
 
                if field['ttype'] == 'selection':
3363
 
                    self._columns[field['name']] = fields.selection(eval(field['selection']), **attrs)
3364
 
                elif field['ttype'] == 'reference':
3365
 
                    self._columns[field['name']] = fields.reference(selection=eval(field['selection']), **attrs)
3366
 
                elif field['ttype'] == 'many2one':
3367
 
                    self._columns[field['name']] = fields.many2one(field['relation'], **attrs)
3368
 
                elif field['ttype'] == 'one2many':
3369
 
                    self._columns[field['name']] = fields.one2many(field['relation'], field['relation_field'], **attrs)
3370
 
                elif field['ttype'] == 'many2many':
3371
 
                    _rel1 = field['relation'].replace('.', '_')
3372
 
                    _rel2 = field['model'].replace('.', '_')
3373
 
                    _rel_name = 'x_%s_%s_%s_rel' % (_rel1, _rel2, field['name'])
3374
 
                    self._columns[field['name']] = fields.many2many(field['relation'], _rel_name, 'id1', 'id2', **attrs)
3375
 
                else:
3376
 
                    self._columns[field['name']] = getattr(fields, field['ttype'])(**attrs)
3377
 
        self._inherits_check()
3378
 
        self._inherits_reload()
3379
 
        if not self._sequence:
3380
 
            self._sequence = self._table + '_id_seq'
3381
 
        for k in self._defaults:
3382
 
            assert (k in self._columns) or (k in self._inherit_fields), 'Default function defined in %s but field %s does not exist !' % (self._name, k,)
3383
 
        for f in self._columns:
3384
 
            self._columns[f].restart()
3385
 
 
3386
 
    __init__.__doc__ = orm_template.__init__.__doc__ + __init__.__doc__
3387
 
 
3388
3029
    #
3389
3030
    # Update objects that uses this one to update their _inherits fields
3390
3031
    #
3463
3104
    #    return _proxy
3464
3105
 
3465
3106
 
3466
 
    def fields_get(self, cr, user, fields=None, context=None):
3467
 
        """
3468
 
        Get the description of list of fields
 
3107
    def fields_get(self, cr, user, allfields=None, context=None, write_access=True):
 
3108
        """ Return the definition of each field.
 
3109
 
 
3110
        The returned value is a dictionary (indiced by field name) of
 
3111
        dictionaries. The _inherits'd fields are included. The string, help,
 
3112
        and selection (if present) attributes are translated.
3469
3113
 
3470
3114
        :param cr: database cursor
3471
3115
        :param user: current user id
3475
3119
        :raise AccessError: * if user has no create/write rights on the requested object
3476
3120
 
3477
3121
        """
 
3122
        if context is None:
 
3123
            context = {}
 
3124
 
3478
3125
        ira = self.pool.get('ir.model.access')
3479
3126
        write_access = ira.check(cr, user, self._name, 'write', False) or \
3480
3127
                       ira.check(cr, user, self._name, 'create', False)
3481
 
        return super(orm, self).fields_get(cr, user, fields, context, write_access)
 
3128
 
 
3129
        res = {}
 
3130
 
 
3131
        translation_obj = self.pool.get('ir.translation')
 
3132
        for parent in self._inherits:
 
3133
            res.update(self.pool.get(parent).fields_get(cr, user, allfields, context))
 
3134
 
 
3135
        for f, field in self._columns.iteritems():
 
3136
            if allfields and f not in allfields:
 
3137
                continue
 
3138
 
 
3139
            res[f] = fields.field_to_dict(self, cr, user, context, field)
 
3140
 
 
3141
            if not write_access:
 
3142
                res[f]['readonly'] = True
 
3143
                res[f]['states'] = {}
 
3144
 
 
3145
            if 'string' in res[f]:
 
3146
                res_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'field', context.get('lang', False) or 'en_US')
 
3147
                if res_trans:
 
3148
                    res[f]['string'] = res_trans
 
3149
            if 'help' in res[f]:
 
3150
                help_trans = translation_obj._get_source(cr, user, self._name + ',' + f, 'help', context.get('lang', False) or 'en_US')
 
3151
                if help_trans:
 
3152
                    res[f]['help'] = help_trans
 
3153
            if 'selection' in res[f]:
 
3154
                if isinstance(field.selection, (tuple, list)):
 
3155
                    sel = field.selection
 
3156
                    sel2 = []
 
3157
                    for key, val in sel:
 
3158
                        val2 = None
 
3159
                        if val:
 
3160
                            val2 = translation_obj._get_source(cr, user, self._name + ',' + f, 'selection', context.get('lang', False) or 'en_US', val)
 
3161
                        sel2.append((key, val2 or val))
 
3162
                    res[f]['selection'] = sel2
 
3163
 
 
3164
        return res
3482
3165
 
3483
3166
    def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
 
3167
        """ Read records with given ids with the given fields
 
3168
 
 
3169
        :param cr: database cursor
 
3170
        :param user: current user id
 
3171
        :param ids: id or list of the ids of the records to read
 
3172
        :param fields: optional list of field names to return (default: all fields would be returned)
 
3173
        :type fields: list (example ['field_name_1', ...])
 
3174
        :param context: optional context dictionary - it may contains keys for specifying certain options
 
3175
                        like ``context_lang``, ``context_tz`` to alter the results of the call.
 
3176
                        A special ``bin_size`` boolean flag may also be passed in the context to request the
 
3177
                        value of all fields.binary columns to be returned as the size of the binary instead of its
 
3178
                        contents. This can also be selectively overriden by passing a field-specific flag
 
3179
                        in the form ``bin_size_XXX: True/False`` where ``XXX`` is the name of the field.
 
3180
                        Note: The ``bin_size_XXX`` form is new in OpenERP v6.0.
 
3181
        :return: list of dictionaries((dictionary per record asked)) with requested field values
 
3182
        :rtype: [{‘name_of_the_field’: value, ...}, ...]
 
3183
        :raise AccessError: * if user has no read rights on the requested object
 
3184
                            * if user tries to bypass access rules for read on the requested object
 
3185
 
 
3186
        """
 
3187
 
3484
3188
        if not context:
3485
3189
            context = {}
3486
3190
        self.pool.get('ir.model.access').check(cr, user, self._name, 'read')
3657
3361
                            vals[field] = False
3658
3362
        return res
3659
3363
 
 
3364
    # TODO check READ access
3660
3365
    def perm_read(self, cr, user, ids, context=None, details=True):
3661
3366
        """
3662
3367
        Returns some metadata about the given records.
3734
3439
           :raise except_orm: * if current ir.rules do not permit this operation.
3735
3440
           :return: None if the operation is allowed
3736
3441
        """
3737
 
        where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
3738
 
        if where_clause:
3739
 
            where_clause = ' and ' + ' and '.join(where_clause)
3740
 
            for sub_ids in cr.split_for_in_conditions(ids):
3741
 
                cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
3742
 
                           ' WHERE ' + self._table + '.id IN %s' + where_clause,
3743
 
                           [sub_ids] + where_params)
3744
 
                if cr.rowcount != len(sub_ids):
3745
 
                    raise except_orm(_('AccessError'),
3746
 
                                     _('Operation prohibited by access rules, or performed on an already deleted document (Operation: %s, Document type: %s).')
3747
 
                                     % (operation, self._description))
 
3442
        if uid == SUPERUSER_ID:
 
3443
            return
 
3444
 
 
3445
        if self.is_transient:
 
3446
            # Only one single implicit access rule for transient models: owner only!
 
3447
            # This is ok to hardcode because we assert that TransientModels always
 
3448
            # have log_access enabled and this the create_uid column is always there.
 
3449
            # And even with _inherits, these fields are always present in the local
 
3450
            # table too, so no need for JOINs.
 
3451
            cr.execute("""SELECT distinct create_uid
 
3452
                          FROM %s
 
3453
                          WHERE id IN %%s""" % self._table, (tuple(ids),))
 
3454
            uids = [x[0] for x in cr.fetchall()]
 
3455
            if len(uids) != 1 or uids[0] != uid:
 
3456
                raise orm.except_orm(_('AccessError'), '%s access is '
 
3457
                    'restricted to your own records for transient models '
 
3458
                    '(except for the super-user).' % operation.capitalize())
 
3459
        else:
 
3460
            where_clause, where_params, tables = self.pool.get('ir.rule').domain_get(cr, uid, self._name, operation, context=context)
 
3461
            if where_clause:
 
3462
                where_clause = ' and ' + ' and '.join(where_clause)
 
3463
                for sub_ids in cr.split_for_in_conditions(ids):
 
3464
                    cr.execute('SELECT ' + self._table + '.id FROM ' + ','.join(tables) +
 
3465
                               ' WHERE ' + self._table + '.id IN %s' + where_clause,
 
3466
                               [sub_ids] + where_params)
 
3467
                    if cr.rowcount != len(sub_ids):
 
3468
                        raise except_orm(_('AccessError'),
 
3469
                                         _('Operation prohibited by access rules, or performed on an already deleted document (Operation: %s, Document type: %s).')
 
3470
                                         % (operation, self._description))
3748
3471
 
3749
3472
    def unlink(self, cr, uid, ids, context=None):
3750
3473
        """
3795
3518
            # Note: following steps performed as admin to avoid access rights restrictions, and with no context
3796
3519
            #       to avoid possible side-effects during admin calls.
3797
3520
            # Step 1. Calling unlink of ir_model_data only for the affected IDS
3798
 
            reference_ids = pool_model_data.search(cr, ROOT_USER_ID, [('res_id','in',list(sub_ids)),('model','=',self._name)])
 
3521
            reference_ids = pool_model_data.search(cr, SUPERUSER_ID, [('res_id','in',list(sub_ids)),('model','=',self._name)])
3799
3522
            # Step 2. Marching towards the real deletion of referenced records
3800
3523
            if reference_ids:
3801
 
                pool_model_data.unlink(cr, ROOT_USER_ID, reference_ids)
 
3524
                pool_model_data.unlink(cr, SUPERUSER_ID, reference_ids)
3802
3525
 
3803
3526
            # For the same reason, removing the record relevant to ir_values
3804
3527
            ir_value_ids = ir_values_obj.search(cr, uid,
4115
3838
        """
4116
3839
        if not context:
4117
3840
            context = {}
 
3841
 
 
3842
        if self.is_transient():
 
3843
            self._transient_vacuum(cr, user)
 
3844
 
4118
3845
        self.pool.get('ir.model.access').check(cr, user, self._name, 'create')
4119
3846
 
4120
3847
        vals = self._add_missing_default_values(cr, user, vals, context)
4268
3995
        wf_service.trg_create(user, self._name, id_new, cr)
4269
3996
        return id_new
4270
3997
 
 
3998
    def browse(self, cr, uid, select, context=None, list_class=None, fields_process=None):
 
3999
        """Fetch records as objects allowing to use dot notation to browse fields and relations
 
4000
 
 
4001
        :param cr: database cursor
 
4002
        :param user: current user id
 
4003
        :param select: id or list of ids.
 
4004
        :param context: context arguments, like lang, time zone
 
4005
        :rtype: object or list of objects requested
 
4006
 
 
4007
        """
 
4008
        self._list_class = list_class or browse_record_list
 
4009
        cache = {}
 
4010
        # need to accepts ints and longs because ids coming from a method
 
4011
        # launched by button in the interface have a type long...
 
4012
        if isinstance(select, (int, long)):
 
4013
            return browse_record(cr, uid, select, self, cache, context=context, list_class=self._list_class, fields_process=fields_process)
 
4014
        elif isinstance(select, list):
 
4015
            return self._list_class([browse_record(cr, uid, id, self, cache, context=context, list_class=self._list_class, fields_process=fields_process) for id in select], context=context)
 
4016
        else:
 
4017
            return browse_null()
 
4018
 
4271
4019
    def _store_get_values(self, cr, uid, ids, fields, context):
4272
4020
        """Returns an ordered list of fields.functions to call due to
4273
4021
           an update operation on ``fields`` of records with ``ids``,
4290
4038
        mapping = {}
4291
4039
        for function in to_compute:
4292
4040
            # use admin user for accessing objects having rules defined on store fields
4293
 
            target_ids = [id for id in function[id_mapping_fnct_](self, cr, ROOT_USER_ID, ids, context) if id]
 
4041
            target_ids = [id for id in function[id_mapping_fnct_](self, cr, SUPERUSER_ID, ids, context) if id]
4294
4042
 
4295
4043
            # the compound key must consider the priority and model name
4296
4044
            key = (function[priority_], function[model_name_])
4357
4105
            val = todo[key]
4358
4106
            if key:
4359
4107
                # use admin user for accessing objects having rules defined on store fields
4360
 
                result = self._columns[val[0]].get(cr, self, ids, val, ROOT_USER_ID, context=context)
 
4108
                result = self._columns[val[0]].get(cr, self, ids, val, SUPERUSER_ID, context=context)
4361
4109
                for id, value in result.items():
4362
4110
                    if field_flag:
4363
4111
                        for f in value.keys():
4383
4131
            else:
4384
4132
                for f in val:
4385
4133
                    # use admin user for accessing objects having rules defined on store fields
4386
 
                    result = self._columns[f].get(cr, self, ids, f, ROOT_USER_ID, context=context)
 
4134
                    result = self._columns[f].get(cr, self, ids, f, SUPERUSER_ID, context=context)
4387
4135
                    for r in result.keys():
4388
4136
                        if field_flag:
4389
4137
                            if r in field_dict.keys():
4432
4180
                domain = [('active', '=', 1)]
4433
4181
 
4434
4182
        if domain:
4435
 
            import expression
4436
4183
            e = expression.expression(cr, user, domain, self, context)
4437
4184
            tables = e.get_tables()
4438
4185
            where_clause, where_params = e.to_sql()
4580
4327
            context = {}
4581
4328
        self.pool.get('ir.model.access').check(cr, access_rights_uid or user, self._name, 'read')
4582
4329
 
 
4330
        # For transient models, restrict acces to the current user, except for the super-user
 
4331
        if self.is_transient() and self._log_access and user != SUPERUSER_ID:
 
4332
            args = expression.AND(([('create_uid', '=', user)], args or []))
 
4333
 
4583
4334
        query = self._where_calc(cr, user, args, context=context)
4584
4335
        self._apply_ir_rules(cr, user, query, 'read', context=context)
4585
4336
        order_by = self._generate_order_by(order, query)
4769
4520
        return new_id
4770
4521
 
4771
4522
    def exists(self, cr, uid, ids, context=None):
 
4523
        """Checks whether the given id or ids exist in this model,
 
4524
           and return the list of ids that do. This is simple to use for
 
4525
           a truth test on a browse_record::
 
4526
 
 
4527
               if record.exists():
 
4528
                   pass
 
4529
 
 
4530
           :param ids: id or list of ids to check for existence
 
4531
           :type ids: int or [int]
 
4532
           :return: the list of ids that currently exist, out of
 
4533
                    the given `ids`
 
4534
        """
4772
4535
        if type(ids) in (int, long):
4773
4536
            ids = [ids]
4774
4537
        query = 'SELECT id FROM "%s"' % (self._table)
4853
4616
                results[k] = ''
4854
4617
        return results
4855
4618
 
4856
 
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
4857
 
 
 
4619
    # Transience
 
4620
    def is_transient(self):
 
4621
        """ Return whether the model is transient.
 
4622
 
 
4623
        See TransientModel.
 
4624
 
 
4625
        """
 
4626
        return self._transient
 
4627
 
 
4628
    def _transient_clean_rows_older_than(self, cr, seconds):
 
4629
        assert self._transient, "Model %s is not transient, it cannot be vacuumed!" % self._name
 
4630
        cr.execute("SELECT id FROM " + self._table + " WHERE"
 
4631
            " COALESCE(write_date, create_date, now())::timestamp <"
 
4632
            " (now() - interval %s)", ("%s seconds" % seconds,))
 
4633
        ids = [x[0] for x in cr.fetchall()]
 
4634
        self.unlink(cr, SUPERUSER_ID, ids)
 
4635
 
 
4636
    def _transient_clean_old_rows(self, cr, count):
 
4637
        assert self._transient, "Model %s is not transient, it cannot be vacuumed!" % self._name
 
4638
        cr.execute(
 
4639
            "SELECT id, COALESCE(write_date, create_date, now())::timestamp"
 
4640
            " AS t FROM " + self._table +
 
4641
            " ORDER BY t LIMIT %s", (count,))
 
4642
        ids = [x[0] for x in cr.fetchall()]
 
4643
        self.unlink(cr, SUPERUSER_ID, ids)
 
4644
 
 
4645
    def _transient_vacuum(self, cr, uid, force=False):
 
4646
        """Clean the transient records.
 
4647
 
 
4648
        This unlinks old records from the transient model tables whenever the
 
4649
        "_transient_max_count" or "_max_age" conditions (if any) are reached.
 
4650
        Actual cleaning will happen only once every "_transient_check_time" calls.
 
4651
        This means this method can be called frequently called (e.g. whenever
 
4652
        a new record is created).
 
4653
        """
 
4654
        assert self._transient, "Model %s is not transient, it cannot be vacuumed!" % self._name
 
4655
        self._transient_check_count += 1
 
4656
        if (not force) and (self._transient_check_count % self._transient_check_time):
 
4657
            self._transient_check_count = 0
 
4658
            return True
 
4659
 
 
4660
        # Age-based expiration
 
4661
        if self._transient_max_hours:
 
4662
            self._transient_clean_rows_older_than(cr, self._transient_max_hours * 60 * 60)
 
4663
 
 
4664
        # Count-based expiration
 
4665
        if self._transient_max_count:
 
4666
            self._transient_clean_old_rows(cr, self._transient_max_count)
 
4667
 
 
4668
        return True
 
4669
 
 
4670
# keep this import here, at top it will cause dependency cycle errors
 
4671
import expression
 
4672
 
 
4673
class Model(BaseModel):
 
4674
    """Main super-class for regular database-persisted OpenERP models.
 
4675
 
 
4676
    OpenERP models are created by inheriting from this class::
 
4677
 
 
4678
        class user(Model):
 
4679
            ...
 
4680
 
 
4681
    The system will later instantiate the class once per database (on
 
4682
    which the class' module is installed).
 
4683
    """
 
4684
    _register = False # not visible in ORM registry, meant to be python-inherited only
 
4685
    _transient = False # True in a TransientModel
 
4686
 
 
4687
class TransientModel(BaseModel):
 
4688
    """Model super-class for transient records, meant to be temporarily
 
4689
       persisted, and regularly vaccuum-cleaned.
 
4690
 
 
4691
       A TransientModel has a simplified access rights management,
 
4692
       all users can create new records, and may only access the
 
4693
       records they created. The super-user has unrestricted access
 
4694
       to all TransientModel records.
 
4695
    """
 
4696
    _register = False # not visible in ORM registry, meant to be python-inherited only
 
4697
    _transient = True
 
4698
 
 
4699
class AbstractModel(BaseModel):
 
4700
    """Abstract Model super-class for creating an abstract class meant to be
 
4701
       inherited by regular models (Models or TransientModels) but not meant to
 
4702
       be usable on its own, or persisted.
 
4703
 
 
4704
       Technical note: we don't want to make AbstractModel the super-class of
 
4705
       Model or BaseModel because it would not make sense to put the main
 
4706
       definition of persistence methods such as create() in it, and still we
 
4707
       should be able to override them within an AbstractModel.
 
4708
       """
 
4709
    _auto = False # don't create any database backend for AbstractModels
 
4710
    _register = False # not visible in ORM registry, meant to be python-inherited only
 
4711
 
 
4712
 
 
4713
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
 
 
b'\\ No newline at end of file'