1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2025-11-30 08:36:53 +01:00
lodel2_mirror/leapi/lerelation.py

281 lines
11 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#-*- coding: utf-8 -*-
import copy
import re
import warnings
import EditorialModel.classtypes
import EditorialModel.fieldtypes.leo as ft_leo
from . import lecrud
from . import leobject
from . import lefactory
## @brief Main class for relations
class _LeRelation(lecrud._LeCrud):
_superior_field_name = None
_subordinate_field_name = None
## @brief Stores the list of fieldtypes that are common to all relations
_rel_fieldtypes = dict()
def __init__(self, id_relation, **kwargs):
super().__init__(id_relation, **kwargs)
## @brief Forge a filter to match the superior
@classmethod
def sup_filter(self, leo):
if isinstance(leo, leobject._LeObject):
return (self._superior_field_name, '=', leo)
## @brief Forge a filter to match the superior
@classmethod
def sub_filter(self, leo):
if isinstance(leo, leobject._LeObject):
return (self._subordinate_field_name, '=', leo)
## @return The name of the uniq id field
@classmethod
def uidname(cls):
return EditorialModel.classtypes.relation_uid
## @return a dict with field name as key and fieldtype instance as value
@classmethod
def fieldtypes(cls):
rel_ft = dict()
rel_ft.update(cls._uid_fieldtype)
rel_ft.update(cls._rel_fieldtypes)
if cls.implements_lerel2type():
rel_ft.update(cls._rel_attr_fieldtypes)
return rel_ft
@classmethod
def _prepare_relational_fields(cls, field):
return lecrud.LeApiQueryError("Relational field '%s' given but %s doesn't is not a LeObject" % (field,
cls.__name__))
## @brief Prepare filters before sending them to the datasource
# @param cls : Concerned class
# @param filters_l list : List of filters
# @return prepared and checked filters
@classmethod
def _prepare_filters(cls, filters_l):
filters, rel_filters = super()._prepare_filters(filters_l)
res_filters = list()
for field, op, value in filters:
if field in [cls._superior_field_name, cls._subordinate_field_name]:
if isinstance(value, str):
try:
value = int(value)
except ValueError as e:
raise LeApiDataCheckError("Wrong value given for '%s'"%field)
if isinstance(value, int):
value = cls.name2class('LeObject')(value)
res_filters.append( (field, op, value) )
return res_filters, rel_filters
@classmethod
## @brief deletes a relation between two objects
# @param filters_list list
# @param target_class str
def delete(cls, filters_list, target_class):
filters, rel_filters = cls._prepare_filters(filters_list)
if isinstance(target_class, str):
target_class = cls.name2class(target_class)
ret = cls._datasource.delete(target_class, filters)
return True if ret == 1 else False
## @brief move to the first rank
# @return True in case of success, False in case of failure
def move_first(self):
return self.set_rank('first')
## @brief move to the last rank
# @return True in case of success, False in case of failure
def move_last(self):
return self.set_rank('last')
## @brief move to the given rank defined by a shift step
# @param step int : The step
# @return True in case of success, False in case of failure
# @throw ValueError if step is not castable into an integer
def shift_rank(self, step):
step = int(step)
return self.set_rank(self.rank + step)
## @brief modify a relation rank
# @param new_rank int|str : The new rank can be an integer > 1 or strings 'first' or 'last'
# @return True in case of success, False in case of failure
# @throw ValueError if step is not castable into an integer
def set_rank(self, new_rank):
raise NotImplemented("Abtract method")
## @brief Implements set_rank
def _set_rank(self, new_rank, **get_max_rank_args):
max_rank = self.get_max_rank(**get_max_rank_args)
try:
new_rank = int(new_rank)
except ValueError:
if new_rank == 'first':
new_rank = 1
elif new_rank == 'last':
new_rank = max_rank
else:
raise ValueError("The new rank can be an integer > 1 or strings 'first' or 'last', but %s given"%new_rank)
if self.rank == new_rank:
return True
if new_rank < 1:
if strict:
raise ValueError("Rank must be >= 1, but %d given"%rank)
new_rank = 1
elif new_rank > max_rank:
if strict:
raise ValueError("Rank is too big (max_rank = %d), but %d given"%(max_rank,rank))
new_rank = max_rank
self._datasource.update_rank(self, new_rank)
## @returns The maximum assignable rank for this relation
# @todo implementation
def get_max_rank(self):
raise NotImplemented("Abstract method")
## @brief Abstract class to handle hierarchy relations
class _LeHierarch(_LeRelation):
## @brief Delete current instance from DB
def delete(self):
lecrud._LeCrud._delete(self)
## @brief modify a LeHierarch rank
# @param new_rank int|str : The new rank can be an integer > 1 or strings 'first' or 'last'
# @return True in case of success, False in case of failure
# @throw ValueError if step is not castable into an integer
def set_rank(self, new_rank):
return self._set_rank(
new_rank,
id_superior=getattr(self, self.uidname()),
nature=self.nature
)
@classmethod
def insert(cls, datas):
# Checks if the relation exists
datas[EditorialModel.classtypes.relation_name] = None
res = cls.get(
[(cls._subordinate_field_name, '=', datas['subordinate']), ('nature', '=', datas['nature'])],
[ cls.uidname() ]
)
if not(res is None) and len(res) > 0:
return False
return super().insert(datas, 'LeHierarch')
## @brief Get maximum assignable rank given a superior id and a nature
# @return an integer > 1
@classmethod
def get_max_rank(cls, id_superior, nature):
if nature not in EditorialModel.classtypes.EmNature.getall():
raise ValueError("Unknow relation nature '%s'" % nature)
sql_res = cls.get(
query_filters=[
('nature','=', nature),
(cls._superior_field_name, '=', id_superior),
],
field_list=['rank'],
order=[('rank', 'DESC')],
limit=1,
instanciate=False
)
return sql_res[0]['rank']+1 if not(sql_res is None) and len(sql_res) > 0 else 1
## @brief instanciate the relevant lodel object using a dict of datas
@classmethod
def object_from_data(cls, datas):
return cls.name2class('LeHierarch')(**datas)
## @brief Abstract class to handle rel2type relations
class _LeRel2Type(_LeRelation):
## @brief Stores the list of fieldtypes handling relations attributes
_rel_attr_fieldtypes = dict()
## @brief Stores the LeClass child class used as superior
_superior_cls = None
## @brief Stores the LeType child class used as subordinate
_subordinate_cls = None
## @brief Stores the relation name for a rel2type
_relation_name = None
## @brief Delete current instance from DB
def delete(self):
lecrud._LeCrud._delete(self)
## @brief modify a LeRel2Type rank
# @param new_rank int|str : The new rank can be an integer > 1 or strings 'first' or 'last'
# @return True in case of success, False in case of failure
# @throw ValueError if step is not castable into an integer
def set_rank(self, new_rank):
if self._relation_name is None:
raise NotImplementedError("Abstract method")
return self._set_rank(new_rank, superior = self.superior, relation_name = self._relation_name)
@classmethod
def get_max_rank(cls, superior, relation_name):
# SELECT rank FROM relation JOIN object ON object.lodel_id = id_subordinate WHERE object.type_id = <type_em_id>
ret = cls.get(
query_filters = [
(EditorialModel.classtypes.relation_name, '=', relation_name),
(EditorialModel.classtypes.relation_superior, '=', superior),
],
field_list = ['rank'],
order = [('rank', 'DESC')],
limit = 1,
instanciate = False
)
return 1 if ret is None else ret[0]['rank']
## @brief Implements insert for rel2type
# @todo checks when autodetecing the rel2type class
@classmethod
def insert(cls, datas, classname = None):
#Set the nature
if 'nature' not in datas:
datas['nature'] = None
if cls.__name__ == 'LeRel2Type' and classname is None:
if EditorialModel.classtypes.relation_name not in datas:
raise RuntimeError("Unable to autodetect rel2type. No relation_name given")
# autodetect the rel2type child class (BROKEN)
classname = relname(datas[self._superior_field_name], datas[self._subordinate_field_name], datas[EditorialModel.classtypes.relation_name])
else:
if classname != None:
ccls = cls.name2class(classname)
if ccls == False:
raise lecrud.LeApiErrors("Bad classname given")
relation_name = ccls._relation_name
else:
relation_name = cls._relation_name
datas[EditorialModel.classtypes.relation_name] = relation_name
return super().insert(datas, classname)
## @brief Given a superior and a subordinate, returns the classname of the give rel2type
# @param lesupclass LeClass : LeClass child class (not an instance) (can be a LeType or a LeClass child)
# @param lesubclass LeType : A LeType child class (not an instance)
# @return a name as string
@classmethod
def relname(cls, lesupclass, lesubclass, relation_name):
supname = lesupclass._leclass.__name__ if lesupclass.implements_letype() else lesupclass.__name__
subname = lesubclass.__name__
return cls.name2rel2type(supname, subname, relation_name)
## @brief instanciate the relevant lodel object using a dict of datas
@classmethod
def object_from_data(cls, datas):
le_object = cls.name2class('LeObject')
class_name = le_object._me_uid[datas['class_id']].__name__
type_name = le_object._me_uid[datas['type_id']].__name__
relation_classname = lecrud._LeCrud.name2rel2type(class_name, type_name, EditorialModel.classtypes.relation_name)
del(datas['class_id'], datas['type_id'])
return cls.name2class(relation_classname)(**datas)