diff --git a/product_import/README.rst b/product_import/README.rst index 9a770b813..dca56bfae 100644 --- a/product_import/README.rst +++ b/product_import/README.rst @@ -1,3 +1,7 @@ +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + Product Image from URL ====================== Product Image from URL and Path @@ -10,13 +14,22 @@ Company ------- * `Cybrosys Techno Solutions `__ +License +------- +GNU AFFERO GENERAL PUBLIC LICENSE Version 3 (AGPL v3) +(https://www.odoo.com/documentation/user/16.0/legal/licenses/licenses.html) + Credits ------- -* Developers: Mohammed Shahil MP @cybrosys, odoo@cybrosys.com - V14 Minhaj T @cybrosys, odoo@cybrosys.com - V15 Minhaj T @cybrosys, odoo@cybrosys.com - V16 Amaya Aravind EV @cybrosys, odoo@cybrosys.com +* Developers: Mohammed Shahil MP @cybrosys, Contact: odoo@cybrosys.com + (V14) Minhaj T @cybrosys, Contact: odoo@cybrosys.com + (V15) Minhaj T @cybrosys, Contact: odoo@cybrosys.com + (V16) Amaya Aravind EV @cybrosys, Contact: odoo@cybrosys.com +Contacts +-------- +* Mail Contact : odoo@cybrosys.com +* Website : https://cybrosys.com Bug Tracker ----------- @@ -34,5 +47,3 @@ For support and more information, please visit `Our Website `__ - - diff --git a/product_import/__init__.py b/product_import/__init__.py index a65bb6e3b..370a5f6b4 100644 --- a/product_import/__init__.py +++ b/product_import/__init__.py @@ -19,5 +19,5 @@ # If not, see . # ############################################################################# -from . import wizard from . import models +from . import wizard diff --git a/product_import/__manifest__.py b/product_import/__manifest__.py index 17ceda7eb..639860acd 100644 --- a/product_import/__manifest__.py +++ b/product_import/__manifest__.py @@ -19,26 +19,29 @@ # If not, see . # ############################################################################# - { 'name': 'Product Image from URL', + 'version': '16.0.1.1.1', + 'category': 'Sales', 'summary': 'Product Images from Web URL and Path', - 'version': '16.0.1.0.0', - 'description': """Product Images from Web URL, Product Images from path, local""", + 'description': 'users can effortlessly import images by providing a web ' + 'URL, ensuring swift retrieval from online sources. ' + 'Simultaneously, users can opt for a local import, simply ' + 'specifying the file path.', 'author': 'Cybrosys Techno Solutions', 'company': 'Cybrosys Techno Solutions', 'maintainer': 'Cybrosys Techno Solutions', 'website': 'https://www.cybrosys.com', - 'category': 'Sales', - 'license': 'AGPL-3', - 'images': ['static/description/banner.png'], - 'depends': ['sale_management', 'stock'], + 'depends': ['sale_management'], 'data': [ - 'security/ir.model.access.csv', - 'views/product_url.xml', - 'wizard/product_import.xml', - ], + 'security/ir.model.access.csv', + 'views/product_product_views.xml', + 'views/product_template_views.xml', + 'wizard/product_import_views.xml', + ], + 'images': ['static/description/banner.png'], + 'license': 'AGPL-3', 'installable': True, - 'application': False, 'auto_install': False, + 'application': False, } diff --git a/product_import/models/__init__.py b/product_import/models/__init__.py index dea9a8296..a95b0d2d9 100644 --- a/product_import/models/__init__.py +++ b/product_import/models/__init__.py @@ -19,4 +19,5 @@ # If not, see . # ############################################################################# -from . import product_url +from . import product_product +from . import product_template diff --git a/product_import/models/product_product.py b/product_import/models/product_product.py new file mode 100644 index 000000000..6a58e8a74 --- /dev/null +++ b/product_import/models/product_product.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2019-TODAY Cybrosys Technologies(). +# Author: Mohammed Shahil MP @cybrosys(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# +import base64 +import certifi +import urllib3 +from odoo import api, fields, models, _ +from odoo.exceptions import UserError + + +class ProductProduct(models.Model): + """Inherit the model to add fields and function""" + _inherit = 'product.product' + + image_url = fields.Char(string='Image URL', help='Image URL or Path') + image_added = fields.Binary("Image (1920x1920)", + compute='_compute_image_added', store=True) + + @api.depends('image_url') + def _compute_image_added(self): + """ Function to load an image from URL or local file path """ + image = False + if self.image_url: + if self.image_url.startswith(('http://', 'https://')): + # Load image from URL + try: + http = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', + ca_certs=certifi.where()) + image_response = http.request('GET', self.image_url) + image = base64.b64encode(image_response.data) + except Exception as e: + # Handle URL loading errors + raise UserError( + _(f"Error loading image from URL: {str(e)}")) + else: + # Load image from local file path + try: + with open(self.image_url, 'rb') as image_file: + image = base64.b64encode(image_file.read()) + except Exception as e: + # Handle local file loading errors + raise UserError( + _(f"Error loading image from local path: {str(e)}")) + image_added = image + if image_added: + self.image_1920 = image_added diff --git a/product_import/models/product_url.py b/product_import/models/product_template.py similarity index 55% rename from product_import/models/product_url.py rename to product_import/models/product_template.py index 6d0fcf755..00d900c3a 100644 --- a/product_import/models/product_url.py +++ b/product_import/models/product_template.py @@ -19,16 +19,18 @@ # If not, see . # ############################################################################# -import requests import base64 -from odoo import models, fields, api, _ +import certifi +import urllib3 +from odoo import api, fields, models, _ from odoo.exceptions import UserError -class ProductImage(models.Model): +class ProductTemplate(models.Model): + """Inherit the model to add fields and function""" _inherit = 'product.template' - image_url = fields.Char(string='Image URL') + image_url = fields.Char(string='Image URL', help='Image URL or Path') image_added = fields.Binary("Image (1920x1920)", compute='_compute_image_added', store=True) @@ -40,45 +42,14 @@ class ProductImage(models.Model): if self.image_url.startswith(('http://', 'https://')): # Load image from URL try: - image = base64.b64encode( - requests.get(self.image_url).content) + http = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', + ca_certs=certifi.where()) + image_response = http.request('GET', self.image_url) + image = base64.b64encode(image_response.data) except Exception as e: # Handle URL loading errors - raise UserError(_(f"Error loading image from URL: {str(e)}")) - else: - # Load image from local file path - try: - with open(self.image_url, 'rb') as image_file: - image = base64.b64encode(image_file.read()) - except Exception as e: - # Handle local file loading errors raise UserError( - _(f"Error loading image from local path: {str(e)}")) - image_added = image - if image_added: - self.image_1920 = image_added - - -class ProductVariantImage(models.Model): - _inherit = 'product.product' - - image_url = fields.Char(string='Image URL') - image_added = fields.Binary("Image (1920x1920)", - compute='_compute_image_added', store=True) - - @api.depends('image_url') - def _compute_image_added(self): - """ Function to load an image from URL or local file path """ - image = False - if self.image_url: - if self.image_url.startswith(('http://', 'https://')): - # Load image from URL - try: - image = base64.b64encode( - requests.get(self.image_url).content) - except Exception as e: - # Handle URL loading errors - raise UserError(_(f"Error loading image from URL: {str(e)}")) + _(f"Error loading image from URL: {str(e)}")) else: # Load image from local file path try: diff --git a/product_import/security/ir.model.access.csv b/product_import/security/ir.model.access.csv index bbfb68a12..4c768f535 100644 --- a/product_import/security/ir.model.access.csv +++ b/product_import/security/ir.model.access.csv @@ -1,2 +1,2 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_product_import_product_import,product_import.product_import,model_product_import,base.group_user,1,1,1,1 \ No newline at end of file +access_product_import,access.product.import,model_product_import,base.group_user,1,1,1,1 diff --git a/product_import/static/description/assets/screenshots/1.png b/product_import/static/description/assets/screenshots/1.png index 7717cb249..8b95da250 100644 Binary files a/product_import/static/description/assets/screenshots/1.png and b/product_import/static/description/assets/screenshots/1.png differ diff --git a/product_import/static/description/assets/screenshots/2.png b/product_import/static/description/assets/screenshots/2.png deleted file mode 100644 index f75bb3b80..000000000 Binary files a/product_import/static/description/assets/screenshots/2.png and /dev/null differ diff --git a/product_import/static/description/assets/screenshots/3.png b/product_import/static/description/assets/screenshots/3.png index 303abad5e..239f737ef 100644 Binary files a/product_import/static/description/assets/screenshots/3.png and b/product_import/static/description/assets/screenshots/3.png differ diff --git a/product_import/static/description/assets/screenshots/4.png b/product_import/static/description/assets/screenshots/4.png index ccb03afec..4507cad99 100644 Binary files a/product_import/static/description/assets/screenshots/4.png and b/product_import/static/description/assets/screenshots/4.png differ diff --git a/product_import/static/description/assets/screenshots/5.png b/product_import/static/description/assets/screenshots/5.png index 21d72a090..a03404577 100644 Binary files a/product_import/static/description/assets/screenshots/5.png and b/product_import/static/description/assets/screenshots/5.png differ diff --git a/product_import/static/description/assets/screenshots/7.png b/product_import/static/description/assets/screenshots/7.png deleted file mode 100644 index ea1a0662a..000000000 Binary files a/product_import/static/description/assets/screenshots/7.png and /dev/null differ diff --git a/product_import/static/description/index.html b/product_import/static/description/index.html index 5e4619bb2..72c97b0b5 100644 --- a/product_import/static/description/index.html +++ b/product_import/static/description/index.html @@ -140,42 +140,30 @@
-

Paste image URL in the view.

+

Paste image URL in the view or Path.

+

The image will change when the record gets saved.

- -
-

The image will change when the record get saved.

- -
-

Choose the type of file to import products.

-
+

Importing product from CSV file.

You can use both URL and path for importing image.

The fields in the file must need to be in the following format.

- -
-

The products details with image will be imported.

- -
-
+

Importing product from xlsx file.

You can use both URL and path for importing image.

The fields in the file must need to be in the following format.

-

The products details with image will be imported.

- +
-

(The example format for csv and xlsx are added in the module.)

@@ -183,7 +171,6 @@
-
+ + + + product.product.only.form.view.inherit.product.import + product.product + + + + + + + diff --git a/product_import/views/product_template_views.xml b/product_import/views/product_template_views.xml new file mode 100644 index 000000000..1ec721683 --- /dev/null +++ b/product_import/views/product_template_views.xml @@ -0,0 +1,15 @@ + + + + + product.template.only.form.view.inherit.product.import + product.template + + + + + + + diff --git a/product_import/views/product_url.xml b/product_import/views/product_url.xml deleted file mode 100644 index eab94f692..000000000 --- a/product_import/views/product_url.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - Product URL - product.template - - - - - - - - - - - - Product URL - product.product - - - - - - - - - - - \ No newline at end of file diff --git a/product_import/wizard/product_import.py b/product_import/wizard/product_import.py index 0b8a32b72..81d298bf2 100644 --- a/product_import/wizard/product_import.py +++ b/product_import/wizard/product_import.py @@ -19,27 +19,28 @@ # If not, see . # ############################################################################# -import tempfile -import binascii import base64 +import binascii +import tempfile import certifi import urllib3 import xlrd -from odoo.exceptions import Warning +from odoo.exceptions import ValidationError from odoo import models, fields, _ class ProductImport(models.Model): + """Model to import the product""" _name = 'product.import' + _description = 'Product Import' file = fields.Binary(string="Upload File") file_name = fields.Char(string="File Name") - option = fields.Selection([ - ('csv', 'CSV'), - ('xlsx', 'XLSX')], default='csv') + option = fields.Selection([('csv', 'CSV'), ('xlsx', 'XLSX')], + default='csv') def import_file(self): - """ function to import product details from csv and xlsx file """ + """Function to import product details from csv and xlsx file """ if self.option == 'csv': try: product_temp_data = self.env['product.template'].search([]) @@ -48,9 +49,8 @@ class ProductImport(models.Model): file_string = file_string.split('\n') http = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs=certifi.where()) - except: - raise Warning(_("Please choose the correct file!")) - + except Exception: + raise ValidationError(_("Please choose the correct file!")) firstline = True for file_item in file_string: if firstline: @@ -59,49 +59,34 @@ class ProductImport(models.Model): product_temp = self.env['product.template'].search( [('name', '=', file_item.split(",")[0])], limit=0) if not product_temp.id: - if file_item.split(",")[0]: - if "http://" in file_item.split(",")[4] or "https://" in \ - file_item.split(",")[4]: - link = file_item.split(",")[4] - image_response = http.request('GET', link) + file_parts = file_item.split(",") + if len(file_parts) >= 5: + name, detailed_type, barcode, list_price, file_path_or_url = file_parts[:5] + product_name = { + 'name': name, + 'detailed_type': detailed_type, + 'barcode': barcode, + 'list_price': list_price, + } + if (file_path_or_url.startswith("http://") or + file_path_or_url.startswith("https://")): + image_response = http.request('GET', + file_path_or_url) image_thumbnail = base64.b64encode( image_response.data) - product_name = { - 'name': file_item.split(",")[0], - 'detailed_type': file_item.split(",")[1], - 'barcode': file_item.split(",")[2], - 'list_price': file_item.split(",")[3], - 'image_1920': image_thumbnail, - } - product_line = product_temp_data.create( - product_name) - elif '/home' in file_item.split(",")[4]: - with open(file_item.split(",")[4], 'rb') as file: + product_name['image_1920'] = image_thumbnail + elif file_path_or_url.startswith('/home'): + with open(file_path_or_url, 'rb') as file: data = base64.b64encode(file.read()) - product_name = { - 'name': file_item.split(",")[0], - 'detailed_type': file_item.split(",")[1], - 'barcode': file_item.split(",")[2], - 'list_price': file_item.split(",")[3], - 'image_1920': data, - } - product_line = product_temp_data.create( - product_name) - else: - product_name = { - 'name': file_item.split(",")[0], - 'detailed_type': file_item.split(",")[1], - 'barcode': file_item.split(",")[2], - 'list_price': file_item.split(",")[3], - } - product_line = product_temp_data.create( - product_name) + product_name['image_1920'] = data + product_temp_data.create(product_name) else: - raise Warning(_("Add the product which is not available in products")) - - if self.option == 'xlsx': + raise ValidationError( + _("Add the product which is not available in products") + ) + elif self.option == 'xlsx': try: - product_temp_data = self.env['product.template'].search([]) + product_temp_data = self.env['product.template'] file_string = tempfile.NamedTemporaryFile(suffix=".xlsx") file_string.write(binascii.a2b_base64(self.file)) book = xlrd.open_workbook(file_string.name) @@ -109,8 +94,7 @@ class ProductImport(models.Model): http = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs=certifi.where()) except: - raise Warning(_("Please choose the correct file")) - + raise ValidationError(_("Please choose the correct file")) startline = True for i in range(sheet.nrows): if startline: @@ -119,42 +103,26 @@ class ProductImport(models.Model): line = list(sheet.row_values(i)) product_temp = self.env['product.template'].search( [('name', '=', line[0])], limit=0) - if not product_temp.id: - if line[0]: - if "http://" in line[4] or "https://" in line[4]: - link = line[4] - image_response = http.request('GET', link) - image_thumbnail = base64.b64encode( - image_response.data) - product_name = { - 'name': line[0], - 'detailed_type': line[1], - 'barcode': line[2], - 'list_price': line[3], - 'image_1920': image_thumbnail, - } - product_line = product_temp_data.create( - product_name) - elif "/home" in line[4]: - with open(line[4], 'rb') as file: - data = base64.b64encode(file.read()) - product_name = { - 'name': line[0], - 'detailed_type': line[1], - 'barcode': line[2], - 'list_price': line[3], - 'image_1920': data, - } - product_line = product_temp_data.create( - product_name) - else: - product_name = { - 'name': line[0], - 'detailed_type': line[1], - 'barcode': line[2], - 'list_price': line[3], - } - product_line = product_temp_data.create( - product_name) - else: - raise Warning(_("Add the product which not available in products")) + if product_temp.id: + raise ValidationError( + _("Add the product which is not available in" + " products")) + if line[0]: + if "http://" in line[4] or "https://" in line[4]: + link = line[4] + image_response = http.request('GET', link) + image_thumbnail = base64.b64encode( + image_response.data) + elif "/home" in line[4]: + with open(line[4], 'rb') as file: + image_thumbnail = base64.b64encode(file.read()) + else: + image_thumbnail = False # or None + product_name = { + 'name': line[0], + 'detailed_type': line[1], + 'barcode': line[2], + 'list_price': line[3], + 'image_1920': image_thumbnail, + } + product_temp_data.create(product_name) diff --git a/product_import/wizard/product_import.xml b/product_import/wizard/product_import_views.xml similarity index 78% rename from product_import/wizard/product_import.xml rename to product_import/wizard/product_import_views.xml index b53c29a0f..5a8196618 100644 --- a/product_import/wizard/product_import.xml +++ b/product_import/wizard/product_import_views.xml @@ -1,8 +1,8 @@ - - - Import Product + + + product.import.view.form product.import
@@ -17,20 +17,19 @@
- + Import Product product.import ir.actions.act_window tree,form - + {} new - + -