Skip to content

Commit

Permalink
shopinvader_product_url: add module, shopinvader_base_url: improve mo…
Browse files Browse the repository at this point in the history
…dule
  • Loading branch information
sebastienbeau committed Jul 25, 2023
1 parent baf8e6d commit e132a15
Show file tree
Hide file tree
Showing 18 changed files with 231 additions and 10 deletions.
6 changes: 6 additions & 0 deletions setup/shopinvader_product_url/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import setuptools

setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)
6 changes: 5 additions & 1 deletion shopinvader_base_url/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
# any module necessary for this one to work correctly
"depends": ["base"],
"external_dependencies": {"python": ["python-slugify"]},
"data": ["views/url_view.xml", "security/ir.model.access.csv"],
"data": [
"views/url_view.xml",
"security/res_groups.xml",
"security/ir.model.access.csv",
],
"url": "",
"installable": True,
}
55 changes: 52 additions & 3 deletions shopinvader_base_url/models/abstract_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import logging

from lxml import etree

from odoo import _, api, fields, models
from odoo.exceptions import UserError

Expand All @@ -17,6 +19,18 @@
_logger.debug("Cannot `import slugify`.")


SMART_BUTTON = """
<button class="oe_stat_button"
name="open_url"
icon="fa-list-ul"
type="object">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="count_url"/></span>
<span>URL</span>
</div>
</button>"""


class AbstractUrl(models.AbstractModel):
_name = "abstract.url"
_description = "Abstract Url"
Expand All @@ -26,6 +40,20 @@ class AbstractUrl(models.AbstractModel):
url_need_refresh = fields.Boolean(
compute="_compute_url_need_refresh", store=True, readonly=False
)
count_url = fields.Integer(compute="_compute_count_url")

def _compute_count_url(self):
res = self.env["url.url"].read_group(
domain=[
("res_id", "in", self.ids),
("res_model", "=", self._name),
],
fields=["res_id"],
groupby=["res_id"],
)
id2count = {item["res_id"]: item["res_id_count"] for item in res}
for record in self:
record.count_url = id2count.get(record.id, 0)

def _compute_url_need_refresh_depends(self):
return self._get_keyword_fields()
Expand All @@ -50,7 +78,7 @@ def _get_keyword_fields(self):
# moreover for unicity you can put the default code of the product or ean13
return ["name"]

def _generate_url_key(self):
def _generate_url_key(self, referential, lang):
def get(self, key_path):
value = self
for key_field in key_path.split("."):
Expand Down Expand Up @@ -89,6 +117,7 @@ def _prepare_url(self, referential, lang, url_key):
"res_id": self.id,
"referential": referential,
"lang_id": self.env["res.lang"]._lang_get_id(lang),
"manual": False,
}

def _reuse_url(self, existing_url):
Expand All @@ -115,18 +144,18 @@ def _update_url_key(self, referential="global", lang=DEFAULT_LANG):
# Before updating one specific url (referential + lang)
# the flag is propagated on all valid url
if record.url_need_refresh:
record.url_need_refresh = False
record.url_ids.filtered(
lambda s: not s.redirect and not s.manual
).write({"need_refresh": True})
if not current_url or current_url.need_refresh:
current_url.need_refresh = False
url_key = record._generate_url_key()
url_key = record._generate_url_key(referential, lang)
# maybe some change have been done but the url is the same
# so check it
if current_url.key != url_key:
current_url.redirect = True
record._add_url(referential, lang, url_key)
record.url_need_refresh = False

def _add_url(self, referential, lang, url_key):
self.ensure_one()
Expand Down Expand Up @@ -179,3 +208,23 @@ def write(self, vals):
if "active" in vals and not vals["active"]:
self._redirect_existing_url("archived")
return res

@api.model
def _get_view(self, view_id=None, view_type="form", **options):
arch, view = super()._get_view(view_id=view_id, view_type=view_type, **options)
button_box = arch.xpath("//div[@name='button_box']")
if button_box:
button_box[0].append(etree.fromstring(SMART_BUTTON))
return arch, view

def open_url(self):
self.ensure_one()
action = self.env.ref("shopinvader_base_url.base_url_action_view").read()[0]
action["domain"] = [("res_model", "=", self._name), ("res_id", "in", self.ids)]
action["context"] = {
"hide_res_model": True,
"hide_res_id": True,
"default_res_model": self._name,
"default_res_id": self.id,
}
return action
11 changes: 10 additions & 1 deletion shopinvader_base_url/models/url_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class UrlUrl(models.Model):
_description = "Url"
_order = "res_model,res_id,redirect desc"

manual = fields.Boolean()
manual = fields.Boolean(default=True, readonly=True)
key = fields.Char(required=True, index=True)
res_id = fields.Many2oneReference(
string="Record ID",
Expand All @@ -31,6 +31,7 @@ class UrlUrl(models.Model):
selection=lambda s: s._get_all_referential(),
index=True,
default="global",
required=True,
)
lang_id = fields.Many2one("res.lang", "Lang", index=True, required=True)
need_refresh = fields.Boolean()
Expand All @@ -43,6 +44,14 @@ class UrlUrl(models.Model):
)
]

def init(self):
self.env.cr.execute(
f"""CREATE UNIQUE INDEX IF NOT EXISTS main_url_uniq
ON {self._table} (referential, lang_id, res_id, res_model)
WHERE redirect = False"""
)
return super().init()

@tools.ormcache()
@api.model
def _get_model_with_url_selection(self):
Expand Down
3 changes: 2 additions & 1 deletion shopinvader_base_url/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_url_url,access_url_url,model_url_url,,1,0,0,0
access_read_url_url,access_url_url,model_url_url,,1,0,0,0
access_edit_url_url,access_url_url,model_url_url,group_edit_url,1,1,1,1
12 changes: 12 additions & 0 deletions shopinvader_base_url/security/res_groups.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>

<record model="res.groups" id="group_edit_url">
<field name="name">Edit url</field>
<field
name="users"
eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"
/>
</record>

</odoo>
13 changes: 9 additions & 4 deletions shopinvader_base_url/views/url_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
<group>
<field name="referential" />
<field name="lang_id" />
<field name="res_model" />
<field
name="res_model"
invisible="context.get('hide_res_model')"
/>
<field name="key" />
<field name="res_id" />
<field name="res_id" invisible="context.get('hide_res_id')" />
<field name="manual" />
<field name="redirect" />
</group>
</sheet>
Expand All @@ -25,9 +29,10 @@
<tree>
<field name="referential" />
<field name="lang_id" />
<field name="res_model" />
<field name="res_model" invisible="context.get('hide_res_model')" />
<field name="key" />
<field name="res_id" />
<field name="res_id" invisible="context.get('hide_res_id')" />
<field name="manual" />
<field name="redirect" />
</tree>
</field>
Expand Down
Empty file.
1 change: 1 addition & 0 deletions shopinvader_product_url/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
25 changes: 25 additions & 0 deletions shopinvader_product_url/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2023 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).


{
"name": "Shopinvader product url",
"summary": "Generate url for product and category",
"version": "16.0.1.0.0",
"development_status": "Alpha",
"category": "Shopinvader",
"website": "https://github.com/shopinvader/odoo-shopinvader",
"author": " Akretion",
"license": "AGPL-3",
"external_dependencies": {
"python": [],
"bin": [],
},
"depends": [
"shopinvader_base_url",
"product",
],
"data": [],
"demo": [],
}
2 changes: 2 additions & 0 deletions shopinvader_product_url/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import product_template
from . import product_category
33 changes: 33 additions & 0 deletions shopinvader_product_url/models/product_category.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright 2023 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models

from odoo.addons.shopinvader_base_url.models.abstract_url import DEFAULT_LANG


class ProductCategory(models.Model):
_inherit = ["product.category", "abstract.url"]
_name = "product.category"

url_need_refresh = fields.Boolean(recursive=True)

def _update_url_key(self, referential="global", lang=DEFAULT_LANG):
# Ensure that parent url is up to date before updating the current url
if self.parent_id:
self.parent_id._update_url_key(referential=referential, lang=lang)
return super()._update_url_key(referential=referential, lang=lang)

def _generate_url_key(self, referential, lang):
url_key = super()._generate_url_key(referential, lang)
if self.parent_id:
parent_url = self.parent_id._get_main_url(referential, lang)
if parent_url:
return "/".join([parent_url.key, url_key])
return url_key

def _compute_url_need_refresh_depends(self):
return super()._compute_url_need_refresh_depends() + [
"parent_id.url_need_refresh"
]
13 changes: 13 additions & 0 deletions shopinvader_product_url/models/product_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2023 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import models


class ProductTemplate(models.Model):
_inherit = ["product.template", "abstract.url"]
_name = "product.template"

def _get_keyword_fields(self):
return super()._get_keyword_fields() + ["default_code"]
1 change: 1 addition & 0 deletions shopinvader_product_url/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Sebastien BEAU <[email protected]>
1 change: 1 addition & 0 deletions shopinvader_product_url/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Generate url for product and category
1 change: 1 addition & 0 deletions shopinvader_product_url/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_category_url
57 changes: 57 additions & 0 deletions shopinvader_product_url/tests/test_category_url.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Copyright 2019 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from odoo.tests import TransactionCase


class TestCategoryUrl(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.lang_en = cls.env.ref("base.lang_en")
cls.lang_fr = cls.env.ref("base.lang_fr")
cls.lang_fr.active = True
cls.categ_1 = (
cls.env["product.category"]
.with_context(lang="en_US")
.create({"name": "Root"})
)
cls.categ_2 = (
cls.env["product.category"]
.with_context(lang="en_US")
.create({"name": "Level 1", "parent_id": cls.categ_1.id})
)
cls.categ_3 = (
cls.env["product.category"]
.with_context(lang="en_US")
.create({"name": "Level 2", "parent_id": cls.categ_2.id})
)

def _expect_url_for_lang(self, record, lang, url_key):
self.assertEqual(record._get_main_url("global", lang).key, url_key)

def test_url_for_main_categ(self):
self.categ_1._update_url_key(lang="en_US")
self._expect_url_for_lang(self.categ_1, "en_US", "root")

def test_url_for_child(self):
self.categ_3._update_url_key(lang="en_US")
self._expect_url_for_lang(self.categ_1, "en_US", "root")
self._expect_url_for_lang(self.categ_2, "en_US", "root/level-1")
self._expect_url_for_lang(self.categ_3, "en_US", "root/level-1/level-2")

def test_update_main(self):
self.categ_3._update_url_key(lang="en_US")
self.categ_1.name = "New Root"
self.categ_3._update_url_key(lang="en_US")
self._expect_url_for_lang(self.categ_1, "en_US", "new-root")
self._expect_url_for_lang(self.categ_2, "en_US", "new-root/level-1")
self._expect_url_for_lang(self.categ_3, "en_US", "new-root/level-1/level-2")

def test_update_child(self):
self.categ_3._update_url_key(lang="en_US")
self.categ_2.name = "New Level 1"
self.categ_3._update_url_key(lang="en_US")
self._expect_url_for_lang(self.categ_1, "en_US", "root")
self._expect_url_for_lang(self.categ_2, "en_US", "root/new-level-1")
self._expect_url_for_lang(self.categ_3, "en_US", "root/new-level-1/level-2")

0 comments on commit e132a15

Please sign in to comment.