1 parent
e0f11efc94
commit
0dffe1087c
4 files changed
+104
-1
No files matched your search
@@ -1,3 +1,4 @@
|
||||
* Add fmany2one field
|
||||
* Allow creation of foreign key on non primary key
|
||||
* Include context information in access error
|
||||
* Remove trytond-worker max tasks per child option for Python <= 3.10
|
||||
|
||||
@@ -1174,3 +1174,17 @@ Instance methods:
|
||||
following ``type_``.
|
||||
The descriptor must be used on the same class as the field.
|
||||
Default ``type_`` is ``values``.
|
||||
|
||||
fmany2one
|
||||
---------
|
||||
|
||||
.. function:: fmany2one(name, sources, target[, string[, ondelete[, \**options]]])
|
||||
|
||||
Return a mixin_ class which adds a :class:`Function` field to a
|
||||
:class:`~trytond.model.ModelSQL` class that emulate a :class:`Many2One` that
|
||||
refers to the ``target`` record having the ``sources`` as value.
|
||||
``target`` is composed of the name of the model and the name of the fields
|
||||
separated by a comma.
|
||||
The target fields must be unique.
|
||||
|
||||
.. _mixin: http://en.wikipedia.org/wiki/Mixin
|
||||
@@ -10,6 +10,7 @@ from .field import (
|
||||
SQL_OPERATORS, Field, context_validate, depends, domain_validate,
|
||||
get_eval_fields, on_change_result, states_validate)
|
||||
from .float import Float
|
||||
from .fmany2one import fmany2one
|
||||
from .function import Function, MultiValue
|
||||
from .integer import Integer
|
||||
from .many2many import Many2Many
|
||||
@@ -28,4 +29,4 @@ __all__ = [
|
||||
Boolean, Integer, Char, Text, FullText, Float, Numeric, Date,
|
||||
Timestamp, DateTime, Time, TimeDelta, Binary, Selection, Reference,
|
||||
Many2One, One2Many, Many2Many, Function, MultiValue, One2One, Dict,
|
||||
MultiSelection]
|
||||
MultiSelection, fmany2one]
|
||||
@@ -0,0 +1,87 @@
|
||||
# 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 sql import Column, Literal
|
||||
|
||||
from trytond.pool import Pool
|
||||
from trytond.tools import grouped_slice
|
||||
|
||||
from .function import Function
|
||||
from .many2one import Many2One
|
||||
|
||||
|
||||
def fmany2one(
|
||||
name, sources, target, string="", ondelete='SET NULL', **kwargs):
|
||||
sources = sources.split(',')
|
||||
target_model, target_fields = target.split(',', 1)
|
||||
target_fields = target_fields.split(',')
|
||||
assert len(sources) == len(target_fields)
|
||||
|
||||
class Mixin:
|
||||
__slots__ = ()
|
||||
|
||||
@classmethod
|
||||
def __register__(cls, module):
|
||||
pool = Pool()
|
||||
Target = pool.get(target_model)
|
||||
table_h = cls.__table_handler__(module)
|
||||
super().__register__(module)
|
||||
table_h.add_fk(
|
||||
sources, Target._table, target_fields,
|
||||
on_delete=getattr(cls, name).ondelete)
|
||||
|
||||
@classmethod
|
||||
def getter(cls, records, name):
|
||||
pool = Pool()
|
||||
Target = pool.get(target_model)
|
||||
values = set(filter(all,
|
||||
(tuple(getattr(r, s) for s in sources) for r in records)))
|
||||
values2id = {}
|
||||
for sub_values in grouped_slice(values):
|
||||
domain = ['OR']
|
||||
for values in sub_values:
|
||||
domain.append(
|
||||
[(f, '=', v) for f, v in zip(target_fields, values)])
|
||||
targets = Target.search(domain)
|
||||
values2id.update(
|
||||
(tuple(getattr(t, f) for f in target_fields), t.id)
|
||||
for t in targets)
|
||||
return {r.id: values2id.get(
|
||||
tuple(getattr(r, s) for s in sources)) for r in records}
|
||||
|
||||
@classmethod
|
||||
def setter(cls, records, name, value):
|
||||
pool = Pool()
|
||||
Target = pool.get(target_model)
|
||||
if value:
|
||||
value = getattr(Target(value), target_fields[0])
|
||||
else:
|
||||
value = None
|
||||
cls.write(records, {sources[0]: value})
|
||||
|
||||
@classmethod
|
||||
def searcher(cls, clause, tables):
|
||||
pool = Pool()
|
||||
Target = pool.get(target_model)
|
||||
table, _ = tables[None]
|
||||
if name not in tables:
|
||||
target = Target.__table__()
|
||||
join = Literal(True)
|
||||
for source, target_field in zip(sources, target_fields):
|
||||
join &= Column(table, source) == Column(target, target_field)
|
||||
tables[name] = {
|
||||
None: (target, join),
|
||||
}
|
||||
nested = clause[0][len(name) + 1:]
|
||||
domain = [(nested or 'rec_name', *clause[1:])]
|
||||
tables, clause = Target.search_domain(
|
||||
domain, tables=tables[name])
|
||||
return clause
|
||||
|
||||
setattr(Mixin, name, Function(
|
||||
Many2One(target_model, string, ondelete=ondelete, **kwargs),
|
||||
f'get_{name}', setter=f'set_{name}'))
|
||||
setattr(Mixin, f'get_{name}', getter)
|
||||
setattr(Mixin, f'set_{name}', setter)
|
||||
setattr(Mixin, f'domain_{name}', searcher)
|
||||
return Mixin
|
||||
Reference in new issue
Block a user