Add fmany2one field

Closes #13051
This commit is contained in:
Cédric Krier committed 2024-02-22 17:36:51 +01:00
1 parent e0f11efc94
commit 0dffe1087c
4 files changed
+104 -1

No files matched your search

+1
View File
@@ -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
+14
View File
@@ -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
+2 -1
View File
@@ -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]
+87
View File
@@ -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