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.

217 lines
7.2 KiB

  1. # -*- coding: utf-8 -*-
  2. # © 2014 Serv. Tecnol. Avanzados (http://www.serviciosbaeza.com)
  3. # Pedro M. Baeza <pedro.baeza@serviciosbaeza.com>
  4. # © 2015 Antiun Ingeniería S.L. - Jairo Llopis
  5. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  6. import base64
  7. import urllib
  8. import os
  9. import logging
  10. from odoo import models, fields, api, exceptions, _
  11. from odoo import tools
  12. _logger = logging.getLogger(__name__)
  13. class Image(models.Model):
  14. _name = "base_multi_image.image"
  15. _order = "sequence, owner_model, owner_id, id"
  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. s.owner_ref_id = "{0.owner_model},{0.owner_id}".format(s)
  90. @api.multi
  91. @api.depends('storage', 'path', 'file_db_store', 'url')
  92. def _get_image(self):
  93. """Get image data from the right storage type."""
  94. for s in self:
  95. s.image_main = getattr(s, "_get_image_from_%s" % s.storage)()
  96. @api.multi
  97. @api.depends("owner_id", "owner_model")
  98. def _show_technical(self):
  99. """Know if you need to show the technical fields."""
  100. self.show_technical = all(
  101. "default_owner_%s" % f not in self.env.context
  102. for f in ("id", "model"))
  103. @api.multi
  104. def _get_image_from_filestore(self):
  105. return self.attachment_id.datas
  106. @api.multi
  107. def _get_image_from_db(self):
  108. return self.file_db_store
  109. @api.multi
  110. def _get_image_from_file(self):
  111. if self.path and os.path.exists(self.path):
  112. try:
  113. with open(self.path, 'rb') as f:
  114. return base64.b64encode(f.read())
  115. except Exception as e:
  116. _logger.error("Can not open the image %s, error : %s",
  117. self.path, e, exc_info=True)
  118. else:
  119. _logger.error("The image %s doesn't exist ", self.path)
  120. return False
  121. @api.multi
  122. def _get_image_from_url(self):
  123. return self._get_image_from_url_cached(self.url)
  124. @api.model
  125. @tools.ormcache("url")
  126. def _get_image_from_url_cached(self, url):
  127. """Allow to download an image and cache it by its URL."""
  128. if url:
  129. try:
  130. (filename, header) = urllib.urlretrieve(url)
  131. with open(filename, 'rb') as f:
  132. return base64.b64encode(f.read())
  133. except:
  134. _logger.error("URL %s cannot be fetched", url,
  135. exc_info=True)
  136. return False
  137. @api.multi
  138. @api.depends('image_main')
  139. def _get_image_sizes(self):
  140. for s in self:
  141. try:
  142. vals = tools.image_get_resized_images(
  143. s.with_context(bin_size=False).image_main)
  144. except:
  145. vals = {"image_medium": False,
  146. "image_small": False}
  147. s.update(vals)
  148. @api.model
  149. def _make_name_pretty(self, name):
  150. return name.replace('_', ' ').capitalize()
  151. @api.onchange('url')
  152. def _onchange_url(self):
  153. if self.url:
  154. filename = self.url.split('/')[-1]
  155. self.name, self.extension = os.path.splitext(filename)
  156. self.name = self._make_name_pretty(self.name)
  157. @api.onchange('path')
  158. def _onchange_path(self):
  159. if self.path:
  160. self.name, self.extension = os.path.splitext(os.path.basename(
  161. self.path))
  162. self.name = self._make_name_pretty(self.name)
  163. @api.onchange('filename')
  164. def _onchange_filename(self):
  165. if self.filename:
  166. self.name, self.extension = os.path.splitext(self.filename)
  167. self.name = self._make_name_pretty(self.name)
  168. @api.onchange('attachment_id')
  169. def _onchange_attachmend_id(self):
  170. if self.attachment_id:
  171. self.name = self.attachment_id.res_name
  172. @api.constrains('storage', 'url')
  173. def _check_url(self):
  174. if self.storage == 'url' and not self.url:
  175. raise exceptions.ValidationError(
  176. _('You must provide an URL for the image.'))
  177. @api.constrains('storage', 'path')
  178. def _check_path(self):
  179. if self.storage == 'file' and not self.path:
  180. raise exceptions.ValidationError(
  181. _('You must provide a file path for the image.'))
  182. @api.constrains('storage', 'file_db_store')
  183. def _check_store(self):
  184. if self.storage == 'db' and not self.file_db_store:
  185. raise exceptions.ValidationError(
  186. _('You must provide an attached file for the image.'))
  187. @api.constrains('storage', 'attachment_id')
  188. def _check_attachment_id(self):
  189. if self.storage == 'filestore' and not self.attachment_id:
  190. raise exceptions.ValidationError(
  191. _('You must provide an attachment for the image.'))