123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- #-*- coding: utf-8 -*-
-
- ## @package leobject API to access lodel datas
- #
- # This package contains abstract classes leapi.leclass.LeClass , leapi.letype.LeType, leapi.leapi._LeObject.
- # Those abstract classes are designed to be mother classes of dynamically generated classes ( see leapi.lefactory.LeFactory )
-
- ## @package leapi.leobject
- # @brief Abstract class designed to be implemented by LeObject
- #
- # @note LeObject will be generated by leapi.lefactory.LeFactory
-
- import leapi
- from leapi.lecrud import _LeCrud, LeApiDataCheckError, LeApiQueryError
- from leapi.leclass import _LeClass
- from leapi.leobject import LeObjectError
-
- ## @brief Represent an EmType data instance
- # @note Is not a derivated class of LeClass because the concrete class will be a derivated class from LeClass
- class _LeType(_LeClass):
-
- ## @brief Stores selected fields with key = name
- _fields = list()
- ## @brief Allowed LeType superiors
- _superiors = list()
- ## @brief Stores the class of LeClass
- _leclass = None
- ## @brief Stores the EM uid
- _type_id = None
-
- ## @brief Instanciate a new LeType
- # @param lodel_id : The lodel id
- # @param **kwargs : Datas used to populate a LeType
- def __init__(self, lodel_id, **kwargs):
- if self._leclass is None:
- raise NotImplementedError("Abstract class")
-
- self.lodel_id, err = self._uid_fieldtype['lodel_id'].check_data_value(lodel_id)
- if isinstance(err, Exception):
- raise err
-
- if 'type_id' in kwargs:
- if self.__class__._type_id != int(kwargs['type_id']):
- raise RuntimeError("Trying to instanciate a %s with an type_id that is not correct"%self.__class__.__name__)
- if 'class_id' in kwargs:
- if self.__class__._class_id != int(kwargs['class_id']):
- raise RuntimeError("Trying to instanciate a %s with a clas_id that is not correct"%self.__class__.__name__)
-
- ## Populate the object from the datas received in kwargs
- err_l = dict()
- for name, value in kwargs.items():
- if name not in self._fields:
- err_l[name] = AttributeError("No such field '%s' for %s"%(name, self.__class__.__name__))
- else:
- cvalue, err = self.fieldtypes()[name].check_data_value(value)
- if isinstance(err, Exception):
- err_l[name] = err
- else:
- setattr(self, name, value)
- if len(err_l) > 0:
- raise LeApiDataCheckError("Invalid arguments given to constructor", err_l)
-
- @classmethod
- def leo_class(cls):
- return cls._leclass
-
- @classmethod
- def fieldlist(cls):
- return cls._fields
-
- @classmethod
- def get(cls, query_filters, field_list = None, order = None, group = None, limit = None, offset = 0):
- query_filters.append(('type_id', '=', cls._type_id))
- return super().get(query_filters, field_list, order, group, limit, offset)
-
- @classmethod
- def fieldtypes(cls):
- return { fname: cls._fieldtypes[fname] for fname in cls._fieldtypes if fname in cls._fields }
-
- ## @brief Populate the LeType wih datas from DB
- # @param field_list None|list : List of fieldname to fetch. If None fetch all the missing datas
- def populate(self, field_list=None):
- if field_list == None:
- field_list = [ fname for fname in self._fields if not hasattr(self, fname) ]
- filters = [self._id_filter()]
- rel_filters = []
-
- fdatas = self._datasource.select(self.__class__, field_list, filters, rel_filters)
- if fdatas is None or len(fdatas) == 0:
- raise LeApiQueryError("Error when trying to populate an object. For type %s id : %d"% (self.__class__.__name__, self.lodel_id))
-
- for fname, fval in fdatas[0].items():
- setattr(self, fname, fval)
-
- ## @brief Get a fieldname:value dict
- # @return A dict with field name as key and the field value as value
- def datas(self):
- return { fname: getattr(self, fname) for fname in self._fields if hasattr(self,fname) }
-
- ## @brief Get all the datas for this LeType
- # @return a dict with fieldname as key and field value as value
- # @warning Can represent a performance issue
- def all_datas(self):
- self.populate()
- return self.datas()
-
- ## @brief Delete current instance from DB
- def delete(self):
- _LeCrud._delete(self)
-
- ## @brief Add a superior
- # @param lesup LeObject : LeObject child class instance
- # @param nature str : Relation nature
- # @param del_if_exists bool : If true delete the superior if any before setting the new one
- # @return relation id if successfully created else returns false
- def add_superior(self, lesup, nature, del_if_exists = False):
- lehierarch = self.name2class('LeHierarch')
- if del_if_exists:
- prev_sup = lehierarch.get(
- [('lesub', '=', self), ('nature', '=', nature)],
- [ lehierarch.uidname() ]
- )
- if len(prev_sup) > 0:
- for todel_sup in prev_sup: #This loop shoud be useless...but we never know
- todel_sup.delete()
-
- return lehierarch.insert({'lesup':lesup, 'lesub':self, 'nature':nature})
-
- ## @brief Link the LeObject with another one (rel2type relations)
- #
- # @note This methods asser that self is the superior and leo_tolink the subordinate
- #
- # @param leo_tolink LeObject : LeObject child instance to link with
- # @param **datas : Relation attributes (if any)
- # @return a relation id if success
- def link_with(self, leo_tolink, datas):
- # Fetch rel2type leapi class
- r2t = self.name2class('LeRel2Type')
- class_name = r2t.relname(self, leo_tolink.__class__)
- r2tcls = self.name2class(class_name)
- if not r2tcls:
- raise ValueError("No rel2type possible between a '%s' as superior and a '%s' as subordinate" % (self._leclass.__name__, leo_tolink.__class__.__name__))
- datas['lesup'] = self
- datas['lesub'] = leo_tolink
- return r2tcls.insert(datas, class_name)
-
- ## @brief Get the linked objects lodel_id
- # @param letype LeType : Filter the result with LeType child class (not instance)
- # @return a dict with LeType instance as key and dict{attr_name:attr_val...} as value
- # @todo unit tests
- def ___linked(self, letype):
- if leapi.letype.LeType not in letype.__bases__:
- raise ValueError("letype has to be a child class of LeType (not an instance) but %s found"%type(letype))
-
- if letype in self._linked_types.keys():
- get_sub = True
- elif self.__class__ in letype._linked_types.keys():
- get_sub = False
- else:
- raise ValueError("The two LeType classes %s and %s are not linked with a rel2type field"%(self.__class__.__name__, letype.__name__))
-
- return self._datasource.get_related(self, letype, get_sub)
-
- ## @brief Link this object with a LeObject as subordinate
- # @note shortcut for @ref leapi.leapi._LeObject.link_together()
- # @param lesub LeObject : The object to be linked with as subordinate
- # @param **rel_attr : keywords arguments for relations attributes
- # @return The relation_id if success else return False
- # @throw LeObjectError if the link is not valid
- # @throw LeObkectError if the link already exists
- # @throw AttributeError if an non existing relation attribute is given as argument
- # @throw ValueError if the relation attrivute value check fails
- # @see leapi.lefactory.LeFactory.link_together()
- def ___link(self, leo, **rel_attr):
- return leapi.lefactory.LeFactory.leobj_from_name('LeObject').link_together(self, leo, **rel_attr)
-
- ## @brief Returns linked subordinates in a rel2type given a wanted LeType child class
- # @param letype LeType(class) : The wanted LeType of result
- # @return A dict with LeType child class instance as key and dict {'id_relation':id, rel_attr_name:rel_attr_value, ...}
- # @throw LeObjectError if the relation is not possible
- # @see leapi.lefactory.LeFactory.linked_together()
- def ___linked_subordinates(self, letype):
- return leapi.lefactory.LeFactory.leobj_from_name('LeObject').linked_together(self, letype, True)
-
- ## @brief Remove a link with a subordinate
- # @param leo LeType : LeType child instance
- # @return True if a link has been deleted
- # @throw LeObjectError if the relation do not concern the current LeType
- def ___unlink_subordinate(self, id_relation):
- return leapi.lefactory.LeFactory.leobj_from_name('LeObject').linked_together(self, leo)
-
- ## @brief Remove a link bewteen this object and another
- # @param leo LeType : LeType child class instance
- # @todo unit tests
- def ___unlink(self, leo):
- if leo.__class__ in self._linked_types.keys():
- sup = self
- sub = leo
- elif self.__class__ in leo._linked_types.keys():
- sup = leo
- sub = self
-
- return self._datasource.del_related(sup, sub)
-
- ## @brief Add a superior
- # @param lesup LeType | LeRoot : LeType child class instance that will be the superior
- # @param nature str : The nature of the relation @ref EditorialModel.classtypes
- # @param rank str|int : The relation rank. Can be 'last', 'first' or an integer
- # @param replace_if_exists bool : if True delete the old superior and set the new one. If False and there is a superior raise an LeObjectQueryError
- # @return The relation ID or False if fails
- def ___superior_add(self, leo, nature, rank = 'last', replace_if_exists = False):
- return leapi.lefactory.LeFactory.leobj_from_name('LeObject').hierarchy_add(leo, self, nature, rank, replace_if_exists)
-
- ## @brief Delete a superior given a relation's natue
- # @param leo LeType | LeRoot : The superior to delete
- # @param nature str : The nature of the relation @ref EditorialModel.classtypes
- # @return True if deletion is a success
- def ___superior_del(self, leo, nature):
- return leapi.lefactory.leobj_from_name('LeObject').hierarchy_del(leo, self, nature)
-
- ## @brief Fetch superiors by depth
- # @return A list of LeObject ordered by depth (the first is the one with the bigger depth)
- def ___superiors(self):
- return leapi.lefactory.leobj_from_name('LeObject').hierarchy_get(self,nature, leo_is_sup = False)
-
- ## @brief Fetch subordinates ordered by rank
- # @return A list of LeObject ordered by rank
- def ___subordinates(self):
- return leapi.lefactory.leobj_from_name('LeObject').hierarchy_get(self,nature, leo_is_sup = True)
-
- ## @brief Update a LeType in db
- def ___db_update(self):
- return self.update(filters=[('lodel_id', '=', repr(self.lodel_id))], datas = self.datas)
-
|