import ast import datetime import json import re import uuid import logging import base64 import subprocess import tempfile import easywebdav import os import os.path import requests import hashlib from PIL import Image,ImageDraw,ImageFont from odoo import api, fields, models, _ from odoo import tools from odoo.exceptions import ValidationError from datetime import date from datetime import datetime from dateutil.relativedelta import relativedelta from pyffmpeg import FFmpeg from tuya_iot import TuyaOpenAPI, TUYA_LOGGER from tuya_connector import TuyaOpenAPI, TUYA_LOGGER from webdav4.client import Client from webdav4.fsspec import WebdavFileSystem import sys TUYA_LOGGER.setLevel(logging.DEBUG) _logger = logging.getLogger(__name__) class dsscsvs(models.Model): @api.model def create(self,vals): result = super().create(vals) return result _name = "dss.csvs" _description = "DigitalSignage CSV Dateien" # _inherit = ['mail.thread','mail.activity.mixin'] _rec_name = "csv_id" # _inherit = ['mail.thread', 'mail.activity.mixin'] uuid = fields.Char(default=lambda self: self._default_uuid(), required=True, readonly=True, copy=False, string='UUID') date_create = fields.Date('Erstellungsdatum',default=lambda self: self._default_create_date()) date_lastedit = fields.Date('Änderungsdatum') user_create = fields.Char('Erstellungsuser',default=lambda self: self._default_create_user()) user_lastedit = fields.Char('Änderungsuser') csv_id = fields.Char(string="CSV ID", required=True, help = "Das ist eine eindeutige ID für diese Dateityp. Sie wird automatisch generiert und sollte nicht manuell geändert werden.", default=lambda self: self._default_csv_id()) csv_name = fields.Char(string="CSV Name", required=True, help = "Der Name des Dateityp, die Sie hochladen möchten. Dieser Name wird in der Benutzeroberfläche angezeigt.") csv_binary = fields.Binary(string="CSV Datei", help = "Die eigentliche Datei, die Sie hochladen möchten. Diese Datei wird in der Datenbank und Cloud gespeichert") csv_filename = fields.Char(string="CSV Dateiname", help = "Der Name der hochgeladenen Datei. Dies wird automatisch aus dem Dateinamen der hochgeladenen Datei abgerufen.",compute="_compute_csv_filename", store=True) csv_cloud_link = fields.Char(string="Cloud Link", help = "Der Link zur Datei in der Cloud, wenn die Datei in der Cloud gespeichert ist.") csv_description = fields.Text(string="CSV Beschreibung", help = "Eine Beschreibung der hochgeladenen Datei. Dies kann Informationen über den Inhalt der Datei, den Verwendungszweck oder andere relevante Details enthalten.") csv_attachment_id = fields.Many2one('ir.attachment', string="CSV Anhang", help = "Der Anhang, der mit dieser CSV-Datei verbunden ist. Dieser Anhang enthält die tatsächliche Datei, die in der Datenbank gespeichert ist.") csv_mailtext = fields.Text(string="CSV Mailtext", help = "Der Text, der in der E-Mail verwendet wird, wenn diese CSV-Datei per E-Mail versendet wird.") @api.depends('binary_binary') @api.onchange('binary_binary') def _compute_binary_filename(self): """Compute the binary filename based on the uploaded file.""" _logger.info("Binary getting Filename - B_"+str(self.id)) if len(self)>0: for record in self: if record.binary_binary: # Extract the filename from the binary content extension = 'jpg' # Default extension if isinstance(self.binary_binary, str): filedata = base64.b64decode(self.binary_binary.split("base64,")[-1]) else: filedata = base64.b64decode(self.binary_binary) filedataStr = str(filedata) _logger.info("Binary getting Filename - B_"+str(self.id)+" - ["+str(filedataStr[0:23])+"]") if filedataStr[0:12] == "b'\\x89PNG\\": extension = 'png' elif filedataStr[0:17] == "b'\\xff\\xd8\\xff": extension = 'jpg' elif filedataStr[0:8] == "b'GIF89a": extension = 'gif' elif filedataStr[0:22] == "b'\\x49\\x49\\x2a\\x00": extension = 'tif' elif filedataStr[0:27] == "b'\\x25\\x50\\x44\\x46\\x2d": extension = 'pdf' elif filedataStr[0:23] == "b'\\x00\\x00\\x00 ftypisom": extension = 'mp4' elif filedataStr[0:22] == "b'\\x00\\x00\\x00\\x18ftyp": extension = 'mp4' elif filedataStr[0:12] == "b'\\x42\\x4d": extension = 'bmp' record.binary_filename = f"{record.binary_mediatype.filepartname}{record.binary_contract.contract_auto_id}.{extension}" @api.model def _default_uuid(self): return str(uuid.uuid4()) @api.model def _default_create_date(self): return datetime.now().strftime('%Y-%m-%d %H:%M:%S') @api.model def _default_create_user(self): return self.env.user.name if self.env.user else 'System' @api.model def _default_binary_id(self): # Generate a unique binary ID based on the current timestamp and a random UUID return f"Datei_{str(uuid.uuid4())[:8]}" @api.onchange('binary_binary') def _onchange_binary_binary(self): """Update binary size and type when binary content is changed.""" _logger.info("New Binary B_"+str(self.id)) existing_binary = False if not self._origin.binary_binary: existing_binary = False else: existing_binary = True if not self.binary_contract.cloudlink: raise ValidationError(_("Der Vertrag hat keinen Cloudlink. Bitte überprüfen Sie den Vertrag.")) cloudpath = str(self.binary_contract.cloudlink) _logger.info("New Binary checking Cloudlink B_"+str(self.id)+" - "+str(cloudpath)) cloudpath = cloudpath + '/' + str(self.binary_mediatype.cloudlink) client = Client("https://cloud.logumedia.de/remote.php/dav/files/OdooDav/", auth=("odooClient@logumedia.de", "lm2020#OdooDav")) _logger.info("New Binary checking Cloudlink for cont : "+str(cloudpath)) cloudexists = False if client.exists(cloudpath): # Convert self.binary_binary to bytes-like object if isinstance(self.binary_binary, str): filedata = base64.b64decode(self.binary_binary.split("base64,")[-1]) else: filedata = base64.b64decode(self.binary_binary) filedataStr = str(filedata) _logger.info("New Binary Cloudlink exists - Saving File - B_"+str(self.id)+" - "+str(cloudpath)+" - " + str(filedata)+' - ' + filedataStr[0:8]) try: decoded_data = filedata except base64.binascii.Error as e: if 'Incorrect padding' in str(e): filedataStr += b'=' * (4 - len(filedataStr) % 4) decoded_data = base64.b64decode(filedataStr) if filedataStr[0:10] == "b'\x89PNG": extension = 'png' else: extension = 'jpg' _logger.info("New Binary Cloudlink exists - Saving File - B_"+str(self.id)+" - "+str(cloudpath)+" - " + str(base64.b64encode(decoded_data))) ##infile = open('/tmp/'+filename, "w+b") #infile = tempfile.NamedTemporaryFile(delete=False) ##infile.write(filedata) ##client.upload_fileobj(infile, f"{cloudpath}/{filename}") ##infile.close() ##infile.delete() # Delete the temporary file after upload cloudexists = True else: _logger.info("New Binary Cloudlink does not exists B_"+str(self.id)+" - "+str(cloudpath)) raise ValidationError(_("Der Cloudlink im Vertrag ist nicht vorhanden. Bitte überprüfen Sie den Link ggf. anlegen.")) if cloudexists: if self.binary_binary: # Calculate the size of the binary content self.binary_size = len(filedata) # Determine the file type based on the binary content self.binary_cloud_link = f"{cloudpath}/{self.binary_filename}" self.binary_issaved = True else: self.binary_size = 0 self.binary_type = '' self.binary_cloud_link = '' self.binary_issaved = False def dload(self): """Download the binary file.""" if not self.binary_binary: raise ValidationError(_("No binary file to download.")) # Create a temporary file to store the downloaded content temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=self.binary_filename) temp_file.write(base64.b64decode(self.binary_binary)) temp_file.close() # Return the file for download return { 'type': 'ir.actions.act_url', 'url': f'/web/content/{self.id}/{self.binary_filename}?download=true&filename={self.binary_filename}', 'target': 'self', } def dedit(self): """Edit the binary file.""" return { 'type': 'ir.actions.act_window', 'res_model': 'dss.binaries', 'view_mode': 'form', 'res_id': self.id, 'target': 'new', } def resend(self): """Resend the binary file to the contract's partner email.""" if not self.binary_contract: raise ValidationError(_("No contract associated with this binary file.")) if not self.binary_contract.project.standortpartner: raise ValidationError(_("No partner associated with the contract.")) if not self.binary_contract.project.standortpartner.email: raise ValidationError(_("The partner does not have an email address.")) # Send email with the binary file as attachment def genstamp(self): _logger.info('Create Stamps for Binary: B_'+str(self.id)+' '+str(id)) # Bildparameter definieren Medium = self.binary_mediatype if Medium and Medium.can_gen_stamp and self.binary_contract: _logger.info('Create Stamps for Contract : B_'+str(self.id)+' - Contract: C_'+str(self.binary_contract.id)+'- Medium : M_'+str(Medium.id)+' - '+str(Medium.medianame)) # Bildgröße und Hintergrundfarbe festlegen background_color = (255, 255, 255) # Weißer Hintergrund (RGB) # Neues Bild erstellen maxh = Medium.maxsize_h maxw = Medium.maxsize_w image = Image.new("RGB", (maxw, maxh), background_color) d = ImageDraw.Draw(image) Zeilen = Medium.maxsize_h // 6 zposy = 2 if self.binary_contract.client: text = self.binary_contract.contact_company_name _logger.info('Create Stamps for Contract : B_'+str(self.id)+' - Contract: C_'+str(self.binary_contract.id)+'- Medium : M_'+str(Medium.id)+' - Text1: '+str(text)) if len(text) > 35: # Trenne den Text an einem Leerzeichen nahe bei Position 40 idx = text.rfind(' ', 0, 35) if idx == -1: idx = 35 text2 = text[:idx] bcount = len(text2) if text2 else 1 fonts = maxw // (bcount ) *1.8 if fonts > maxh: fonts = maxh - 10 if fonts <=0 : fonts = 10 # font = ImageFont.truetype("addons/web/static/fonts/sign/Zeyada-Regular.ttf", fonts) font = ImageFont.truetype("addons/web/static/fonts/google/Open_Sans/Open_Sans-SemiBold.ttf", fonts) d.text((maxw // 2, 1*Zeilen), text2, fill="black",align="center", anchor="ms", font=font) # Beispiel: Ein Rechteck text = text[idx:] zposy = 2 if len(text) > 35: # Trenne den Text an einem Leerzeichen nahe bei Position 40 idx = text.rfind(' ', 0, 35) if idx == -1: idx = 35 text2 = text[:idx] # font = ImageFont.truetype("addons/web/static/fonts/sign/Zeyada-Regular.ttf", fonts) font = ImageFont.truetype("addons/web/static/fonts/google/Open_Sans/Open_Sans-SemiBold.ttf", fonts) d.text((maxw // 2, zposy*Zeilen), text2, fill="black",align="center", anchor="ms", font=font) # Beispiel: Ein Rechteck text = text[idx:] zposy = 3 else: bcount = len(text) if text else 1 fonts = maxw // (bcount ) *1.8 if fonts > maxh: fonts = maxh - 10 if fonts <=0 : fonts = 10 # font = ImageFont.truetype("addons/web/static/fonts/sign/Zeyada-Regular.ttf", fonts) font = ImageFont.truetype("addons/web/static/fonts/google/Open_Sans/Open_Sans-SemiBold.ttf", fonts) d.text((maxw // 2, zposy*Zeilen), text, fill="black",align="center", anchor="ms", font=font) # Beispiel: Ein Rechteck text = self.binary_contract.contact_street bcount = len(text) if text else 1 Zeilen = maxh // 6 fonts = maxw // (bcount ) *1.2 if fonts > maxh: fonts = maxh - 10 if fonts <=0 : fonts = 10 # font = ImageFont.truetype("addons/web/static/fonts/sign/Zeyada-Regular.ttf", fonts) font = ImageFont.truetype("addons/web/static/fonts/google/Open_Sans/Open_Sans-Regular.ttf", fonts) d.text((maxw // 2, 4*Zeilen), text, fill="black",align="center", anchor="ms", font=font) # Beispiel: Ein Rechteck text = self.binary_contract.contact_zip+' '+self.binary_contract.contact_city bcount = len(text) if text else 1 Zeilen = maxh // 6 fonts = maxw // (bcount ) *1.2 if fonts > maxh: fonts = maxh - 10 if fonts <=0 : fonts = 10 # font = ImageFont.truetype("addons/web/static/fonts/sign/Zeyada-Regular.ttf", fonts) font = ImageFont.truetype("addons/web/static/fonts/google/Open_Sans/Open_Sans-Regular.ttf", fonts) d.text((maxw // 2, 5*Zeilen), text, fill="black",align="center", anchor="ms", font=font) # Beispiel: Ein Rechteck # Bild speichern filename = Medium.filepartname+self.binary_contract.contract_auto_id+".png" _logger.info('Create Stamps for Contract : B_'+str(self.id)+' - Contract: C_'+str(self.binary_contract.id)+'- Feld : F_'+str(Medium.id)+' - Filename: '+str(filename)) image.save(filename) _logger.info("Stamp file created "+str(filename)) _logger.info("Copy file to Binaries B_"+str(self)) try: self.binary_binary = base64.b64encode(open(filename, "rb").read()) self.binary_filename = filename self.binary_type = 'image/png' self.binary_size = os.path.getsize(filename) self.binary_issaved = False self.binary_secured_ro = False self.binary_used_ro = False finally: _logger.info("Binary inserted "+str(self)) cloudpath = self.binary_contract.cloudlink if not cloudpath: raise ValidationError(_("Der Vertrag hat keinen Cloudlink. Bitte überprüfen Sie den Vertrag.")) if not Medium.cloudlink: raise ValidationError(_("Der Mediatyp hat keinen Cloudlink. Bitte überprüfen Sie den Mediatyp.")) cloudpath = cloudpath + '/' + str(Medium.cloudlink) _logger.info("Copy file to Cloud Path : "+str(cloudpath)) try: client = Client("https://cloud.logumedia.de/remote.php/dav/files/OdooDav/", auth=("odooClient@logumedia.de", "lm2020#OdooDav")) try: #client.mkdir(new_folder) #_logger.info("Make Cloud Path : "+str(new_folder)) client.upload_file(filename, f"{cloudpath}/{filename}") except Exception as e: _logger.info("Make Cloud Path error : "+str(e)) finally: self.binary_issaved = True _logger.info("File copied to Cloud Path : "+str(cloudpath)) return True