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.

222 lines
7.5 KiB

  1. # © 2014 Serv. Tecnol. Avanzados (http://www.serviciosbaeza.com)
  2. # Pedro M. Baeza <pedro.baeza@serviciosbaeza.com>
  3. # © 2015 Antiun Ingeniería S.L. - Jairo Llopis
  4. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  5. import base64
  6. from urllib.request import urlretrieve
  7. import os
  8. import logging
  9. from odoo import models, fields, api, exceptions, _
  10. from odoo import tools
  11. _logger = logging.getLogger(__name__)
  12. class Image(models.Model):
  13. _name = "base_multi_image.image"
  14. _order = "sequence, owner_model, owner_id, id"
  15. _description = """ image model for multiple image functionality """
  16. _sql_constraints = [
  17. ('uniq_name_owner', 'UNIQUE(owner_id, owner_model, name)',
  18. _('A document can have only one image with the same name.')),
  19. ]
  20. owner_id = fields.Integer(
  21. "Owner",
  22. required=True,
  23. ondelete="cascade", # This Integer is really a split Many2one
  24. )
  25. owner_model = fields.Char(
  26. required=True)
  27. owner_ref_id = fields.Reference(
  28. selection="_selection_owner_ref_id",
  29. string="Referenced Owner",
  30. compute="_compute_owner_ref_id",
  31. store=True,
  32. )
  33. storage = fields.Selection(
  34. [('url', 'URL'), ('file', 'OS file'), ('db', 'Database'),
  35. ('filestore', 'Filestore')],
  36. required=True)
  37. name = fields.Char(
  38. 'Image title',
  39. translate=True)
  40. filename = fields.Char()
  41. extension = fields.Char(
  42. 'File extension',
  43. readonly=True)
  44. attachment_id = fields.Many2one(
  45. 'ir.attachment',
  46. string='Attachment',
  47. domain="[('index_content', '=', 'image')]")
  48. file_db_store = fields.Binary(
  49. 'Image stored in database',
  50. filters='*.png,*.jpg,*.gif')
  51. path = fields.Char(
  52. "Image path",
  53. help="Image path")
  54. url = fields.Char(
  55. 'Image remote URL')
  56. image_main = fields.Binary(
  57. "Full-sized image",
  58. compute="_get_image")
  59. image_medium = fields.Binary(
  60. "Medium-sized image",
  61. compute="_get_image_sizes",
  62. help="Medium-sized image. It is automatically resized as a "
  63. "128 x 128 px image, with aspect ratio preserved, only when the "
  64. "image exceeds one of those sizes. Use this field in form views "
  65. "or kanban views.")
  66. image_small = fields.Binary(
  67. "Small-sized image",
  68. compute="_get_image_sizes",
  69. help="Small-sized image. It is automatically resized as a 64 x 64 px "
  70. "image, with aspect ratio preserved. Use this field anywhere a "
  71. "small image is required.")
  72. comments = fields.Text(
  73. 'Comments',
  74. translate=True)
  75. sequence = fields.Integer(
  76. default=10)
  77. show_technical = fields.Boolean(
  78. compute="_show_technical")
  79. @api.model
  80. @tools.ormcache("self")
  81. def _selection_owner_ref_id(self):
  82. """Allow any model; after all, this field is readonly."""
  83. return [(r.model, r.name) for r in self.env["ir.model"].search([])]
  84. @api.multi
  85. @api.depends("owner_model", "owner_id")
  86. def _compute_owner_ref_id(self):
  87. """Get a reference field based on the split model and id fields."""
  88. for s in self:
  89. if s.owner_model:
  90. s.owner_ref_id = "{0.owner_model},{0.owner_id}".format(s)
  91. @api.multi
  92. @api.depends('storage', 'path', 'file_db_store', 'url')
  93. def _get_image(self):
  94. """Get image data from the right storage type."""
  95. for s in self:
  96. s.image_main = getattr(s, "_get_image_from_%s" % s.storage)()
  97. @api.multi
  98. @api.depends("owner_id", "owner_model")
  99. def _show_technical(self):
  100. """Know if you need to show the technical fields."""
  101. self.show_technical = all(
  102. "default_owner_%s" % f not in self.env.context
  103. for f in ("id", "model"))
  104. @api.multi
  105. def _get_image_from_filestore(self):
  106. return self.attachment_id.datas
  107. @api.multi
  108. def _get_image_from_db(self):
  109. return self.file_db_store
  110. @api.multi
  111. def _get_image_from_file(self):
  112. if self.path and os.path.exists(self.path):
  113. try:
  114. with open(self.path, 'rb') as f:
  115. return base64.b64encode(f.read())
  116. except Exception as e:
  117. _logger.error("Can not open the image %s, error : %s",
  118. self.path, e, exc_info=True)
  119. else:
  120. _logger.error("The image %s doesn't exist ", self.path)
  121. return False
  122. @api.multi
  123. def _get_image_from_url(self):
  124. return self._get_image_from_url_cached(self.url)
  125. @api.model
  126. @tools.ormcache("url")
  127. def _get_image_from_url_cached(self, url):
  128. """Allow to download an image and cache it by its URL."""
  129. if url:
  130. try:
  131. (filename, header) = urlretrieve(url)
  132. with open(filename, 'rb') as f:
  133. return base64.b64encode(f.read())
  134. except:
  135. _logger.error("URL %s cannot be fetched", url,
  136. exc_info=True)
  137. return False
  138. @api.multi
  139. @api.depends('image_main')
  140. def _get_image_sizes(self):
  141. for s in self:
  142. try:
  143. vals = tools.image_get_resized_images(
  144. s.with_context(bin_size=False).image_main)
  145. except:
  146. vals = {"image_medium": False,
  147. "image_small": False}
  148. s.update(vals)
  149. @api.model
  150. def _make_name_pretty(self, name):
  151. return name.replace('_', ' ').capitalize()
  152. @api.onchange('url')
  153. def _onchange_url(self):
  154. if self.url:
  155. filename = self.url.split('/')[-1]
  156. self.name, self.extension = os.path.splitext(filename)
  157. self.name = self._make_name_pretty(self.name)
  158. @api.onchange('path')
  159. def _onchange_path(self):
  160. if self.path:
  161. self.name, self.extension = os.path.splitext(os.path.basename(
  162. self.path))
  163. self.name = self._make_name_pretty(self.name)
  164. @api.onchange('filename')
  165. def _onchange_filename(self):
  166. if self.filename:
  167. self.name, self.extension = os.path.splitext(self.filename)
  168. self.name = self._make_name_pretty(self.name)
  169. @api.onchange('attachment_id')
  170. def _onchange_attachmend_id(self):
  171. if self.attachment_id:
  172. self.name = self.attachment_id.res_name
  173. @api.constrains('storage', 'url')
  174. def _check_url(self):
  175. for record in self:
  176. if record.storage == 'url' and not record.url:
  177. raise exceptions.ValidationError(
  178. _('You must provide an URL for the image.'))
  179. @api.constrains('storage', 'path')
  180. def _check_path(self):
  181. for record in self:
  182. if record.storage == 'file' and not record.path:
  183. raise exceptions.ValidationError(
  184. _('You must provide a file path for the image.'))
  185. @api.constrains('storage', 'file_db_store')
  186. def _check_store(self):
  187. for record in self:
  188. if record.storage == 'db' and not record.file_db_store:
  189. raise exceptions.ValidationError(
  190. _('You must provide an attached file for the image.'))
  191. @api.constrains('storage', 'attachment_id')
  192. def _check_attachment_id(self):
  193. for record in self:
  194. if record.storage == 'filestore' and not record.attachment_id:
  195. raise exceptions.ValidationError(
  196. _('You must provide an attachment for the image.'))