Sin descripción
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

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