base.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. import datetime
  2. from functools import reduce
  3. import logging
  4. from typing import Any, Type
  5. from flask_babel import lazy_gettext
  6. from .filters import BaseFilterConverter, Filters
  7. try:
  8. import enum
  9. _has_enum = True
  10. except ImportError:
  11. _has_enum = False
  12. log = logging.getLogger(__name__)
  13. class BaseInterface:
  14. """
  15. Base class for all data model interfaces.
  16. Sub class it to implement your own interface for some data engine.
  17. """
  18. filter_converter_class = Type[BaseFilterConverter]
  19. """ when sub classing override with your own custom filter converter """
  20. """ Messages to display on CRUD Events """
  21. add_row_message = lazy_gettext("Added Row")
  22. edit_row_message = lazy_gettext("Changed Row")
  23. delete_row_message = lazy_gettext("Deleted Row")
  24. delete_integrity_error_message = lazy_gettext(
  25. "Associated data exists, please delete them first"
  26. )
  27. add_integrity_error_message = lazy_gettext(
  28. "Integrity error, probably unique constraint"
  29. )
  30. edit_integrity_error_message = lazy_gettext(
  31. "Integrity error, probably unique constraint"
  32. )
  33. general_error_message = lazy_gettext("General Error")
  34. database_error_message = lazy_gettext("Database Error")
  35. """ Tuple with message and text with severity type ex: ("Added Row", "info") """
  36. message = ()
  37. def __init__(self, obj: Type[Any]):
  38. self.obj = obj
  39. def __getattr__(self, name: str) -> Any:
  40. """
  41. Make mypy happy about the injected filters like self.datamodel.FilterEqual
  42. https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html#when-you-re-puzzled-or-when-things-are-complicated
  43. """
  44. return super().__getattr__(name)
  45. def _get_attr(self, col_name):
  46. if not hasattr(self.obj, col_name):
  47. # it's an inner obj attr
  48. try:
  49. _obj = self.obj
  50. for i in col_name.split("."):
  51. try:
  52. _obj = self.get_related_model(i)
  53. except Exception:
  54. _obj = getattr(_obj, i)
  55. return _obj
  56. except Exception:
  57. return None
  58. return getattr(self.obj, col_name)
  59. @staticmethod
  60. def _get_attr_value(item, col):
  61. if not hasattr(item, col):
  62. # it's an inner obj attr
  63. try:
  64. return reduce(getattr, col.split("."), item)
  65. except Exception:
  66. return ""
  67. if hasattr(getattr(item, col), "__call__"):
  68. # its a function
  69. return getattr(item, col)()
  70. else:
  71. # its an attribute
  72. value = getattr(item, col)
  73. # if value is an Enum instance than list and show widgets should display
  74. # its .value rather than its .name:
  75. if _has_enum and isinstance(value, enum.Enum):
  76. return value.value
  77. return value
  78. def get_filters(self, search_columns=None, search_filters=None):
  79. search_columns = search_columns or []
  80. return Filters(
  81. self.filter_converter_class,
  82. self,
  83. search_columns=search_columns,
  84. search_filters=search_filters,
  85. )
  86. def get_values_item(self, item, show_columns):
  87. return [self._get_attr_value(item, col) for col in show_columns]
  88. def _get_values(self, lst, list_columns):
  89. """
  90. Get Values: formats values for list template.
  91. returns [{'col_name':'col_value',....},{'col_name':'col_value',....}]
  92. :param lst:
  93. The list of item objects from query
  94. :param list_columns:
  95. The list of columns to include
  96. """
  97. retlst = []
  98. for item in lst:
  99. retdict = {}
  100. for col in list_columns:
  101. retdict[col] = self._get_attr_value(item, col)
  102. retlst.append(retdict)
  103. return retlst
  104. def get_values(self, lst, list_columns):
  105. """
  106. Get Values: formats values for list template.
  107. returns [{'col_name':'col_value',....},{'col_name':'col_value',....}]
  108. :param lst:
  109. The list of item objects from query
  110. :param list_columns:
  111. The list of columns to include
  112. """
  113. for item in lst:
  114. retdict = {}
  115. for col in list_columns:
  116. retdict[col] = self._get_attr_value(item, col)
  117. yield retdict
  118. def get_values_json(self, lst, list_columns):
  119. """
  120. Converts list of objects from query to JSON
  121. """
  122. result = []
  123. for item in self.get_values(lst, list_columns):
  124. for key, value in list(item.items()):
  125. if isinstance(value, datetime.datetime) or isinstance(
  126. value, datetime.date
  127. ):
  128. value = value.isoformat()
  129. item[key] = value
  130. if isinstance(value, list):
  131. item[key] = [str(v) for v in value]
  132. result.append(item)
  133. return result
  134. """
  135. Returns the models class name
  136. useful for auto title on views
  137. """
  138. @property
  139. def model_name(self):
  140. return self.obj.__class__.__name__
  141. """
  142. Next methods must be overridden
  143. """
  144. def query(
  145. self,
  146. filters=None,
  147. order_column="",
  148. order_direction="",
  149. page=None,
  150. page_size=None,
  151. ):
  152. pass
  153. def is_image(self, col_name):
  154. return False
  155. def is_file(self, col_name):
  156. return False
  157. def is_gridfs_file(self, col_name):
  158. return False
  159. def is_gridfs_image(self, col_name):
  160. return False
  161. def is_string(self, col_name):
  162. return False
  163. def is_text(self, col_name):
  164. return False
  165. def is_binary(self, col_name):
  166. return False
  167. def is_integer(self, col_name):
  168. return False
  169. def is_numeric(self, col_name):
  170. return False
  171. def is_float(self, col_name):
  172. return False
  173. def is_boolean(self, col_name):
  174. return False
  175. def is_date(self, col_name):
  176. return False
  177. def is_datetime(self, col_name):
  178. return False
  179. def is_enum(self, col_name):
  180. return False
  181. def is_relation(self, prop):
  182. return False
  183. def is_relation_col(self, col):
  184. return False
  185. def is_relation_many_to_one(self, prop):
  186. return False
  187. def is_relation_many_to_many(self, prop):
  188. return False
  189. def is_relation_one_to_one(self, prop):
  190. return False
  191. def is_relation_one_to_many(self, prop):
  192. return False
  193. def is_nullable(self, col_name):
  194. return True
  195. def is_unique(self, col_name):
  196. return False
  197. def is_pk(self, col_name):
  198. return False
  199. def is_pk_composite(self):
  200. raise False
  201. def is_fk(self, col_name):
  202. return False
  203. def get_max_length(self, col_name):
  204. return -1
  205. def get_min_length(self, col_name):
  206. return -1
  207. """
  208. -----------------------------------------
  209. FUNCTIONS FOR CRUD OPERATIONS
  210. -----------------------------------------
  211. """
  212. def add(self, item):
  213. """
  214. Adds object
  215. """
  216. raise NotImplementedError
  217. def edit(self, item):
  218. """
  219. Edit (change) object
  220. """
  221. raise NotImplementedError
  222. def delete(self, item):
  223. """
  224. Deletes object
  225. """
  226. raise NotImplementedError
  227. def get_col_default(self, col_name):
  228. pass
  229. def get_keys(self, lst):
  230. """
  231. return a list of pk values from object list
  232. """
  233. pk_name = self.get_pk_name()
  234. if self.is_pk_composite():
  235. return [[getattr(item, pk) for pk in pk_name] for item in lst]
  236. else:
  237. return [getattr(item, pk_name) for item in lst]
  238. def get_pk_name(self):
  239. """
  240. Returns the primary key name
  241. """
  242. raise NotImplementedError
  243. def get_pk_value(self, item):
  244. pk_name = self.get_pk_name()
  245. if self.is_pk_composite():
  246. return [getattr(item, pk) for pk in pk_name]
  247. else:
  248. return getattr(item, pk_name)
  249. def get(self, pk, filter=None):
  250. """
  251. return the record from key, you can optionally pass filters
  252. if pk exits on the db but filters exclude it it will return none.
  253. """
  254. pass
  255. def get_related_model(self, prop):
  256. raise NotImplementedError
  257. def get_related_interface(self, col_name):
  258. """
  259. Returns a BaseInterface for the related model
  260. of column name.
  261. :param col_name: Column name with relation
  262. :return: BaseInterface
  263. """
  264. raise NotImplementedError
  265. def get_related_obj(self, col_name, value):
  266. raise NotImplementedError
  267. def get_related_fk(self, model):
  268. raise NotImplementedError
  269. def get_columns_list(self):
  270. """
  271. Returns a list of all the columns names
  272. """
  273. return []
  274. def get_user_columns_list(self):
  275. """
  276. Returns a list of user viewable columns names
  277. """
  278. return self.get_columns_list()
  279. def get_search_columns_list(self):
  280. """
  281. Returns a list of searchable columns names
  282. """
  283. return []
  284. def get_order_columns_list(self, list_columns=None):
  285. """
  286. Returns a list of order columns names
  287. """
  288. return []
  289. def get_relation_fk(self, prop):
  290. pass