1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2025-11-25 23:06:55 +01:00

Implements get_max_rank() in LeApi. Add a fieldtype rank. + A lot of bugfix

The me.json was not up to date (Models don't forward classtypes modifications to loaded json file)
Some bugfix on fieldtypes that was not used (because of old me.json)
This commit is contained in:
Yann 2016-01-11 14:03:01 +01:00
commit 43e8145dab
15 changed files with 186 additions and 58 deletions

View file

@ -20,7 +20,7 @@ class DummyDatasource(object):
# @param limit int : Number of row to be returned
# @param offset int : Used with limit to choose the start row
# @return a list of LeCrud child classes
def select(self, target_cls, field_list, filters, rel_filters=None, order=None, group=None, limit=None, offset=0):
def select(self, target_cls, field_list, filters, rel_filters=None, order=None, group=None, limit=None, offset=0, instanciate=True):
pass
## @brief delete lodel editorial components given filters

View file

@ -4,15 +4,15 @@
common_fields = {
'lodel_id': {
'fieldtype': 'pk',
'internal': 'automatic',
'internal': 'autosql',
},
'class_id': {
'fieldtype': 'emuid',
'class_id': True,
'is_id_class': True,
},
'type_id': {
'fieldtype': 'emuid',
'class_id': False,
'is_id_class': False,
},
'string': {
'fieldtype': 'char',
@ -22,20 +22,20 @@ common_fields = {
'creation_date': {
'fieldtype': 'datetime',
'now_on_create': True,
'internal': 'automatic',
'internal': 'autosql',
},
'modification_date': {
'fieldtype': 'datetime',
'now_on_create': True,
'now_on_update': True,
'internal': 'automatic',
'internal': 'autosql',
}
}
relations_common_fields = {
'id_relation': {
'fieldtype': 'pk',
'internal': 'automatic',
'internal': 'autosql',
},
'nature': {
'fieldtype': 'naturerelation',
@ -45,7 +45,7 @@ relations_common_fields = {
'internal': 'automatic',
},
'rank': {
'fieldtype': 'integer',
'fieldtype': 'rank',
'internal': 'automatic',
},
'superior': {

View file

@ -39,8 +39,8 @@ class EmField(EmComponent):
if not internal:
self.internal = False
else:
if internal.lower() not in ['automatic']:
raise ValueError("The internal arguments possible values are : [False, 'object', 'automatic']")
if internal.lower() not in ['automatic', 'autosql']:
raise ValueError("The internal arguments possible values are : [False, 'autosql', 'automatic']")
self.internal = internal.lower()
if self.internal == 'object' and name not in EditorialModel.classtypes.common_fields.keys():

View file

@ -10,27 +10,30 @@ class EmFieldType(integer.EmFieldType):
help = 'Fieldtypes designed to handle editorial model UID for LeObjects'
def __init__(self, id_class=True, **kwargs):
def __init__(self, is_id_class, **kwargs):
self._is_id_class = is_id_class
kwargs['internal'] = 'automatic'
super(EmFieldType, self).__init__(id_class = id_class, **kwargs)
super(EmFieldType, self).__init__(is_id_class = is_id_class, **kwargs)
def _check_data_value(self, value):
if not( value is None):
return ValueError("Cannot set this value. Only None is authorized")
return (value, None)
def construct_data(self, lec, fname, datas):
if isinstance(lec, letype._LeType):
# if None try to fetch data from lec itself
fname[datas] = lec.em_uid()[ 0 if self.class_id else 1]
if self.is_id_class:
if lec.implements_leclass():
datas[fname] = lec._class_id
else:
datas[fname] = None
else:
raise RuntimeError("The LeObject is not a LeType")
if lec.implements_letype():
datas[fname] = lec._type_id
else:
datas[fname] = None
return datas[fname]
def check_data_consistency(self, lec, fname, datas):
if isinstance(lec, lecrud._LeCrud) and lec.implements_letype():
if datas[fname] != (lec._class_id if self.class_id else lec._type_id):
return FieldTypeError("Given Editorial model uid doesn't fit with given LeObject")
else:
return FieldTypeError("You have to give a LeType !!!")
if datas[fname] != (lec._class_id if self.is_id_class else lec._type_id):
return FieldTypeError("Given Editorial model uid doesn't fit with given LeObject")

View file

@ -78,7 +78,13 @@ class GenericFieldType(object):
# @param datas dict : dict storing fields values
# @return constructed datas
def construct_data(self, lec, fname, datas):
return datas[fname]
if fname in datas:
return datas[fname]
elif hasattr(lec.fieldtypes()[fname], 'default'):
return lec.fieldtypes()[fname].default
elif lec.fieldtypes()[fname].nullable:
return None
raise RuntimeError("Unable to construct data for field %s", fname)
## @brief Check datas consistency
# @param leo LeCrud : A LeCrud child class instance

View file

@ -13,7 +13,7 @@ class EmFieldType(integer.EmFieldType):
allowed = {
'nullable': False,
'uniq': False,
'internal': 'automatic',
'internal': 'autosql',
'primary': True,
}
# Checking args

View file

@ -0,0 +1,25 @@
#-*- coding utf-8 -*-
import leapi.lerelation as lerelation
from .generic import FieldTypeError
from . import integer
class EmFieldType(integer.EmFieldType):
help = 'Fieldtype designed to handle relations rank'
def __init__(self, **kwargs):
super().__init__(**kwargs)
def construct_data(self, lec, fname, datas):
superior_id = datas[lec._superior_field_name]
if lec.is_lerel2type():
subordinate = lec._subordinate_cls
sub_em_type_id = subordinate._type_id
datas[fname] = lec.get_max_rank(superior_id, sub_em_type_id)
elif lec.is_lehierarch():
datas[fname] = lec.get_max_rank(superior_id, datas['nature'])
else:
raise ValueError("Called with bad class : ", lec.__name__)
return datas[fname]

View file

@ -269,10 +269,11 @@
"class_id": 1,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 1,
"fieldtype": "integer",
"fieldtype": "emuid",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"is_id_class": true,
"name": "class_id"
},
"24": {
@ -304,10 +305,11 @@
"class_id": 1,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 3,
"fieldtype": "integer",
"fieldtype": "emuid",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"is_id_class": false,
"name": "type_id"
},
"26": {
@ -323,7 +325,7 @@
"rank": 4,
"fieldtype": "pk",
"help_text": "",
"internal": "automatic",
"internal": "autosql",
"rel_field_id": null,
"name": "lodel_id"
},
@ -338,10 +340,11 @@
"class_id": 2,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 1,
"fieldtype": "integer",
"fieldtype": "emuid",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"is_id_class": true,
"name": "class_id"
},
"29": {
@ -373,10 +376,11 @@
"class_id": 2,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 3,
"fieldtype": "integer",
"fieldtype": "emuid",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"is_id_class": false,
"name": "type_id"
},
"31": {
@ -392,7 +396,7 @@
"rank": 4,
"fieldtype": "pk",
"help_text": "",
"internal": "automatic",
"internal": "autosql",
"rel_field_id": null,
"name": "lodel_id"
},
@ -407,10 +411,11 @@
"class_id": 13,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 1,
"fieldtype": "integer",
"fieldtype": "emuid",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"is_id_class": true,
"name": "class_id"
},
"34": {
@ -442,10 +447,11 @@
"class_id": 13,
"date_create": "Fri Oct 16 11:05:04 2015",
"rank": 3,
"fieldtype": "integer",
"fieldtype": "emuid",
"help_text": "",
"internal": "automatic",
"rel_field_id": null,
"is_id_class": false,
"name": "type_id"
},
"36": {
@ -461,7 +467,7 @@
"rank": 4,
"fieldtype": "pk",
"help_text": "",
"internal": "automatic",
"internal": "autosql",
"rel_field_id": null,
"name": "lodel_id"
},
@ -477,7 +483,7 @@
"class_id": 1,
"date_create": "Wed Nov 4 10:52:13 2015",
"now_on_create": true,
"internal": "automatic",
"internal": "autosql",
"fieldtype": "datetime",
"now_on_update": true,
"help_text": "",
@ -497,7 +503,7 @@
"rank": 6,
"string": "",
"help_text": "",
"internal": "automatic",
"internal": "autosql",
"optional": false,
"name": "creation_date",
"fieldtype": "datetime"
@ -516,7 +522,7 @@
"string": "",
"now_on_update": true,
"help_text": "",
"internal": "automatic",
"internal": "autosql",
"optional": false,
"name": "modification_date",
"fieldtype": "datetime"
@ -534,7 +540,7 @@
"rank": 6,
"string": "",
"help_text": "",
"internal": "automatic",
"internal": "autosql",
"optional": false,
"name": "creation_date",
"fieldtype": "datetime"
@ -553,7 +559,7 @@
"string": "",
"now_on_update": true,
"help_text": "",
"internal": "automatic",
"internal": "autosql",
"optional": false,
"name": "modification_date",
"fieldtype": "datetime"
@ -571,7 +577,7 @@
"rank": 6,
"string": "",
"help_text": "",
"internal": "automatic",
"internal": "autosql",
"optional": false,
"name": "creation_date",
"fieldtype": "datetime"

View file

@ -215,6 +215,9 @@ class _LeCrud(object):
def is_lerel2type(cls):
return cls.implements_lerel2type()
def uidget(self):
return getattr(self, self.uidname())
## @brief Returns object datas
# @param
# @return a dict of fieldname : value
@ -411,6 +414,7 @@ class _LeCrud(object):
raise LeApiErrors("Error when inserting",[ValueError("The class '%s' was not found"%classname)])
if not callcls.implements_letype() and not callcls.implements_lerelation():
raise ValueError("You can only insert relations and LeTypes objects but tying to insert a '%s'"%callcls.__name__)
insert_datas = callcls.prepare_datas(datas, complete = True, allow_internal = False)
return callcls._datasource.insert(callcls, **insert_datas)
@ -430,6 +434,7 @@ class _LeCrud(object):
ret_datas = cls.check_datas_value(datas, complete, allow_internal)
if isinstance(ret_datas, Exception):
raise ret_datas
if complete:
ret_datas = cls._construct_datas(ret_datas)
cls._check_datas_consistency(ret_datas)
@ -458,7 +463,7 @@ class _LeCrud(object):
def _construct_datas(cls, datas):
res_datas = dict()
for fname, ftype in cls.fieldtypes().items():
if fname in datas:
if not ftype.is_internal() or ftype.internal != 'autosql':
res_datas[fname] = ftype.construct_data(cls, fname, datas)
return res_datas
## @brief Check datas consistency

View file

@ -210,7 +210,7 @@ import %s
type_id = None
for fname, finfo in EditorialModel.classtypes.common_fields.items():
if finfo['fieldtype'] == 'emuid':
if finfo['class_id']:
if finfo['is_id_class']:
class_id = fname
else:
type_id = fname

View file

@ -2,7 +2,9 @@
import copy
import re
import warnings
import EditorialModel.classtypes
import EditorialModel.fieldtypes.leo as ft_leo
from . import lecrud
from . import leobject
@ -101,7 +103,11 @@ class _LeRelation(lecrud._LeCrud):
# @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):
max_rank = self.get_max_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:
@ -128,9 +134,7 @@ class _LeRelation(lecrud._LeCrud):
## @returns The maximum assignable rank for this relation
# @todo implementation
def get_max_rank(self):
max_rank_result = self.__class__.get(field_list=['rank'], order=[('rank', 'DESC')], limit=1)
max_rank = int(max_rank_result[0].rank)
return max_rank+1
raise NotImplemented("Abstract method")
## @brief Abstract class to handle hierarchy relations
class _LeHierarch(_LeRelation):
@ -139,6 +143,46 @@ class _LeHierarch(_LeRelation):
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
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):
@ -158,6 +202,23 @@ class _LeRel2Type(_LeRelation):
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):
return self._set_rank(
new_rank,
id_superior=getattr(self, self.uidname()),
type_em_id=self._subordinate_cls._type_id
)
@classmethod
def get_max_rank(cls, id_superior, type_em_id):
# SELECT rank FROM relation JOIN object ON object.lodel_id = id_subordinate WHERE object.type_id = <type_em_id>
warnings.warn("LeRel2Type.get_max_rank() is not implemented yet and will always return 0")
return 0
## @brief Implements insert for rel2type
# @todo checks when autodetecing the rel2type class
@classmethod

View file

@ -74,9 +74,9 @@ class _LeType(_LeClass):
# @return relation id if successfully created else returns false
def add_superior(self, superior, nature, del_if_exists = False):
lehierarch = self.name2class('LeHierarch')
if del_if_exists:
if del_if_exists: #Duplicated test with lehierarch.insert
prev_sup = lehierarch.get(
[('subordinate', '=', self), ('nature', '=', nature)],
[(lehierarch._subordinate_field_name, '=', self), ('nature', '=', nature)],
[ lehierarch.uidname() ]
)
if len(prev_sup) > 0:

View file

@ -2,6 +2,8 @@
Tests for _LeCrud and LeCrud
"""
import copy
import unittest
from unittest import TestCase
from unittest.mock import patch
@ -188,11 +190,18 @@ class LeCrudTestCase(TestCase):
]
for lecclass, ndats in ndatas:
lecclass.insert(ndats)
dsmock.assert_called_once_with(lecclass, **ndats)
# Adding default values for internal fields
expt_dats = copy.copy(ndats)
expt_dats['string'] = None
expt_dats['class_id'] = lecclass._class_id
expt_dats['type_id'] = lecclass._type_id if lecclass.implements_letype() else None
dsmock.assert_called_once_with(lecclass, **expt_dats)
dsmock.reset_mock()
lecclass.insert(ndats)
dsmock.assert_called_once_with(lecclass, **ndats)
dsmock.assert_called_once_with(lecclass, **expt_dats)
dsmock.reset_mock()
## @todo try failing on inserting from LeClass child or LeObject
@ -308,7 +317,8 @@ class LeCrudTestCase(TestCase):
order=None,
group=None,
limit=None,
offset=0
offset=0,
instanciate=True
)
dsmock.reset_mock()
@ -387,6 +397,7 @@ class LeCrudTestCase(TestCase):
exptargs['filters'].append( ('type_id', '=', callcls._type_id) )
if callcls.implements_leclass:
exptargs['filters'].append( ('class_id', '=', callcls._class_id) )
exptargs['instanciate'] = True
callcls.get(**getargs)
dsmock.assert_called_once_with(**exptargs)
dsmock.reset_mock()

View file

@ -2,6 +2,8 @@
Tests for _LeObject and LeObject
"""
import copy
import unittest
from unittest import TestCase
from unittest.mock import patch
@ -114,12 +116,18 @@ class LeObjectMockDatasourceTestCase(TestCase):
{'titre':'world'},
]
for ndats in ndatas:
# Adding values for internal fields
expt_dats = copy.copy(ndats)
expt_dats['string'] = None
expt_dats['class_id'] = Publication._class_id
expt_dats['type_id'] = Numero._type_id
Publication.insert(ndats, classname='Numero')
dsmock.assert_called_once_with(Numero, **ndats)
dsmock.assert_called_once_with(Numero, **expt_dats)
dsmock.reset_mock()
LeObject.insert(ndats, classname = 'Numero')
dsmock.assert_called_once_with(Numero, **ndats)
dsmock.assert_called_once_with(Numero, **expt_dats)
dsmock.reset_mock()
@unittest.skip("Wait FieldTypes modification (cf. #90) and classmethod capabilities for update")

View file

@ -180,6 +180,9 @@ class LeHierarch(LeRelationTestCase):
]
"""
for query, equery in queries:
equery['rank'] = 1
equery['depth'] = None
LeHierarch.insert(query)
dsmock.assert_called_once_with(LeHierarch, **equery)
dsmock.reset_mock()
@ -241,7 +244,7 @@ class LeRel2TypeTestCase(LeRelationTestCase):
for query in queries:
Rel_Textes2Personne.insert(query)
eres = {'nature': None}
eres = {'nature': None, 'depth': None, 'rank': 0}
eres.update(query)
for fname in ('superior', 'subordinate'):
if isinstance(eres[fname], int):