Use Constraint instance in ModelSQL._sql_constraints
issue4814 review15491002
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
* Use Constraint instance in ModelSQL._sql_constraints
|
||||
* Only return default record if no domain supplied on ModelSingleton search
|
||||
* Rule.domain_get returns a domain and Rule.query_get a SQL query
|
||||
* Add target_search option to Many2One
|
||||
|
||||
@@ -400,11 +400,11 @@ Class attributes are:
|
||||
|
||||
A list of SQL constraints that are added on the table:
|
||||
|
||||
[ ('constraint name', 'SQL constraint', 'error message key'), ... ]
|
||||
[ ('constraint name', constraint, 'error message key'), ... ]
|
||||
|
||||
- `constraint name` is the name of the SQL constraint in the database
|
||||
|
||||
- `SQL constraint` is the actual SQL constraint
|
||||
- constraint is an instance of :class:`Constraint`
|
||||
|
||||
- `error message key` is the key of
|
||||
:attr:`_sql_error_messages`
|
||||
@@ -471,6 +471,48 @@ Class methods:
|
||||
},
|
||||
}
|
||||
|
||||
Constraint
|
||||
==========
|
||||
|
||||
.. class:: Constraint(table)
|
||||
|
||||
It represents a SQL constraint on a table of the database and it follows the
|
||||
API of the python-sql expression.
|
||||
|
||||
Instance attributes:
|
||||
|
||||
.. attribute:: Constraint.table
|
||||
|
||||
The SQL Table on which the constraint is defined.
|
||||
|
||||
Check
|
||||
-----
|
||||
|
||||
.. class:: Check(table, expression)
|
||||
|
||||
It represents a check :class:`Constraint` which enforce the validity of the
|
||||
expression.
|
||||
|
||||
Instance attributes:
|
||||
|
||||
.. attribute:: Check.expression
|
||||
|
||||
The SQL expression to check.
|
||||
|
||||
Unique
|
||||
------
|
||||
|
||||
.. class:: Unique(table, \*columns)
|
||||
|
||||
It represents a unique :class:`Constraint` which enforce the uniqeness of the
|
||||
group of columns with respect to all the rows in the table.
|
||||
|
||||
Instance attributes:
|
||||
|
||||
.. attribute:: Unique.columns
|
||||
|
||||
The tuple of SQL Column instances.
|
||||
|
||||
========
|
||||
Workflow
|
||||
========
|
||||
|
||||
@@ -329,7 +329,7 @@ class TableHandler(TableHandlerInterface):
|
||||
try:
|
||||
self.cursor.execute('ALTER TABLE `%s` '
|
||||
'ADD CONSTRAINT `%s` %s'
|
||||
% (self.table_name, ident, constraint,))
|
||||
% (self.table_name, ident, constraint), constraint.params)
|
||||
except Exception:
|
||||
if exception:
|
||||
raise
|
||||
|
||||
@@ -382,7 +382,7 @@ class TableHandler(TableHandlerInterface):
|
||||
try:
|
||||
self.cursor.execute('ALTER TABLE "%s" '
|
||||
'ADD CONSTRAINT "%s" %s'
|
||||
% (self.table_name, ident, constraint))
|
||||
% (self.table_name, ident, constraint), constraint.params)
|
||||
except Exception:
|
||||
if exception:
|
||||
raise
|
||||
|
||||
@@ -5,7 +5,7 @@ import hashlib
|
||||
from sql.operators import Concat
|
||||
from sql.conditionals import Coalesce
|
||||
|
||||
from ..model import ModelView, ModelSQL, fields
|
||||
from ..model import ModelView, ModelSQL, fields, Unique
|
||||
from ..config import config
|
||||
from .. import backend
|
||||
from ..transaction import Transaction
|
||||
@@ -56,8 +56,11 @@ class Attachment(ModelSQL, ModelView):
|
||||
def __setup__(cls):
|
||||
super(Attachment, cls).__setup__()
|
||||
cls._order.insert(0, ('last_modification', 'DESC'))
|
||||
|
||||
table = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('resource_name', 'UNIQUE(resource, name)',
|
||||
('resource_name',
|
||||
Unique(table, table.resource, table.name),
|
||||
'The names of attachments must be unique by resource!'),
|
||||
]
|
||||
|
||||
|
||||
+4
-2
@@ -4,7 +4,7 @@ import datetime
|
||||
import warnings
|
||||
from ast import literal_eval
|
||||
|
||||
from ..model import ModelView, ModelSQL, fields
|
||||
from ..model import ModelView, ModelSQL, fields, Check
|
||||
from ..cache import Cache
|
||||
from ..tools import datetime_strftime
|
||||
from ..transaction import Transaction
|
||||
@@ -49,9 +49,11 @@ class Lang(ModelSQL, ModelView):
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(Lang, cls).__setup__()
|
||||
|
||||
table = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('check_decimal_point_thousands_sep',
|
||||
'CHECK(decimal_point != thousands_sep)',
|
||||
Check(table, table.decimal_point != table.thousands_sep),
|
||||
'decimal_point and thousands_sep must be different!'),
|
||||
]
|
||||
cls._error_messages.update({
|
||||
|
||||
+10
-5
@@ -12,7 +12,7 @@ try:
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
from ..model import ModelView, ModelSQL, fields
|
||||
from ..model import ModelView, ModelSQL, fields, Unique
|
||||
from ..report import Report
|
||||
from ..wizard import Wizard, StateView, StateAction, Button
|
||||
from ..transaction import Transaction
|
||||
@@ -64,8 +64,9 @@ class Model(ModelSQL, ModelView):
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(Model, cls).__setup__()
|
||||
table = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('model_uniq', 'UNIQUE(model)',
|
||||
('model_uniq', Unique(table, table.model),
|
||||
'The model must be unique!'),
|
||||
]
|
||||
cls._error_messages.update({
|
||||
@@ -249,8 +250,9 @@ class ModelField(ModelSQL, ModelView):
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(ModelField, cls).__setup__()
|
||||
table = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('name_model_uniq', 'UNIQUE(name, model)',
|
||||
('name_model_uniq', Unique(table, table.name, table.model),
|
||||
'The field name in model must be unique!'),
|
||||
]
|
||||
cls._error_messages.update({
|
||||
@@ -770,8 +772,9 @@ class ModelButton(ModelSQL, ModelView):
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(ModelButton, cls).__setup__()
|
||||
table = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('name_model_uniq', 'UNIQUE(name, model)',
|
||||
('name_model_uniq', Unique(table, table.name, table.model),
|
||||
'The button name in model must be unique!'),
|
||||
]
|
||||
cls._order.insert(0, ('model', 'ASC'))
|
||||
@@ -838,8 +841,10 @@ class ModelData(ModelSQL, ModelView):
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(ModelData, cls).__setup__()
|
||||
table = cls.__table__()
|
||||
cls._sql_constraints = [
|
||||
('fs_id_module_model_uniq', 'UNIQUE("fs_id", "module", "model")',
|
||||
('fs_id_module_model_uniq',
|
||||
Unique(table, table.fs_id, table.module, table.model),
|
||||
'The triple (fs_id, module, model) must be unique!'),
|
||||
]
|
||||
cls._buttons.update({
|
||||
|
||||
@@ -4,7 +4,7 @@ from functools import wraps
|
||||
|
||||
from sql.operators import NotIn
|
||||
|
||||
from trytond.model import ModelView, ModelSQL, fields
|
||||
from trytond.model import ModelView, ModelSQL, fields, Unique
|
||||
from trytond.modules import create_graph, get_module_list, get_module_info
|
||||
from trytond.wizard import Wizard, StateView, Button, StateTransition, \
|
||||
StateAction
|
||||
@@ -55,8 +55,9 @@ class Module(ModelSQL, ModelView):
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(Module, cls).__setup__()
|
||||
table = cls.__table__()
|
||||
cls._sql_constraints = [
|
||||
('name_uniq', 'unique (name)',
|
||||
('name_uniq', Unique(table, table.name),
|
||||
'The name of the module must be unique!'),
|
||||
]
|
||||
cls._order.insert(0, ('name', 'ASC'))
|
||||
@@ -341,8 +342,9 @@ class ModuleDependency(ModelSQL, ModelView):
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(ModuleDependency, cls).__setup__()
|
||||
table = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('name_module_uniq', 'UNIQUE(name, module)',
|
||||
('name_module_uniq', Unique(table, table.name, table.module),
|
||||
'Dependency must be unique by module!'),
|
||||
]
|
||||
|
||||
|
||||
+6
-3
@@ -1,6 +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 ..model import ModelView, ModelSQL, fields, EvalEnvironment
|
||||
from ..model import ModelView, ModelSQL, fields, EvalEnvironment, Check
|
||||
from ..transaction import Transaction
|
||||
from ..cache import Cache
|
||||
from ..pool import Pool
|
||||
@@ -40,10 +40,13 @@ class RuleGroup(ModelSQL, ModelView):
|
||||
cls._order.insert(0, ('model', 'ASC'))
|
||||
cls._order.insert(1, ('global_p', 'ASC'))
|
||||
cls._order.insert(2, ('default_p', 'ASC'))
|
||||
|
||||
t = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('global_default_exclusive', 'CHECK(NOT(global_p AND default_p))',
|
||||
('global_default_exclusive',
|
||||
Check(t, (t.global_p == False) | (t.default_p == False)),
|
||||
'Global and Default are mutually exclusive!'),
|
||||
]
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def default_global_p():
|
||||
|
||||
@@ -5,7 +5,7 @@ import time
|
||||
from itertools import izip
|
||||
from sql import Flavor
|
||||
|
||||
from ..model import ModelView, ModelSQL, fields
|
||||
from ..model import ModelView, ModelSQL, fields, Check
|
||||
from ..tools import datetime_strftime
|
||||
from ..pyson import Eval, And
|
||||
from ..transaction import Transaction
|
||||
@@ -84,8 +84,10 @@ class Sequence(ModelSQL, ModelView):
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(Sequence, cls).__setup__()
|
||||
table = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('check_timestamp_rounding', 'CHECK(timestamp_rounding > 0)',
|
||||
('check_timestamp_rounding',
|
||||
Check(table, table.timestamp_rounding > 0),
|
||||
'Timestamp rounding should be greater than 0'),
|
||||
]
|
||||
cls._error_messages.update({
|
||||
|
||||
@@ -19,7 +19,7 @@ from sql.conditionals import Case
|
||||
from sql.operators import Or, And
|
||||
from sql.aggregate import Max
|
||||
|
||||
from ..model import ModelView, ModelSQL, fields
|
||||
from ..model import ModelView, ModelSQL, fields, Unique
|
||||
from ..wizard import Wizard, StateView, StateTransition, StateAction, \
|
||||
Button
|
||||
from ..tools import file_open, reduce_ids, grouped_slice
|
||||
@@ -80,9 +80,11 @@ class Translation(ModelSQL, ModelView):
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(Translation, cls).__setup__()
|
||||
t = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('translation_md5_uniq',
|
||||
'UNIQUE (name, res_id, lang, type, src_md5, module)',
|
||||
Unique(t,
|
||||
t.name, t.res_id, t.lang, t.type, t.src_md5, t.module),
|
||||
'Translation must be unique'),
|
||||
]
|
||||
cls._error_messages.update({
|
||||
|
||||
@@ -5,7 +5,7 @@ import time
|
||||
from sql import Literal, Null
|
||||
from sql.aggregate import Count, Max
|
||||
|
||||
from ..model import ModelView, ModelSQL, fields, EvalEnvironment
|
||||
from ..model import ModelView, ModelSQL, fields, EvalEnvironment, Check
|
||||
from ..pyson import Eval, PYSONDecoder
|
||||
from ..tools import grouped_slice
|
||||
from .. import backend
|
||||
@@ -56,9 +56,13 @@ class Trigger(ModelSQL, ModelView):
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(Trigger, cls).__setup__()
|
||||
t = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('on_exclusive',
|
||||
'CHECK(NOT(on_time AND (on_create OR on_write OR on_delete)))',
|
||||
Check(t, ~((t.on_time == True)
|
||||
& ((t.on_create == True)
|
||||
| (t.on_write == True)
|
||||
| (t.on_delete == True)))),
|
||||
'"On Time" and others are mutually exclusive!'),
|
||||
]
|
||||
cls._error_messages.update({
|
||||
|
||||
@@ -4,7 +4,7 @@ from .model import Model
|
||||
from .modelview import ModelView
|
||||
from .modelstorage import ModelStorage, EvalEnvironment
|
||||
from .modelsingleton import ModelSingleton
|
||||
from .modelsql import ModelSQL
|
||||
from .modelsql import ModelSQL, Check, Unique
|
||||
from .workflow import Workflow
|
||||
from .dictschema import DictSchemaMixin
|
||||
from .match import MatchMixin
|
||||
|
||||
+80
-34
@@ -1,12 +1,10 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
import re
|
||||
import datetime
|
||||
from functools import reduce
|
||||
from itertools import islice, izip, chain, ifilter
|
||||
from collections import OrderedDict
|
||||
|
||||
from sql import Table, Column, Literal, Desc, Asc, Expression, Flavor, Null
|
||||
from sql import Table, Column, Literal, Desc, Asc, Expression, Null
|
||||
from sql.functions import Now, Extract
|
||||
from sql.conditionals import Coalesce
|
||||
from sql.operators import Or, And, Operator
|
||||
@@ -25,8 +23,67 @@ from trytond.rpc import RPC
|
||||
|
||||
from .modelstorage import cache_size
|
||||
|
||||
_RE_UNIQUE = re.compile('UNIQUE\s*\((.*)\)', re.I)
|
||||
_RE_CHECK = re.compile('CHECK\s*\((.*)\)', re.I)
|
||||
|
||||
class Constraint(object):
|
||||
__slots__ = ('_table',)
|
||||
|
||||
def __init__(self, table):
|
||||
assert isinstance(table, Table)
|
||||
self._table = table
|
||||
|
||||
@property
|
||||
def table(self):
|
||||
return self._table
|
||||
|
||||
def __str__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def params(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Check(Constraint):
|
||||
__slots__ = ('_expression',)
|
||||
|
||||
def __init__(self, table, expression):
|
||||
super(Check, self).__init__(table)
|
||||
assert isinstance(expression, Expression)
|
||||
self._expression = expression
|
||||
|
||||
@property
|
||||
def expression(self):
|
||||
return self._expression
|
||||
|
||||
def __str__(self):
|
||||
return 'CHECK(%s)' % self.expression
|
||||
|
||||
@property
|
||||
def params(self):
|
||||
return self.expression.params
|
||||
|
||||
|
||||
class Unique(Constraint):
|
||||
__slots__ = ('_columns',)
|
||||
|
||||
def __init__(self, table, *columns):
|
||||
super(Unique, self).__init__(table)
|
||||
assert all(isinstance(col, Column) for col in columns)
|
||||
self._columns = tuple(columns)
|
||||
|
||||
@property
|
||||
def columns(self):
|
||||
return self._columns
|
||||
|
||||
def __str__(self):
|
||||
return 'UNIQUE(%s)' % (', '.join(map(str, self.columns)))
|
||||
|
||||
@property
|
||||
def params(self):
|
||||
p = []
|
||||
for column in self.columns:
|
||||
p.extend(column.params)
|
||||
return tuple(p)
|
||||
|
||||
|
||||
class ModelSQL(ModelStorage):
|
||||
@@ -1310,47 +1367,36 @@ class ModelSQL(ModelStorage):
|
||||
return
|
||||
# Works only for a single transaction
|
||||
ids = map(int, records)
|
||||
table = cls.__table__()
|
||||
param = Flavor.get().param
|
||||
for _, sql, error in cls._sql_constraints:
|
||||
match = _RE_UNIQUE.match(sql)
|
||||
if match:
|
||||
sql = match.group(1)
|
||||
columns = sql.split(',')
|
||||
sql_clause = ' AND '.join('%s = %s'
|
||||
% (i, param) for i in columns)
|
||||
sql_clause = '(id != ' + param + ' AND ' + sql_clause + ')'
|
||||
|
||||
table = sql.table
|
||||
if isinstance(sql, Unique):
|
||||
columns = [Column(table, c.name) for c in sql.columns]
|
||||
columns.insert(0, table.id)
|
||||
in_max = cursor.IN_MAX / (len(columns) + 1)
|
||||
for sub_ids in grouped_slice(ids, in_max):
|
||||
red_sql = reduce_ids(table.id, sub_ids)
|
||||
|
||||
cursor.execute('SELECT id,' + sql + ' '
|
||||
'FROM "' + cls._table + '" '
|
||||
'WHERE ' + str(red_sql), red_sql.params)
|
||||
|
||||
fetchall = cursor.fetchall()
|
||||
cursor.execute('SELECT id '
|
||||
'FROM "' + cls._table + '" '
|
||||
'WHERE ' +
|
||||
' OR '.join((sql_clause,) * len(fetchall)),
|
||||
reduce(lambda x, y: x + list(y), fetchall, []))
|
||||
cursor.execute(*table.select(*columns, where=red_sql))
|
||||
|
||||
where = Literal(False)
|
||||
for row in cursor.fetchall():
|
||||
clause = table.id != row[0]
|
||||
for column, value in zip(sql.columns, row[1:]):
|
||||
if value is None:
|
||||
# NULL is always unique
|
||||
clause &= Literal(False)
|
||||
clause &= Column(table, column.name) == value
|
||||
where |= clause
|
||||
cursor.execute(*table.select(table.id, where=where))
|
||||
if cursor.fetchone():
|
||||
cls.raise_user_error(error)
|
||||
continue
|
||||
match = _RE_CHECK.match(sql)
|
||||
if match:
|
||||
sql = match.group(1)
|
||||
elif isinstance(sql, Check):
|
||||
for sub_ids in grouped_slice(ids):
|
||||
red_sql = reduce_ids(table.id, sub_ids)
|
||||
cursor.execute('SELECT id '
|
||||
'FROM "' + cls._table + '" '
|
||||
'WHERE NOT (' + sql + ') '
|
||||
'AND ' + str(red_sql), red_sql.params)
|
||||
cursor.execute(*table.select(table.id,
|
||||
where=~sql.expression & red_sql))
|
||||
if cursor.fetchone():
|
||||
cls.raise_user_error(error)
|
||||
continue
|
||||
|
||||
|
||||
def convert_from(table, tables):
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
"Group"
|
||||
from itertools import chain
|
||||
from ..model import ModelView, ModelSQL, fields
|
||||
from ..model import ModelView, ModelSQL, fields, Unique
|
||||
from ..pool import Pool, PoolMeta
|
||||
from ..tools import grouped_slice
|
||||
|
||||
@@ -46,8 +46,9 @@ class Group(ModelSQL, ModelView):
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(Group, cls).__setup__()
|
||||
table = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('name_uniq', 'unique (name)',
|
||||
('name_uniq', Unique(table, table.name),
|
||||
'The name of the group must be unique!')
|
||||
]
|
||||
|
||||
|
||||
+3
-2
@@ -21,7 +21,7 @@ try:
|
||||
except ImportError:
|
||||
bcrypt = None
|
||||
|
||||
from ..model import ModelView, ModelSQL, fields
|
||||
from ..model import ModelView, ModelSQL, fields, Unique
|
||||
from ..wizard import Wizard, StateView, Button, StateTransition
|
||||
from ..tools import grouped_slice
|
||||
from .. import backend
|
||||
@@ -81,8 +81,9 @@ class User(ModelSQL, ModelView):
|
||||
'set_preferences': RPC(readonly=False, check_access=False),
|
||||
'get_preferences_fields_view': RPC(check_access=False),
|
||||
})
|
||||
table = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('login_key', 'UNIQUE (login)',
|
||||
('login_key', Unique(table, table.login),
|
||||
'You can not have two users with the same login!')
|
||||
]
|
||||
cls._preferences_fields = [
|
||||
|
||||
+5
-2
@@ -13,6 +13,8 @@ import time
|
||||
from getpass import getpass
|
||||
import threading
|
||||
|
||||
from sql import Table
|
||||
|
||||
from trytond.config import config, parse_listen
|
||||
from trytond import backend
|
||||
from trytond.pool import Pool
|
||||
@@ -101,8 +103,9 @@ class TrytonServer(object):
|
||||
if not cursor.test():
|
||||
raise Exception("'%s' is not a Tryton database!"
|
||||
% db_name)
|
||||
cursor.execute('SELECT code FROM ir_lang '
|
||||
'WHERE translatable = %s', (True,))
|
||||
lang = Table('ir_lang')
|
||||
cursor.execute(*lang.select(lang.code,
|
||||
where=lang.translatable == True))
|
||||
lang = [x[0] for x in cursor.fetchall()]
|
||||
else:
|
||||
lang = None
|
||||
|
||||
+10
-7
@@ -3,7 +3,7 @@
|
||||
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from trytond.model import ModelSQL, DictSchemaMixin, fields
|
||||
from trytond.model import ModelSQL, DictSchemaMixin, fields, Unique
|
||||
from trytond.pyson import Eval
|
||||
|
||||
__all__ = [
|
||||
@@ -365,10 +365,11 @@ class One2OneRelation(ModelSQL):
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(One2OneRelation, cls).__setup__()
|
||||
table = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('origin_unique', 'UNIQUE(origin)',
|
||||
('origin_unique', Unique(table, table.origin),
|
||||
'Origin must be unique'),
|
||||
('target_unique', 'UNIQUE(target)',
|
||||
('target_unique', Unique(table, table.target),
|
||||
'Target must be unique'),
|
||||
]
|
||||
|
||||
@@ -390,10 +391,11 @@ class One2OneRequiredRelation(ModelSQL):
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(One2OneRequiredRelation, cls).__setup__()
|
||||
table = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('origin_unique', 'UNIQUE(origin)',
|
||||
('origin_unique', Unique(table, table.origin),
|
||||
'Origin must be unique'),
|
||||
('target_unique', 'UNIQUE(target)',
|
||||
('target_unique', Unique(table, table.target),
|
||||
'Target must be unique'),
|
||||
]
|
||||
|
||||
@@ -416,10 +418,11 @@ class One2OneDomainRelation(ModelSQL):
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(One2OneDomainRelation, cls).__setup__()
|
||||
table = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('origin_unique', 'UNIQUE(origin)',
|
||||
('origin_unique', Unique(table, table.origin),
|
||||
'Origin must be unique'),
|
||||
('target_unique', 'UNIQUE(target)',
|
||||
('target_unique', Unique(table, table.target),
|
||||
'Target must be unique'),
|
||||
]
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ from dateutil.relativedelta import relativedelta
|
||||
from sql.functions import Extract
|
||||
from sql.conditionals import Coalesce
|
||||
|
||||
from trytond.model import ModelView, ModelSQL, fields
|
||||
from trytond.model import ModelView, ModelSQL, fields, Unique
|
||||
from trytond.tools import reduce_ids
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.pool import Pool
|
||||
@@ -57,8 +57,9 @@ class Collection(ModelSQL, ModelView):
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(Collection, cls).__setup__()
|
||||
table = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('name_parent_uniq', 'UNIQUE (name, parent)',
|
||||
('name_parent_uniq', Unique(table, table.name, table.parent),
|
||||
'The collection name must be unique inside a collection!'),
|
||||
]
|
||||
cls._error_messages.update({
|
||||
|
||||
Reference in New Issue
Block a user