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.

209 lines
7.9 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  2. import datetime
  3. import logging
  4. import os
  5. import odoo
  6. from odoo import api, fields, models, tools
  7. from odoo.osv import expression
  8. _logger = logging.getLogger(__name__)
  9. try:
  10. # We use a jinja2 sandboxed environment to render mako templates.
  11. # Note that the rendering does not cover all the mako syntax, in particular
  12. # arbitrary Python statements are not accepted, and not all expressions are
  13. # allowed: only "public" attributes (not starting with '_') of objects may
  14. # be accessed.
  15. # This is done on purpose: it prevents incidental or malicious execution of
  16. # Python code that may break the security of the server.
  17. from jinja2.sandbox import SandboxedEnvironment
  18. mako_template_env = SandboxedEnvironment(
  19. variable_start_string="${",
  20. variable_end_string="}",
  21. line_statement_prefix="%",
  22. trim_blocks=True, # do not output newline after blocks
  23. )
  24. mako_template_env.globals.update(
  25. {
  26. "str": str,
  27. "datetime": datetime,
  28. "len": len,
  29. "abs": abs,
  30. "min": min,
  31. "max": max,
  32. "sum": sum,
  33. "filter": filter,
  34. "map": map,
  35. "round": round,
  36. }
  37. )
  38. except ImportError:
  39. _logger.warning("jinja2 not available, templating features will not work!")
  40. class AttachmentSynchronizeTask(models.Model):
  41. _name = "attachment.synchronize.task"
  42. _description = "Attachment synchronize task"
  43. name = fields.Char(required=True)
  44. method_type = fields.Selection(
  45. [("import", "Import Task"), ("export", "Export Task")], required=True
  46. )
  47. pattern = fields.Char(
  48. string="Selection Pattern",
  49. help="Pattern used to select the files to be imported following the 'fnmatch' "
  50. "special characters (e.g. '*.txt' to catch all the text files).\n"
  51. "If empty, import all the files found in 'File Path'.",
  52. )
  53. filepath = fields.Char(
  54. string="File Path", help="Path to imported/exported files in the Backend"
  55. )
  56. backend_id = fields.Many2one("storage.backend", string="Backend")
  57. attachment_ids = fields.One2many("attachment.queue", "task_id", string="Attachment")
  58. move_path = fields.Char(
  59. string="Move Path", help="Imported File will be moved to this path"
  60. )
  61. new_name = fields.Char(
  62. string="New Name",
  63. help="Imported File will be renamed to this name.\n"
  64. "New Name can use 'mako' template where 'obj' is the original file's name.\n"
  65. "For instance : ${obj.name}-${obj.create_date}.csv",
  66. )
  67. after_import = fields.Selection(
  68. selection=[
  69. ("rename", "Rename"),
  70. ("move", "Move"),
  71. ("move_rename", "Move & Rename"),
  72. ("delete", "Delete"),
  73. ],
  74. help="Action after import a file",
  75. )
  76. file_type = fields.Selection(
  77. selection=[],
  78. string="File Type",
  79. help="Used to fill the 'File Type' field in the imported 'Attachments Queues'."
  80. "\nFurther operations will be realized on these Attachments Queues depending "
  81. "on their 'File Type' value.",
  82. )
  83. enabled = fields.Boolean("Enabled", default=True)
  84. avoid_duplicated_files = fields.Boolean(
  85. string="Avoid importing duplicated files",
  86. help="If checked, a file will not be imported if an Attachment Queue with the "
  87. "same name already exists.",
  88. )
  89. failure_emails = fields.Char(
  90. string="Failure Emails",
  91. help="Used to fill the 'Failure Emails' field in the 'Attachments Queues' "
  92. "related to this task.\nAn alert will be sent to these emails if any operation "
  93. "on these Attachment Queue's file type fails.",
  94. )
  95. def _prepare_attachment_vals(self, data, filename):
  96. self.ensure_one()
  97. vals = {
  98. "name": filename,
  99. "datas": data,
  100. "datas_fname": filename,
  101. "task_id": self.id,
  102. "file_type": self.file_type or False,
  103. }
  104. return vals
  105. @api.model
  106. def _template_render(self, template, record):
  107. try:
  108. template = mako_template_env.from_string(tools.ustr(template))
  109. except Exception:
  110. _logger.exception("Failed to load template '{}'".format(template))
  111. variables = {"obj": record}
  112. try:
  113. render_result = template.render(variables)
  114. except Exception:
  115. _logger.exception(
  116. "Failed to render template '{}'' using values '{}'".format(
  117. template, variables
  118. )
  119. )
  120. render_result = u""
  121. if render_result == u"False":
  122. render_result = u""
  123. return render_result
  124. @api.model
  125. def run_task_import_scheduler(self, domain=None):
  126. if domain is None:
  127. domain = []
  128. domain = expression.AND(
  129. [domain, [("method_type", "=", "import"), ("enabled", "=", True)]]
  130. )
  131. for task in self.search(domain):
  132. task.run_import()
  133. def run_import(self):
  134. self.ensure_one()
  135. attach_obj = self.env["attachment.queue"]
  136. backend = self.backend_id
  137. filepath = self.filepath or ""
  138. filenames = backend._list(relative_path=filepath, pattern=self.pattern)
  139. if self.avoid_duplicated_files:
  140. filenames = self._file_to_import(filenames)
  141. total_import = 0
  142. for file_name in filenames:
  143. with api.Environment.manage():
  144. with odoo.registry(self.env.cr.dbname).cursor() as new_cr:
  145. new_env = api.Environment(new_cr, self.env.uid, self.env.context)
  146. try:
  147. full_absolute_path = os.path.join(filepath, file_name)
  148. data = backend._get_b64_data(full_absolute_path)
  149. attach_vals = self._prepare_attachment_vals(data, file_name)
  150. attachment = attach_obj.with_env(new_env).create(attach_vals)
  151. new_full_path = False
  152. if self.after_import == "rename":
  153. new_name = self._template_render(self.new_name, attachment)
  154. new_full_path = os.path.join(filepath, new_name)
  155. elif self.after_import == "move":
  156. new_full_path = os.path.join(self.move_path, file_name)
  157. elif self.after_import == "move_rename":
  158. new_name = self._template_render(self.new_name, attachment)
  159. new_full_path = os.path.join(self.move_path, new_name)
  160. if new_full_path:
  161. backend._add_b64_data(new_full_path, data)
  162. if self.after_import in (
  163. "delete",
  164. "rename",
  165. "move",
  166. "move_rename",
  167. ):
  168. backend._delete(full_absolute_path)
  169. total_import += 1
  170. except Exception as e:
  171. new_env.cr.rollback()
  172. raise e
  173. else:
  174. new_env.cr.commit()
  175. _logger.info("Run import complete! Imported {0} files".format(total_import))
  176. def _file_to_import(self, filenames):
  177. imported = (
  178. self.env["attachment.queue"]
  179. .search([("name", "in", filenames)])
  180. .mapped("name")
  181. )
  182. return list(set(filenames) - set(imported))
  183. def run_export(self):
  184. for task in self:
  185. task.attachment_ids.filtered(lambda a: a.state == "pending").run()
  186. def button_toogle_enabled(self):
  187. for rec in self:
  188. rec.enabled = not rec.enabled
  189. def button_duplicate_record(self):
  190. self.ensure_one()
  191. self.copy({"enabled": False})