Skip to content

Commit

Permalink
shopinvader_base_url: WIP refactor module
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastienbeau committed Jun 26, 2023
1 parent 9164e69 commit 447d71d
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 326 deletions.
2 changes: 2 additions & 0 deletions setup/.setuptools-odoo-make-default-ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# addons listed in this file are ignored by
# setuptools-odoo-make-default (one addon per line)
2 changes: 2 additions & 0 deletions setup/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
To learn more about this directory, please visit
https://pypi.python.org/pypi/setuptools-odoo
6 changes: 6 additions & 0 deletions setup/shopinvader_base_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,
)
244 changes: 79 additions & 165 deletions shopinvader_base_url/models/abstract_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import logging
from collections import defaultdict

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

DEFAULT_LANG = "en_US"

_logger = logging.getLogger(__name__)

Expand All @@ -16,50 +17,12 @@
_logger.debug("Cannot `import slugify`.")


def get_model_ref(record):
return "{},{}".format(record._name, record.id)


class AbstractUrl(models.AbstractModel):
_name = "abstract.url"
_description = "Abstract Url"
_have_url = True

url_builder = fields.Selection(
selection=[("auto", "Automatic"), ("manual", "Manual")], default="auto"
)
automatic_url_key = fields.Char(compute="_compute_automatic_url_key", store=True)
manual_url_key = fields.Char()
url_key = fields.Char(string="Url key", compute="_compute_url_key", store=True)
url_url_ids = fields.One2many(
compute="_compute_url_url_ids", comodel_name="url.url"
)
redirect_url_url_ids = fields.One2many(
compute="_compute_redirect_url_url_ids", comodel_name="url.url"
)
lang_id = fields.Many2one("res.lang", string="Lang", required=True)
active = fields.Boolean(default=True)

@api.constrains("url_builder", "manual_url_key")
def _check_manual_url_key(self):
for rec in self:
if rec.url_builder == "manual" and not rec.manual_url_key:
raise ValidationError(
_("Manual url key is required if builder is set to manual")
)

@api.onchange("manual_url_key")
def on_url_key_change(self):
self.ensure_one()
if self.manual_url_key:
url = slugify(self.manual_url_key)
if url != self.manual_url_key:
self.manual_url_key = url
return {
"warning": {
"title": "Adapt text rules",
"message": "it will be adapted to %s" % url,
}
}
url_ids = fields.One2many("url.url", "res_id")

def _get_url_keywords(self):
"""This method return a list of keyword that will be concatenated
Expand All @@ -71,160 +34,111 @@ def _get_url_keywords(self):
self.ensure_one()
# TODO: IMO we should add the ID here by default
# to make sure the URL is always unique
# seb.beau: not sure, most of site do not have id in url
# the url have an seo impact so it's better to only put meaning information
# moreover for unicity you can put the default code of the product or ean13
return [self.name]

def _post_process_url_key(self, key):
"""This method allow you to customized the url key.
you can use it to build full path be adding the url of parent record
Ex: key is 42 you can prefix it with "foo" and so return "foo/42"
Note: the self do not include in the context the lang of the record
"""
def _generate_url_key(self):
self.ensure_one()
return key

def _generic_compute_automatic_url_key(self):
records_by_lang = defaultdict(self.browse)
for record in self:
records_by_lang[record.lang_id] |= record
return slugify("-".join(self._get_url_keywords()))

key_by_id = {}
for lang_id, records in records_by_lang.items():
for record in records.with_context(lang=lang_id.code):
if not isinstance(record.id, models.NewId):
key_by_id[record.id] = slugify("-".join(record._get_url_keywords()))

for record in self:
if not isinstance(record.id, models.NewId):
record.automatic_url_key = record._post_process_url_key(
key_by_id[record.id]
)
else:
record.automatic_url_key = False

def _compute_automatic_url_key_depends(self):
return ["lang_id", "record_id.name"]

@api.depends(lambda self: self._compute_automatic_url_key_depends())
def _compute_automatic_url_key(self):
raise NotImplementedError(
"Automatic url key must be computed in concrete model"
)

@api.depends("manual_url_key", "automatic_url_key", "url_builder", "active")
def _compute_url_key(self):
for record in self:
if not record.active:
record.url_key = ""
record._redirect_existing_url()
else:
if record.url_builder == "manual":
new_url = record.manual_url_key
else:
new_url = record.automatic_url_key
if record.url_key != new_url:
record.url_key = new_url
record.set_url(record.url_key)

@api.depends("url_key")
def _compute_redirect_url_url_ids(self):
self.flush()
for record in self:
record.redirect_url_url_ids = record.env["url.url"].search(
[
("model_id", "=", get_model_ref(record)),
("redirect", "=", True),
]
def _get_redirect_urls(self, referential, lang):
self.ensure_one()
return self.url_ids.filtered(
lambda s: (
s.lang_id.code == lang and s.referential == referential and s.redirect
)
)

@api.depends("url_key")
def _compute_url_url_ids(self):
self.flush()
for record in self:
record.url_url_ids = record.env["url.url"].search(
[("model_id", "=", get_model_ref(record))]
def _get_main_url(self, referential, lang):
self.ensure_one()
return self.url_ids.filtered(
lambda s: (
s.lang_id.code == lang
and s.referential == referential
and not s.redirect
)
)

@api.model
def _prepare_url(self, url_key):
def _prepare_url(self, url_key, referential):
return {
"url_key": url_key,
"redirect": False,
"model_id": get_model_ref(self),
"res_model": self._name,
"res_id": self.id,
"referential": referential,
}

def _reuse_url(self, existing_url):
# TODO add user notification in the futur SEO dashboard
existing_url.write({"model_id": get_model_ref(self), "redirect": False})

def set_url(self, url_key):
"""Se a new url
backup old url
1 find url redirect true and same model_id
if other model id refuse
2 if exists set to False
existing_url.write(
{
"res_model": self._name,
"res_id": self.id,
"redirect": False,
}
)

3 write the new one
"""
def _update_url_key(self, referential="global", lang=DEFAULT_LANG):
for record in self.with_context(lang=lang):
# TODO maybe we should have a computed field that flag the
# current url if the key used for building the url have changed
# so we can skip this check if nothing have changed
url_key = record._get_url_key()
current_url = record._get_main_url()
if current_url.key != url_key:
current_url.redirect = True
record._add_url(referential, url_key, lang)

def _add_url(self, referential, url_key, lang):
self.ensure_one()
existing_url = self.env["url.url"].search(
[
("url_key", "=", url_key),
("backend_id", "=", get_model_ref(self.backend_id)),
("lang_id", "=", self.lang_id.id),
("referential", "=", referential)("lang_id.code", "=", lang),
("key", "=", url_key),
]
)
if existing_url:
if self != existing_url.model_id:
if existing_url.redirect:
self._reuse_url(existing_url)
else:
raise UserError(
_(
"Url_key already exist in other model"
"\n- name: %(model_name)s\n - id: %(model_id)s\n"
"- url_key: %(url_key)s\n - url_key_id %(url_id)s"
)
% dict(
model_name=existing_url.model_id.name,
model_id=existing_url.model_id.id,
url_key=existing_url.url_key,
url_id=existing_url.id,
)
)
if existing_url.redirect:
self._reuse_url(existing_url)
else:
existing_url.write({"redirect": False})
raise UserError(
_(
"Url_key already exist in other model"
"\n- name: %(model_name)s\n - id: %(model_id)s\n"
"- url_key: %(url_key)s\n - url_key_id %(url_id)s"
)
% dict(
model_name=existing_url.model_id.name,
model_id=existing_url.model_id.id,
url_key=existing_url.url_key,
url_id=existing_url.id,
)
)

else:
# no existing key creating one if not empty
self.env["url.url"].create(self._prepare_url(url_key))
# other url of object set redirect to True
redirect_urls = self.env["url.url"].search(
[
("model_id", "=", get_model_ref(self)),
("url_key", "!=", url_key),
("redirect", "=", False),
]
)
redirect_urls.write({"redirect": True})
# we must explicitly invalidate the cache since there is no depends
# defined on this computed fields and this field could have already
# been loaded into the cache
self.invalidate_cache(fnames=["url_url_ids"], ids=self.ids)
vals = self._prepare_url(url_key, referential)
self.env["url.url"].create(vals)

def _redirect_existing_url(self):
def _redirect_existing_url(self, action):
"""
This method is called when the record is deactivated to give a chance
to the concrete model to implement a redirect strategy
action can be "archived" or "unlink"
"""
return True

def unlink(self):
for record in self:
# TODO we should propose to redirect the old url
urls = record.env["url.url"].search(
[("model_id", "=", get_model_ref(record))]
)
urls.unlink()
self.flush()
return super(AbstractUrl, self).unlink()
record._redirect_existing_url("unlink")
# Remove dead url that have been not redirected
record.url_ids.unlink()
return super().unlink()

def write(self, vals):
res = super().write(vals)
if "active" in vals and not vals["active"]:
self._redirect_existing_url("archived")
return res
Loading

0 comments on commit 447d71d

Please sign in to comment.