No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

types.py 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. #-*- coding: utf-8 -*-
  2. import EditorialModel
  3. from EditorialModel.components import EmComponent
  4. from EditorialModel.fields import EmField
  5. from EditorialModel.classtypes import EmClassType, EmNature
  6. from EditorialModel.exceptions import MigrationHandlerChangeError, EmComponentCheckError
  7. import EditorialModel.classes
  8. ## Represents type of documents
  9. # A type is a specialisation of a class, it can select optional field,
  10. # they have hooks, are organized in hierarchy and linked to other
  11. # EmType with special fields called relation_to_type fields
  12. #
  13. # @see EditorialModel::components::EmComponent
  14. # @todo sortcolumn handling
  15. class EmType(EmComponent):
  16. ranked_in = 'class_id'
  17. ## Instanciate a new EmType
  18. # @todo define and check types for icon and sortcolumn
  19. # @todo better check self.subordinates
  20. def __init__(self, model, uid, name, class_id, fields_list=None, superiors_list=None, icon='0', sortcolumn='rank', string=None, help_text=None, date_update=None, date_create=None, rank=None):
  21. self.class_id = class_id
  22. self.check_type('class_id', int)
  23. self.fields_list = fields_list if fields_list is not None else []
  24. self.check_type('fields_list', list)
  25. for field_uid in self.fields_list:
  26. if not isinstance(field_uid, int):
  27. raise AttributeError("Excepted fields_list to be a list of integers, but found a " + str(type(field_uid)) + " in it")
  28. self.superiors_list = superiors_list if superiors_list is not None else {}
  29. self.check_type('superiors_list', dict)
  30. for nature, superiors_uid in self.superiors_list.items():
  31. if nature not in [EmNature.PARENT, EmNature.TRANSLATION, EmNature.IDENTITY]:
  32. raise AttributeError("Nature '%s' of superior is not allowed !" % nature)
  33. if not isinstance(superiors_uid, list):
  34. raise AttributeError("Excepted superiors of nature '%s' to be an list !" % nature)
  35. for superior_uid in superiors_uid:
  36. if not isinstance(superior_uid, int):
  37. raise AttributeError("Excepted superiors_list of nature '%s' to be a list of integers, but found a '%s' in it" % str(type(superior_uid)))
  38. self.icon = icon
  39. self.sortcolumn = sortcolumn
  40. super(EmType, self).__init__(model=model, uid=uid, name=name, string=string, help_text=help_text, date_update=date_update, date_create=date_create, rank=rank)
  41. @classmethod
  42. ## Create a new EmType and instanciate it
  43. # @param name str: The name of the new type
  44. # @param em_class EmClass: The class that the new type will specialize
  45. # @param sortcolumn str : The name of the field that will be used to sort
  46. # @param **em_component_args : @ref EditorialModel::components::create()
  47. # @param cls
  48. # @return An EmType instance
  49. # @throw EmComponentExistError if an EmType with this name but different attributes exists
  50. # @see EmComponent::__init__()
  51. #
  52. # @todo check that em_class is an EmClass object (fieldtypes can handle it)
  53. def create(cls, name, em_class, sortcolumn='rank', **em_component_args):
  54. return super(EmType, cls).create(name=name, class_id=em_class.uid, sortcolumn=sortcolumn, **em_component_args)
  55. @property
  56. ## Return the EmClassType of the type
  57. # @return EditorialModel.classtypes.*
  58. def classtype(self):
  59. return getattr(EmClassType, self.em_class.classtype)
  60. @property
  61. ## Return an instance of the class this type belongs to
  62. # @return EditorialModel.EmClass
  63. def em_class(self):
  64. return self.model.component(self.class_id)
  65. ## @brief Delete an EmType
  66. # The deletion is only possible if a type is not linked by any EmClass
  67. # and if it has no subordinates
  68. # @return True if delete False if not deleted
  69. # @todo Check if the type is not linked by any EmClass
  70. # @todo Check if there is no other ''non-deletion'' conditions
  71. def delete_check(self):
  72. if len(self.subordinates()) > 0:
  73. return False
  74. #Delete all relation with superiors
  75. for nature, sups in self.superiors().items():
  76. for sup in sups:
  77. self.del_superior(sup, nature)
  78. return True
  79. ## Return selected optional field
  80. # @return A list of EmField instance
  81. def selected_fields(self):
  82. selected = [self.model.component(field_id) for field_id in self.fields_list]
  83. return selected
  84. ## Return the list of associated fields
  85. # @return A list of EmField instance
  86. def fields(self, relational=True):
  87. return [field for field in self.em_class.fields(relational) if not field.optional or (field.optional and field.uid in self.fields_list)]
  88. ## Select_field (Function)
  89. #
  90. # Indicates that an optional field is used
  91. #
  92. # @param field EmField: The optional field to select
  93. #
  94. # @throw TypeError if field is not an EmField instance
  95. # @throw ValueError if field is not optional or is not associated with this type
  96. # @throw MigrationHandlerChangeError if migration handler is not happy with the change
  97. # @see EmType::_change_field_list()
  98. def select_field(self, field):
  99. if field.uid in self.fields_list:
  100. return True
  101. self._change_field_list(field, True)
  102. ## Unselect_field (Function)
  103. #
  104. # Indicates that an optional field will not be used
  105. #
  106. # @param field EmField: The optional field to unselect
  107. #
  108. # @throw TypeError if field is not an EmField instance
  109. # @throw ValueError if field is not optional or is not associated with this type
  110. # @throw MigrationHandlerChangeError if migration handler is not happy with the change
  111. # @see EmType::_change_field_list()
  112. def unselect_field(self, field):
  113. if field.uid not in self.fields_list:
  114. return True
  115. self._change_field_list(field, False)
  116. ## @brief Select or unselect an optional field
  117. # @param field EmField: The EmField to select or unselect
  118. # @param select bool: If True select field, else unselect it
  119. #
  120. # @throw TypeError if field is not an EmField instance
  121. # @throw ValueError if field is not optional or is not associated with this type
  122. # @throw MigrationHandlerChangeError if migration handler is not happy with the change
  123. def _change_field_list(self, field, select=True):
  124. if not isinstance(field, EmField):
  125. raise TypeError("Excepted <class EmField> as field argument. But got " + str(type(field)))
  126. if field not in self.em_class.fields():
  127. raise ValueError("This field " + str(field) + "is not part of the type " + str(self))
  128. if not field.optional:
  129. raise ValueError("This field is not optional")
  130. try:
  131. if select:
  132. self.fields_list.append(field.uid)
  133. self.model.migration_handler.register_change(self.model, self.uid, None, {'fields_list': field.uid})
  134. else:
  135. self.fields_list.remove(field.uid)
  136. self.model.migration_handler.register_change(self.model, self.uid, {'fields_list': field.uid}, None)
  137. except MigrationHandlerChangeError as exception_object:
  138. if select:
  139. self.fields_list.remove(field.uid)
  140. else:
  141. self.fields_list.append(field.uid)
  142. raise exception_object
  143. self.model.migration_handler.register_model_state(self.model, hash(self.model))
  144. ## Get the list of associated hooks
  145. # @note Not conceptualized yet
  146. # @todo Conception
  147. def hooks(self):
  148. raise NotImplementedError()
  149. ## Add a new hook
  150. # @param hook EmHook: An EmHook instance
  151. # @throw TypeError
  152. # @note Not conceptualized yet
  153. # @todo Conception
  154. def add_hook(self, hook):
  155. raise NotImplementedError()
  156. ## Delete a hook
  157. # @param hook EmHook: An EmHook instance
  158. # @throw TypeError
  159. # @note Not conceptualized yet
  160. # @todo Conception
  161. # @todo Maybe we don't need a EmHook instance but just a hook identifier
  162. def del_hook(self, hook):
  163. raise NotImplementedError()
  164. ## @brief Get the list of subordinates EmType
  165. # Get a list of EmType instance that have this EmType for superior
  166. # @return Return a dict with relation nature as keys and values as a list of subordinates
  167. # EmType instance
  168. # @throw RuntimeError if a nature fetched from db is not valid
  169. def subordinates(self):
  170. subordinates = {}
  171. for em_type in self.model.components(EmType):
  172. for nature, superiors_uid in em_type.superiors_list.items():
  173. if self.uid in superiors_uid:
  174. if nature in subordinates:
  175. subordinates[nature].append(em_type)
  176. else:
  177. subordinates[nature] = [em_type]
  178. return subordinates
  179. ## @brief Get the list of superiors by relation's nature
  180. # Get a list of EmType that are superiors of this type
  181. # @return Return a dict with relation nature as keys and an EmType as value
  182. # @throw RuntimeError if a nature has multiple superiors
  183. def superiors(self):
  184. return {nature: [self.model.component(superior_uid) for superior_uid in superiors_uid] for nature, superiors_uid in self.superiors_list.items()}
  185. ## @brief Given a relation's nature return all the possible type to add as superiors
  186. # @param relation_nature str | None : if None check for all natures
  187. # @return a list or a dict with nature as key
  188. def possible_superiors(self, relation_nature=None):
  189. if relation_nature is None:
  190. ret = {}
  191. for nat in EmNature.getall():
  192. ret[nat] = self.possible_superiors(nat)
  193. return ret
  194. #One nature
  195. if relation_nature not in self.classtype['hierarchy']:
  196. return []
  197. att = self.classtype['hierarchy'][relation_nature]['attach']
  198. if att == 'type':
  199. return [self]
  200. else:
  201. return [t for t in self.model.components(EmType) if t.classtype == self.classtype]
  202. ## Add a superior in the type hierarchy
  203. # @param em_type EmType: An EmType instance
  204. # @param relation_nature str: The name of the relation's nature
  205. # @return False if no modification made, True if modifications success
  206. #
  207. # @throw TypeError when em_type not an EmType instance
  208. # @throw ValueError when relation_nature isn't reconized or not allowed for this type
  209. # @throw ValueError when relation_nature don't allow to link this types together
  210. def add_superior(self, em_type, relation_nature):
  211. # check if relation_nature is valid for this type
  212. if relation_nature not in EmClassType.natures(self.classtype['name']):
  213. raise ValueError("Invalid nature for add_superior : '" + relation_nature + "'. Allowed relations for this type are " + str(EmClassType.natures(self.classtype['name'])))
  214. if relation_nature in self.superiors_list and em_type.uid in self.superiors_list[relation_nature]:
  215. return True
  216. att = self.classtype['hierarchy'][relation_nature]['attach']
  217. if att == 'classtype':
  218. if self.classtype['name'] != em_type.classtype['name']:
  219. raise ValueError("Not allowed to put an em_type with a different classtype as superior")
  220. elif self.name != em_type.name:
  221. raise ValueError("Not allowed to put a different em_type as superior in a relation of nature '" + relation_nature + "'")
  222. self._change_superiors_list(em_type, relation_nature, True)
  223. ## Delete a superior in the type hierarchy
  224. # @param em_type EmType: An EmType instance
  225. # @param relation_nature str: The name of the relation's nature
  226. # @throw TypeError when em_type isn't an EmType instance
  227. # @throw ValueError when relation_nature isn't reconized or not allowed for this type
  228. def del_superior(self, em_type, relation_nature):
  229. if relation_nature not in self.superiors_list or em_type.uid not in self.superiors_list[relation_nature]:
  230. return True
  231. self._change_superiors_list(em_type, relation_nature, False)
  232. ## Apply changes to the superiors_list
  233. # @param em_type EmType: An EmType instance
  234. # @param relation_nature str: The name of the relation's nature
  235. # @param add bool: Add or delete relation
  236. def _change_superiors_list(self, em_type, relation_nature, add=True):
  237. # check instance of parameters
  238. if not isinstance(em_type, EmType) or not isinstance(relation_nature, str):
  239. raise TypeError("Excepted <class EmType> and <class str> as em_type argument. But got : " + str(type(em_type)) + " " + str(type(relation_nature)))
  240. try:
  241. if add:
  242. if relation_nature in self.superiors_list:
  243. self.superiors_list[relation_nature].append(em_type.uid)
  244. else:
  245. self.superiors_list[relation_nature] = [em_type.uid]
  246. self.model.migration_handler.register_change(self.model, self.uid, None, {'superiors_list': {relation_nature: em_type.uid}})
  247. else:
  248. self.superiors_list[relation_nature].remove(em_type.uid)
  249. if len(self.superiors_list[relation_nature]) == 0:
  250. del self.superiors_list[relation_nature]
  251. self.model.migration_handler.register_change(self.model, self.uid, {'superiors_list': {relation_nature: em_type.uid}}, None)
  252. # roll-back
  253. except MigrationHandlerChangeError as exception_object:
  254. if add:
  255. self.superiors_list[relation_nature].remove(em_type.uid)
  256. if len(self.superiors_list[relation_nature]) == 0:
  257. del self.superiors_list[relation_nature]
  258. else:
  259. if relation_nature in self.superiors_list:
  260. self.superiors_list[relation_nature].append(em_type.uid)
  261. else:
  262. self.superiors_list[relation_nature] = [em_type.uid]
  263. raise exception_object
  264. self.model.migration_handler.register_model_state(self.model, hash(self.model))
  265. ## Checks if the EmType is valid
  266. # @throw EmComponentCheckError if check fails
  267. def check(self):
  268. super(EmType, self).check()
  269. em_class = self.model.component(self.class_id)
  270. if not em_class:
  271. raise EmComponentCheckError("class_id contains an uid that does not exists '%d'" % self.class_id)
  272. if not isinstance(em_class, EditorialModel.classes.EmClass):
  273. raise EmComponentCheckError("class_id contains an uid from a component that is not an EmClass but a %s" % str(type(em_class)))
  274. for i, f_uid in enumerate(self.fields_list):
  275. field = self.model.component(f_uid)
  276. if not field:
  277. raise EmComponentCheckError("The element %d of selected_field is a non existing uid '%d'" % (i, f_uid))
  278. if not isinstance(field, EmField):
  279. raise EmComponentCheckError("The element %d of selected_field is not an EmField but a %s" % (i, str(type(field))))
  280. if not field.optional:
  281. raise EmComponentCheckError("The element %d of selected_field is an EmField not optional" % i)
  282. """
  283. if field.fieldgroup_id not in [fg.uid for fg in self.fieldgroups()]:
  284. raise EmComponentCheckError("The element %d of selected_field is an EmField that is part of an EmFieldGroup that is not associated with this EmType" % i)
  285. """
  286. for nature, superiors_uid in self.superiors_list.items():
  287. for superior_uid in superiors_uid:
  288. em_type = self.model.component(superior_uid)
  289. if not em_type:
  290. raise EmComponentCheckError("The superior is a non existing uid '%d'" % (superior_uid))
  291. if not isinstance(em_type, EmType):
  292. raise EmComponentCheckError("The superior is a component that is not an EmType but a %s" % (str(type(em_type))))
  293. if nature not in EmClassType.natures(self.em_class.classtype):
  294. raise EmComponentCheckError("The relation nature '%s' of the superior is not valid for this EmType classtype '%s'", (nature, self.classtype))
  295. nat_spec = getattr(EmClassType, self.em_class.classtype)['hierarchy'][nature]
  296. if nat_spec['attach'] == 'classtype':
  297. if self.classtype != em_type.classtype:
  298. raise EmComponentCheckError("The superior is of '%s' classtype. But the current type is of '%s' classtype, and relation nature '%s' require two EmType of same classtype" % (em_type.classtype, self.classtype, nature))
  299. elif nat_spec['attach'] == 'type':
  300. if self.uid != em_type.uid:
  301. raise EmComponentCheckError("The superior is a different EmType. But the relation nature '%s' require the same EmType" % (nature))
  302. else:
  303. raise NotImplementedError("The nature['attach'] '%s' is not implemented in this check !" % nat_spec['attach'])
  304. if 'max_depth' in nat_spec and nat_spec['max_depth'] > 0:
  305. depth = 1
  306. cur_type = em_type
  307. while depth >= nat_spec['max_depth']:
  308. depth += 1
  309. if len(cur_type.subordinates()[nature]) == 0:
  310. break
  311. else:
  312. raise EmComponentCheckError("The relation with superior %d has a depth superior than the maximum depth (%d) allowed by the relation's nature '%s'" % (superior_uid, nat_spec['max_depth'], nature))
  313. for nature in self.subordinates():
  314. nat_spec = getattr(EmClassType, self.em_class.classtype)['hierarchy'][nature]
  315. if 'max_child' in nat_spec and nat_spec['max_child'] > 0:
  316. if len(self.subordinates()[nature]) > nat_spec['max_child']:
  317. raise EmComponentCheckError("The EmType has more child than allowed in the relation's nature : %d > %d" % (len(self.subordinates()[nature], nat_spec['max_child'])))