mirror of
https://github.com/yweber/lodel2.git
synced 2026-07-15 11:41:58 +02:00
Introducing the notion of immutable fieldtypes for classtypes fields definition
This commit is contained in:
parent
f28c941a38
commit
5ad9c27a07
8 changed files with 114 additions and 45 deletions
|
|
@ -58,6 +58,7 @@ class EmClass(EmComponent):
|
|||
ctype_opts[opt_name] = opt_val
|
||||
|
||||
if ftype_opts != ctype_opts:
|
||||
field.set_fieldtype_options(**ctype_opts)
|
||||
# If options mismatch produce a diff and display a warning
|
||||
ctype_opts = [ "%s: %s\n"%(repr(k), repr(ctype_opts[k])) for k in sorted(ctype_opts.keys())]
|
||||
ftype_opts = [ "%s: %s\n"%(repr(k), repr(ftype_opts[k])) for k in sorted(ftype_opts.keys())]
|
||||
|
|
|
|||
|
|
@ -14,33 +14,39 @@ common_fields = {
|
|||
object_uid: {
|
||||
'fieldtype': 'pk',
|
||||
'internal': 'autosql',
|
||||
'immutable' : True,
|
||||
},
|
||||
object_em_class_id : {
|
||||
'fieldtype': 'emuid',
|
||||
'is_id_class': True,
|
||||
'internal': 'automatic',
|
||||
'immutable' : True,
|
||||
},
|
||||
object_em_type_id : {
|
||||
'fieldtype': 'emuid',
|
||||
'is_id_class': False,
|
||||
'internal': 'automatic',
|
||||
'immutable' : True,
|
||||
},
|
||||
'string': {
|
||||
'fieldtype': 'char',
|
||||
'max_length': 128,
|
||||
'internal': 'automatic',
|
||||
'nullable': True,
|
||||
'immutable' : False,
|
||||
},
|
||||
'creation_date': {
|
||||
'fieldtype': 'datetime',
|
||||
'now_on_create': True,
|
||||
'internal': 'autosql',
|
||||
'immutable' : True,
|
||||
},
|
||||
'modification_date': {
|
||||
'fieldtype': 'datetime',
|
||||
'now_on_create': True,
|
||||
'now_on_update': True,
|
||||
'internal': 'autosql',
|
||||
'immutable' : True,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,29 +54,36 @@ relations_common_fields = {
|
|||
relation_uid: {
|
||||
'fieldtype': 'pk',
|
||||
'internal': 'autosql',
|
||||
'immutable' : True,
|
||||
},
|
||||
'nature': {
|
||||
'fieldtype': 'naturerelation',
|
||||
'immutable' : True,
|
||||
},
|
||||
'depth': {
|
||||
'fieldtype': 'integer',
|
||||
'internal': 'automatic',
|
||||
'immutable' : True,
|
||||
},
|
||||
'rank': {
|
||||
'fieldtype': 'rank',
|
||||
'internal': 'automatic',
|
||||
'immutable' : True,
|
||||
},
|
||||
relation_superior : {
|
||||
'fieldtype': 'leo',
|
||||
'superior': True,
|
||||
'immutable' : True,
|
||||
},
|
||||
relation_subordinate: {
|
||||
'fieldtype': 'leo',
|
||||
'superior': False,
|
||||
'immutable' : True,
|
||||
},
|
||||
relation_name: {
|
||||
'fieldtype': 'namerelation',
|
||||
'max_length': 128,
|
||||
'immutable' : True,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ class EmField(EmComponent):
|
|||
# @param uniq bool : if True the value should be uniq in the db table
|
||||
# @param **kwargs : more keywords arguments for the fieldtype
|
||||
def __init__(self, model, uid, name, class_id, fieldtype, optional=False, internal=False, rel_field_id=None, icon='0', string=None, help_text=None, date_update=None, date_create=None, rank=None, nullable=False, uniq=False, **kwargs):
|
||||
|
||||
self.class_id = class_id
|
||||
self.check_type('class_id', int)
|
||||
self.optional = bool(optional)
|
||||
|
|
@ -57,21 +56,12 @@ class EmField(EmComponent):
|
|||
self.fieldtype = fieldtype
|
||||
self._fieldtype_args = kwargs
|
||||
self._fieldtype_args.update({'nullable': nullable, 'uniq': uniq, 'internal': self.internal})
|
||||
try:
|
||||
fieldtype_instance = self._fieldtype_cls(**self._fieldtype_args)
|
||||
except AttributeError as e:
|
||||
raise AttributeError("Error will instanciating fieldtype : %s" % e)
|
||||
self.set_fieldtype_options(**self._fieldtype_args)
|
||||
|
||||
if 'default' in kwargs:
|
||||
if not fieldtype_instance.check(default):
|
||||
raise TypeError("Default value ('%s') is not valid given the fieldtype '%s'" % (default, fieldtype))
|
||||
|
||||
self.nullable = nullable
|
||||
self.uniq = uniq
|
||||
|
||||
for kname, kval in kwargs.items():
|
||||
setattr(self, kname, kval)
|
||||
|
||||
super(EmField, self).__init__(model=model, uid=uid, name=name, string=string, help_text=help_text, date_update=date_update, date_create=date_create, rank=rank)
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -89,6 +79,28 @@ class EmField(EmComponent):
|
|||
def em_class(self):
|
||||
return self.model.component(self.class_id)
|
||||
|
||||
## @brief Update the fieldtype of a field
|
||||
def set_fieldtype(self, fieldtype_name):
|
||||
self.fieldtype = fieldtype_name
|
||||
self._fieldtype_cls = GenericFieldType.from_name(self.fieldtype)
|
||||
self.set_fieldtype_options(**self._fieldtype_args)
|
||||
|
||||
## @brief Set fieldtype options
|
||||
def set_fieldtype_options(self, nullable = False, uniq = False, internal=False, **kwargs):
|
||||
# Cleaning old options
|
||||
if hasattr(self, 'name'): # If not called by __init__
|
||||
for opt_name in self._fieldtype_args:
|
||||
if hasattr(self, opt_name):
|
||||
delattr(self,opt_name)
|
||||
self._fieldtype_args = kwargs
|
||||
self._fieldtype_args.update({'nullable': nullable, 'uniq': uniq, 'internal': internal})
|
||||
try:
|
||||
fieldtype_instance = self._fieldtype_cls(**self._fieldtype_args)
|
||||
except AttributeError as e:
|
||||
raise AttributeError("Error will instanciating fieldtype : %s" % e)
|
||||
for opt_name, opt_val in self._fieldtype_args.items():
|
||||
setattr(self, opt_name, opt_val)
|
||||
|
||||
## @brief Getter for private property EmField._fieldtype_args
|
||||
# @return A copy of private dict _fieldtype_args
|
||||
def fieldtype_options(self):
|
||||
|
|
|
|||
|
|
@ -1,27 +1,40 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import warnings
|
||||
|
||||
|
||||
## Handle string with translations
|
||||
# @todo define a default language that will be used in case the wanted language is not available for this string (gettext-like behavior)
|
||||
class MlString(object):
|
||||
|
||||
default_lang = '__default__'
|
||||
## Instanciate a new string with translation
|
||||
#
|
||||
# @param translations dict: With key = lang and value the translation
|
||||
def __init__(self, translations=None):
|
||||
self.translations = dict() if translations is None else translations
|
||||
def __init__(self, translations=None, default_value = None):
|
||||
if translations is None:
|
||||
translations = dict()
|
||||
elif isinstance(translations, str):
|
||||
translations = json.loads(translations)
|
||||
|
||||
if not isinstance(translations, dict):
|
||||
raise ValueError('Bad value given for translations argument on MlString instanciation')
|
||||
else:
|
||||
self.translations = translations
|
||||
|
||||
self.translations[self.default_lang] = '' if default_value is None else default_value
|
||||
if default_value is None:
|
||||
warnings.warn('No default value when isntanciating an MlString')
|
||||
|
||||
## Return a translation
|
||||
# @param lang str: The lang
|
||||
# @return An empty string if the wanted lang don't exist
|
||||
# @warning Returns an empty string if the wanted translation didn't exists
|
||||
# @todo if the asked language is not available, use the default one, defined as a class property
|
||||
def get(self, lang):
|
||||
if not lang in self.translations:
|
||||
return ''
|
||||
|
||||
def get(self, lang = None):
|
||||
if lang is None or lang not in self.translations:
|
||||
lang = self.default_lang
|
||||
return self.translations[lang]
|
||||
|
||||
## Set a translation for this MlString
|
||||
|
|
@ -35,6 +48,9 @@ class MlString(object):
|
|||
else:
|
||||
self.translations[lang] = text
|
||||
|
||||
def set_default(self, text):
|
||||
self.set(self.default_lang, text)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class _LeClass(_LeObject):
|
|||
@classmethod
|
||||
def fieldtypes(cls):
|
||||
ret = dict()
|
||||
ret.update(super(_LeClass,cls).fieldtypes())
|
||||
ret.update(super().fieldtypes())
|
||||
ret.update(cls._fieldtypes)
|
||||
return ret
|
||||
|
||||
|
|
|
|||
|
|
@ -49,23 +49,26 @@ class LeFactory(object):
|
|||
res_ft_l = list()
|
||||
res_uid_ft = None
|
||||
for fname, ftargs in ft_dict.items():
|
||||
ftargs = copy.copy(ftargs)
|
||||
fieldtype = ftargs['fieldtype']
|
||||
self.needed_fieldtypes |= set([fieldtype])
|
||||
del(ftargs['fieldtype'])
|
||||
|
||||
constructor = '{ftname}.EmFieldType(**{ftargs})'.format(
|
||||
ftname = GenericFieldType.module_name(fieldtype),
|
||||
ftargs = ftargs,
|
||||
)
|
||||
|
||||
if fieldtype == 'pk':
|
||||
#
|
||||
# WARNING multiple PK not supported
|
||||
#
|
||||
res_uid_ft = "{ %s: %s }"%(repr(fname),constructor)
|
||||
if ftargs is None:
|
||||
res_ft_l.append('%s: None' % repr(fname))
|
||||
else:
|
||||
res_ft_l.append( '%s: %s'%(repr(fname), constructor) )
|
||||
ftargs = copy.copy(ftargs)
|
||||
fieldtype = ftargs['fieldtype']
|
||||
self.needed_fieldtypes |= set([fieldtype])
|
||||
del(ftargs['fieldtype'])
|
||||
|
||||
constructor = '{ftname}.EmFieldType(**{ftargs})'.format(
|
||||
ftname = GenericFieldType.module_name(fieldtype),
|
||||
ftargs = ftargs,
|
||||
)
|
||||
|
||||
if fieldtype == 'pk':
|
||||
#
|
||||
# WARNING multiple PK not supported
|
||||
#
|
||||
res_uid_ft = "{ %s: %s }"%(repr(fname),constructor)
|
||||
else:
|
||||
res_ft_l.append( '%s: %s'%(repr(fname), constructor) )
|
||||
return (res_uid_ft, res_ft_l)
|
||||
|
||||
## @brief Given a Model generate concrete instances of LeRel2Type classes to represent relations
|
||||
|
|
@ -112,23 +115,30 @@ class {classname}(LeRel2Type):
|
|||
for rfield in [ f for f in emclass.fields() if f.fieldtype == 'rel2type']:
|
||||
fti = rfield.fieldtype_instance()
|
||||
cls_linked_types[rfield.name] = _LeCrud.name2classname(model.component(fti.rel_to_type_id).name)
|
||||
ml_fieldnames = dict()
|
||||
# Populating fieldtype attr
|
||||
for field in emclass.fields(relational = False):
|
||||
self.needed_fieldtypes |= set([field.fieldtype])
|
||||
cls_fields[field.name] = LeFactory.fieldtype_construct_from_field(field)
|
||||
fti = field.fieldtype_instance()
|
||||
if field.name not in EditorialModel.classtypes.common_fields.keys() or not ( hasattr(field, 'immutable') and field.immutable):
|
||||
self.needed_fieldtypes |= set([field.fieldtype])
|
||||
cls_fields[field.name] = LeFactory.fieldtype_construct_from_field(field)
|
||||
fti = field.fieldtype_instance()
|
||||
if field.string.get() == '':
|
||||
field.string.set_default(field.name)
|
||||
ml_fieldnames[field.name] = field.string.__str__()
|
||||
|
||||
return """
|
||||
#Initialisation of {name} class attributes
|
||||
{name}._fieldtypes = {ftypes}
|
||||
{name}.ml_fields_strings = {fieldnames}
|
||||
{name}._linked_types = {ltypes}
|
||||
{name}._classtype = {classtype}
|
||||
""".format(
|
||||
name = _LeCrud.name2classname(emclass.name),
|
||||
ftypes = "{" + (','.join(['\n %s: %s' % (repr(f), v) for f, v in cls_fields.items()])) + "\n}",
|
||||
fieldnames = '{' + (','.join(['\n %s: MlString(%s)' % (repr(f), v) for f,v in ml_fieldnames.items()])) + '\n}',
|
||||
ltypes = "{" + (','.join(['\n %s: %s' % (repr(f), v) for f, v in cls_linked_types.items()])) + "\n}",
|
||||
|
||||
classtype = repr(emclass.classtype)
|
||||
classtype = repr(emclass.classtype),
|
||||
)
|
||||
|
||||
## @brief Given a Model and an EmType instances generate python code for corresponding LeType
|
||||
|
|
@ -177,6 +187,7 @@ class {classname}(LeRel2Type):
|
|||
import EditorialModel
|
||||
from EditorialModel import fieldtypes
|
||||
from EditorialModel.fieldtypes import {needed_fieldtypes_list}
|
||||
from Lodel.utils.mlstring import MlString
|
||||
|
||||
import leapi
|
||||
import leapi.lecrud
|
||||
|
|
@ -197,9 +208,15 @@ import %s
|
|||
leobj_me_uid[comp.uid] = _LeCrud.name2classname(comp.name)
|
||||
|
||||
#Building the fieldtypes dict of LeObject
|
||||
(leobj_uid_fieldtype, leobj_fieldtypes) = self.concret_fieldtypes(EditorialModel.classtypes.common_fields)
|
||||
common_fieldtypes = dict()
|
||||
for ftname, ftdef in EditorialModel.classtypes.common_fields.items():
|
||||
common_fieldtypes[ftname] = ftdef if 'immutable' in ftdef and ftdef['immutable'] else None
|
||||
(leobj_uid_fieldtype, leobj_fieldtypes) = self.concret_fieldtypes(common_fieldtypes)
|
||||
#Building the fieldtypes dict for LeRelation
|
||||
(lerel_uid_fieldtype, lerel_fieldtypes) = self.concret_fieldtypes(EditorialModel.classtypes.relations_common_fields)
|
||||
common_fieldtypes = dict()
|
||||
for ftname, ftdef in EditorialModel.classtypes.relations_common_fields.items():
|
||||
common_fieldtypes[ftname] = ftdef if 'immutable' in ftdef and ftdef['immutable'] else None
|
||||
(lerel_uid_fieldtype, lerel_fieldtypes) = self.concret_fieldtypes(common_fieldtypes)
|
||||
|
||||
result += """
|
||||
## @brief _LeCrud concret class
|
||||
|
|
@ -256,28 +273,36 @@ class LeType(LeClass, _LeType):
|
|||
|
||||
#LeClass child classes definition
|
||||
for emclass in emclass_l:
|
||||
if emclass.string.get() == '':
|
||||
emclass.string.set_default(emclass.name)
|
||||
result += """
|
||||
## @brief EmClass {name} LeClass child class
|
||||
# @see leapi.leclass.LeClass
|
||||
class {name}(LeClass, LeObject):
|
||||
_class_id = {uid}
|
||||
ml_string = MlString({name_translations})
|
||||
|
||||
""".format(
|
||||
name=_LeCrud.name2classname(emclass.name),
|
||||
uid=emclass.uid
|
||||
uid=emclass.uid,
|
||||
name_translations = repr(emclass.string.__str__()),
|
||||
)
|
||||
#LeType child classes definition
|
||||
for emtype in emtype_l:
|
||||
if emtype.string.get() == '':
|
||||
emtype.string.set_default(emtype.name)
|
||||
result += """
|
||||
## @brief EmType {name} LeType child class
|
||||
# @see leobject::letype::LeType
|
||||
class {name}(LeType, {leclass}):
|
||||
_type_id = {uid}
|
||||
ml_string = MlString({name_translations})
|
||||
|
||||
""".format(
|
||||
name=_LeCrud.name2classname(emtype.name),
|
||||
leclass=_LeCrud.name2classname(emtype.em_class.name),
|
||||
uid=emtype.uid
|
||||
uid=emtype.uid,
|
||||
name_translations = repr(emtype.string.__str__()),
|
||||
)
|
||||
|
||||
#Generating concret class of LeRel2Type
|
||||
|
|
|
|||
|
|
@ -55,7 +55,8 @@ class _LeType(_LeClass):
|
|||
|
||||
@classmethod
|
||||
def fieldtypes(cls):
|
||||
return { fname: cls._fieldtypes[fname] for fname in cls._fieldtypes if fname in cls._fields }
|
||||
super_fieldtypes = super().fieldtypes()
|
||||
return { fname: super_fieldtypes[fname] for fname in super_fieldtypes if fname in cls._fields }
|
||||
|
||||
## @brief Get all the datas for this LeType
|
||||
# @return a dict with fieldname as key and field value as value
|
||||
|
|
|
|||
|
|
@ -72,11 +72,12 @@ class TestLeFactory(TestCase):
|
|||
)
|
||||
|
||||
#Testing fieldtypes
|
||||
expected_fieldtypes = [ f for f in emclass.fields(relational=False) if not(hasattr(f, 'immutable') and f.immutable)]
|
||||
self.assertEqual(
|
||||
set([ f.name for f in emclass.fields(relational=False)]),
|
||||
set([ f.name for f in expected_fieldtypes]),
|
||||
set(leclass._fieldtypes.keys())
|
||||
)
|
||||
for field in emclass.fields(relational=False):
|
||||
for field in expected_fieldtypes:
|
||||
self.assertEqual(
|
||||
hash(field.fieldtype_instance()),
|
||||
hash(leclass._fieldtypes[field.name])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue