From 5ad9c27a07b11004615de37955abd2e85041afb1 Mon Sep 17 00:00:00 2001 From: Yann Date: Thu, 21 Jan 2016 14:03:58 +0100 Subject: [PATCH] Introducing the notion of immutable fieldtypes for classtypes fields definition --- EditorialModel/classes.py | 1 + EditorialModel/classtypes.py | 13 +++++++ EditorialModel/fields.py | 34 +++++++++++------ Lodel/utils/mlstring.py | 28 +++++++++++--- leapi/leclass.py | 2 +- leapi/lefactory.py | 73 ++++++++++++++++++++++++------------ leapi/letype.py | 3 +- leapi/test/test_lefactory.py | 5 ++- 8 files changed, 114 insertions(+), 45 deletions(-) diff --git a/EditorialModel/classes.py b/EditorialModel/classes.py index fc1fd0e..8257284 100644 --- a/EditorialModel/classes.py +++ b/EditorialModel/classes.py @@ -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())] diff --git a/EditorialModel/classtypes.py b/EditorialModel/classtypes.py index d0e2ee1..b9406c1 100644 --- a/EditorialModel/classtypes.py +++ b/EditorialModel/classtypes.py @@ -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, } } diff --git a/EditorialModel/fields.py b/EditorialModel/fields.py index b5079f1..17cd5ac 100644 --- a/EditorialModel/fields.py +++ b/EditorialModel/fields.py @@ -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): diff --git a/Lodel/utils/mlstring.py b/Lodel/utils/mlstring.py index 11e4da3..2529eed 100644 --- a/Lodel/utils/mlstring.py +++ b/Lodel/utils/mlstring.py @@ -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__() diff --git a/leapi/leclass.py b/leapi/leclass.py index 419f9b2..59c9bfa 100644 --- a/leapi/leclass.py +++ b/leapi/leclass.py @@ -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 diff --git a/leapi/lefactory.py b/leapi/lefactory.py index 484aa91..d6699d3 100644 --- a/leapi/lefactory.py +++ b/leapi/lefactory.py @@ -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 diff --git a/leapi/letype.py b/leapi/letype.py index 085443a..94e0bc0 100644 --- a/leapi/letype.py +++ b/leapi/letype.py @@ -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 diff --git a/leapi/test/test_lefactory.py b/leapi/test/test_lefactory.py index 015fe34..9af2a4b 100644 --- a/leapi/test/test_lefactory.py +++ b/leapi/test/test_lefactory.py @@ -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])