From fbd43bb45954ae22daa65e4be5076a6ed2789c02 Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Sun, 4 Oct 2026 14:17:09 +0200 Subject: [PATCH] [REF] all: pass pre-commit on the whole repo Needed for the lint job of the CI to go green. Mostly automatic hook fixes (black, isort, prettier, pyupgrade, autoflake, whitespace). By hand: - flake8: two unused imports, an ambiguous variable name `l`. - pylint-odoo mandatory: manifest keys equal to their default value (installable, application, data, external_dependencies). - end-of-file-fixer skips *.patch too, like trailing-whitespace: the upstream patches stay as git format-patch wrote them. No behaviour change. Co-Authored-By: Claude Opus 5.5 --- .pre-commit-config.yaml | 5 +- .../models/res_config_settings.py | 3 +- .../models/res_partner.py | 3 +- membership_payment_date/__manifest__.py | 3 - .../models/account_move.py | 9 +- .../tests/test_membership_payment_date.py | 9 +- .../controllers/portal.py | 6 +- .../static/src/js/pos_full_refund.esm.js | 55 +++++---- .../tests/tours/pos_full_refund_tour.esm.js | 39 +++---- .../ControlButtons/ReorderButton.esm.js | 108 +++++++++--------- .../Screens/TicketScreen/TicketScreen.esm.js | 2 +- .../__manifest__.py | 1 - purchase_rappel/__manifest__.py | 2 - purchase_rappel/models/account_move.py | 4 +- purchase_rappel/models/res_company.py | 3 +- purchase_rappel/models/res_config_settings.py | 3 +- purchase_rappel/models/res_partner.py | 3 +- purchase_rappel/tests/test_purchase_rappel.py | 1 + .../wizard/account_move_rappel_wizard.py | 8 +- website_membership_associate/__manifest__.py | 1 - .../controllers/main.py | 48 +++----- .../models/membership.py | 11 +- .../test_website_membership_associate.py | 7 +- website_membership_signup_required/README.md | 2 +- .../__init__.py | 2 +- .../__manifest__.py | 7 +- .../controllers/__init__.py | 2 +- .../controllers/main.py | 19 +-- .../controllers/website_sale.py | 39 +++++-- .../models/__init__.py | 2 +- .../models/res_config_settings.py | 5 +- .../models/res_partner.py | 5 +- .../models/res_users.py | 5 +- .../models/website.py | 9 +- .../js/website_membership_signup_required.js | 2 +- .../website_membership_signup_required.js | 7 +- .../tests/__init__.py | 2 +- .../tests/test_membership_signup.py | 93 +++++++++------ .../tests/test_membership_signup_tour.py | 78 +++++++------ .../views/auth_signup_templates.xml | 2 +- .../views/res_config_settings_views.xml | 2 +- website_sale_disable_cart/__manifest__.py | 1 - website_sale_lazy_loading/__manifest__.py | 1 - .../controllers/website_sale.py | 2 +- .../tests/test_lazy_loading.py | 4 +- 45 files changed, 330 insertions(+), 295 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ca2cf1f..186eb94 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -68,8 +68,9 @@ repos: # lines of a diff are a single space, stripping it breaks them exclude: /README\.rst$|\.pot?$|\.patch$ - id: end-of-file-fixer - # exclude autogenerated files - exclude: /README\.rst$|\.pot?$ + # exclude autogenerated files, and patches: they are kept as + # git format-patch wrote them + exclude: /README\.rst$|\.pot?$|\.patch$ - id: debug-statements - id: check-case-conflict - id: check-docstring-first diff --git a/membership_expiry_reminder/models/res_config_settings.py b/membership_expiry_reminder/models/res_config_settings.py index 31bacff..8e30339 100644 --- a/membership_expiry_reminder/models/res_config_settings.py +++ b/membership_expiry_reminder/models/res_config_settings.py @@ -1,6 +1,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import fields, models +from odoo import fields +from odoo import models class ResConfigSettings(models.TransientModel): diff --git a/membership_expiry_reminder/models/res_partner.py b/membership_expiry_reminder/models/res_partner.py index 3c59d9b..dc6586d 100644 --- a/membership_expiry_reminder/models/res_partner.py +++ b/membership_expiry_reminder/models/res_partner.py @@ -3,7 +3,8 @@ import logging from datetime import timedelta -from odoo import fields, models +from odoo import fields +from odoo import models _logger = logging.getLogger(__name__) diff --git a/membership_payment_date/__manifest__.py b/membership_payment_date/__manifest__.py index af8fb57..1f1d3ae 100644 --- a/membership_payment_date/__manifest__.py +++ b/membership_payment_date/__manifest__.py @@ -13,7 +13,4 @@ "membership_variable_period", "account", ], - "data": [], - "installable": True, - "application": False, } diff --git a/membership_payment_date/models/account_move.py b/membership_payment_date/models/account_move.py index 5eeb797..dee294f 100644 --- a/membership_payment_date/models/account_move.py +++ b/membership_payment_date/models/account_move.py @@ -4,7 +4,8 @@ import math from datetime import timedelta -from odoo import fields, models +from odoo import fields +from odoo import models class AccountMove(models.Model): @@ -74,7 +75,11 @@ class AccountMove(models.Model): return fields.Date.today() def _compute_membership_dates( - self, partner, product, payment_date, quantity=1.0, + self, + partner, + product, + payment_date, + quantity=1.0, exclude_line_ids=None, ): """Calculate date_from and date_to for a membership line. diff --git a/membership_payment_date/tests/test_membership_payment_date.py b/membership_payment_date/tests/test_membership_payment_date.py index fb6f3ea..65cc0b1 100644 --- a/membership_payment_date/tests/test_membership_payment_date.py +++ b/membership_payment_date/tests/test_membership_payment_date.py @@ -4,11 +4,10 @@ from datetime import date from freezegun import freeze_time - from psycopg2 import ProgrammingError -from odoo import fields -from odoo.tests import Form, common +from odoo.tests import Form +from odoo.tests import common @freeze_time("2026-01-15") @@ -123,9 +122,7 @@ class TestMembershipPaymentDate(common.TransactionCase): def _create_invoice(self, product, invoice_date, quantity=1.0): """Create and return a draft out_invoice with the given product.""" invoice_form = Form( - self.env["account.move"].with_context( - default_move_type="out_invoice" - ) + self.env["account.move"].with_context(default_move_type="out_invoice") ) invoice_form.invoice_date = invoice_date invoice_form.partner_id = self.partner diff --git a/portal_event_registration/controllers/portal.py b/portal_event_registration/controllers/portal.py index b771439..fafa650 100644 --- a/portal_event_registration/controllers/portal.py +++ b/portal_event_registration/controllers/portal.py @@ -1,7 +1,9 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import _, http -from odoo.exceptions import AccessError, MissingError +from odoo import _ +from odoo import http +from odoo.exceptions import AccessError +from odoo.exceptions import MissingError from odoo.http import request from odoo.addons.portal.controllers.portal import CustomerPortal diff --git a/pos_full_refund/static/src/js/pos_full_refund.esm.js b/pos_full_refund/static/src/js/pos_full_refund.esm.js index a10cddb..00927fa 100644 --- a/pos_full_refund/static/src/js/pos_full_refund.esm.js +++ b/pos_full_refund/static/src/js/pos_full_refund.esm.js @@ -2,33 +2,32 @@ import { TicketScreen } from "@point_of_sale/app/screens/ticket_screen/ticket_sc import { patch } from "@web/core/utils/patch"; patch(TicketScreen.prototype, { - /** - * Mark every refundable line of the selected order for a full refund and - * trigger the standard refund flow. - * - * `onDoRefund` is what the "Refund" action of the ticket screen calls: it - * builds the negative lines, links the combo children, carries over the - * fiscal position and the partner, and switches to the destination order. - */ - async onDoFullRefund() { - const order = this.getSelectedOrder(); - if (!order) { - return; - } - for (const line of order.lines) { - const toRefundDetails = line - .getAllLinesInCombo() - .map((comboLine) => this.getToRefundDetail(comboLine)); - for (const toRefundDetail of toRefundDetails) { - // Per line, and net of what was already refunded: the same - // guard the core applies in `_onUpdateSelectedOrderline`. - const refundableQty = - toRefundDetail.line.qty - toRefundDetail.line.refunded_qty; - if (refundableQty > 0) { - toRefundDetail.qty = refundableQty; + /** + * Mark every refundable line of the selected order for a full refund and + * trigger the standard refund flow. + * + * `onDoRefund` is what the "Refund" action of the ticket screen calls: it + * builds the negative lines, links the combo children, carries over the + * fiscal position and the partner, and switches to the destination order. + */ + async onDoFullRefund() { + const order = this.getSelectedOrder(); + if (!order) { + return; } - } - } - await this.onDoRefund(); - }, + for (const line of order.lines) { + const toRefundDetails = line + .getAllLinesInCombo() + .map((comboLine) => this.getToRefundDetail(comboLine)); + for (const toRefundDetail of toRefundDetails) { + // Per line, and net of what was already refunded: the same + // guard the core applies in `_onUpdateSelectedOrderline`. + const refundableQty = toRefundDetail.line.qty - toRefundDetail.line.refunded_qty; + if (refundableQty > 0) { + toRefundDetail.qty = refundableQty; + } + } + } + await this.onDoRefund(); + }, }); diff --git a/pos_full_refund/static/tests/tours/pos_full_refund_tour.esm.js b/pos_full_refund/static/tests/tours/pos_full_refund_tour.esm.js index 1da9cec..454f2d4 100644 --- a/pos_full_refund/static/tests/tours/pos_full_refund_tour.esm.js +++ b/pos_full_refund/static/tests/tours/pos_full_refund_tour.esm.js @@ -14,27 +14,24 @@ import { registry } from "@web/core/registry"; * negated at its full quantity, without touching the numpad. */ registry.category("web_tour.tours").add("pos_full_refund_tour", { - steps: () => - [ - Chrome.startPoS(), - Dialog.confirm("Open Register"), - ProductScreen.addOrderline("Desk Pad", "2", "3"), - ProductScreen.addOrderline("Letter Tray", "3", "2"), - ProductScreen.clickPayButton(), - PaymentScreen.clickPaymentMethod("Bank"), - PaymentScreen.clickValidate(), - ReceiptScreen.isShown(), - ReceiptScreen.clickNextOrder(), + steps: () => + [ + Chrome.startPoS(), + Dialog.confirm("Open Register"), + ProductScreen.addOrderline("Desk Pad", "2", "3"), + ProductScreen.addOrderline("Letter Tray", "3", "2"), + ProductScreen.clickPayButton(), + PaymentScreen.clickPaymentMethod("Bank"), + PaymentScreen.clickValidate(), + ReceiptScreen.isShown(), + ReceiptScreen.clickNextOrder(), - ProductScreen.clickRefund(), - TicketScreen.selectOrder("-0001"), - TicketScreen.clickControlButton("Do Full Refund"), + ProductScreen.clickRefund(), + TicketScreen.selectOrder("-0001"), + TicketScreen.clickControlButton("Do Full Refund"), - { ...ProductScreen.back(), isActive: ["mobile"] }, - ProductScreen.isShown(), - inLeftSide([ - ...Order.hasLine("Desk Pad", "-2"), - ...Order.hasLine("Letter Tray", "-3"), - ]), - ].flat(), + { ...ProductScreen.back(), isActive: ["mobile"] }, + ProductScreen.isShown(), + inLeftSide([...Order.hasLine("Desk Pad", "-2"), ...Order.hasLine("Letter Tray", "-3")]), + ].flat(), }); diff --git a/pos_order_reorder/static/src/js/Screens/TicketScreen/ControlButtons/ReorderButton.esm.js b/pos_order_reorder/static/src/js/Screens/TicketScreen/ControlButtons/ReorderButton.esm.js index bbe050a..4036108 100644 --- a/pos_order_reorder/static/src/js/Screens/TicketScreen/ControlButtons/ReorderButton.esm.js +++ b/pos_order_reorder/static/src/js/Screens/TicketScreen/ControlButtons/ReorderButton.esm.js @@ -4,64 +4,64 @@ import { Component } from "@odoo/owl"; import { usePos } from "@point_of_sale/app/store/pos_hook"; export class ReorderButton extends Component { - static template = "ReorderButton"; + static template = "ReorderButton"; - setup() { - this.pos = usePos(); - } - get isEmptyOrder() { - if (!this.props.order) return true; - return this.props.order.is_empty(); - } - _reOrder() { - if (this.isEmptyOrder) { - return; + setup() { + this.pos = usePos(); } - const order = this.props.order; - const pos = this.pos; - const partner = order.get_partner(); - const newOrder = pos.add_new_order(); - if (partner) { - newOrder.set_partner(partner); + get isEmptyOrder() { + if (!this.props.order) return true; + return this.props.order.is_empty(); } - if (order.fiscal_position) { - newOrder.fiscal_position = order.fiscal_position; + _reOrder() { + if (this.isEmptyOrder) { + return; + } + const order = this.props.order; + const pos = this.pos; + const partner = order.get_partner(); + const newOrder = pos.add_new_order(); + if (partner) { + newOrder.set_partner(partner); + } + if (order.fiscal_position) { + newOrder.fiscal_position = order.fiscal_position; + } + if (order.pricelist) { + newOrder.set_pricelist(order.pricelist); + } + const lines = order.get_orderlines(); + for (var i = 0; i < lines.length; i++) { + const line = lines[i]; + const new_line = this.props.order.models["pos.order.line"].create( + this._prepareReorderLineVals(newOrder, line) + ); + if (line.pack_lot_lines) { + new_line.setPackLotLines({ + modifiedPackLotLines: [], + newPackLotLines: (line.lot_names || []).map((name) => ({ + lot_name: name, + })), + }); + } + new_line.set_unit_price(line.get_unit_price()); + new_line.set_quantity(line.get_quantity()); + new_line.set_discount(line.get_discount()); + } + this.pos.closeScreen(); } - if (order.pricelist) { - newOrder.set_pricelist(order.pricelist); + _prepareReorderLineVals(order, line) { + return { + order_id: order, + product_id: line.product_id, + description: line.name, + price_unit: line.price_unit, + tax_ids: line.tax_ids.map((tax) => ["link", tax]), + price_manually_set: true, + customer_note: line.customer_note, + }; } - const lines = order.get_orderlines(); - for (var i = 0; i < lines.length; i++) { - const line = lines[i]; - const new_line = this.props.order.models["pos.order.line"].create( - this._prepareReorderLineVals(newOrder, line) - ); - if (line.pack_lot_lines) { - new_line.setPackLotLines({ - modifiedPackLotLines: [], - newPackLotLines: (line.lot_names || []).map((name) => ({ - lot_name: name, - })), - }); - } - new_line.set_unit_price(line.get_unit_price()); - new_line.set_quantity(line.get_quantity()); - new_line.set_discount(line.get_discount()); + _onClick() { + this._reOrder(); } - this.pos.closeScreen(); - } - _prepareReorderLineVals(order, line) { - return { - order_id: order, - product_id: line.product_id, - description: line.name, - price_unit: line.price_unit, - tax_ids: line.tax_ids.map((tax) => ["link", tax]), - price_manually_set: true, - customer_note: line.customer_note, - }; - } - _onClick() { - this._reOrder(); - } } diff --git a/pos_order_reorder/static/src/js/Screens/TicketScreen/TicketScreen.esm.js b/pos_order_reorder/static/src/js/Screens/TicketScreen/TicketScreen.esm.js index 23259b2..4277705 100644 --- a/pos_order_reorder/static/src/js/Screens/TicketScreen/TicketScreen.esm.js +++ b/pos_order_reorder/static/src/js/Screens/TicketScreen/TicketScreen.esm.js @@ -3,5 +3,5 @@ import { patch } from "@web/core/utils/patch"; import { ReorderButton } from "@pos_order_reorder/js/Screens/TicketScreen/ControlButtons/ReorderButton.esm"; patch(TicketScreen, { - components: { ...TicketScreen.components, ReorderButton }, + components: { ...TicketScreen.components, ReorderButton }, }); diff --git a/pos_payment_method_cashdro_fix/__manifest__.py b/pos_payment_method_cashdro_fix/__manifest__.py index bbe4598..0bba399 100644 --- a/pos_payment_method_cashdro_fix/__manifest__.py +++ b/pos_payment_method_cashdro_fix/__manifest__.py @@ -19,5 +19,4 @@ "pos_payment_method_cashdro_fix/static/src/js/models_fix.esm.js", ], }, - "installable": True, } diff --git a/purchase_rappel/__manifest__.py b/purchase_rappel/__manifest__.py index 5a5850e..0654722 100644 --- a/purchase_rappel/__manifest__.py +++ b/purchase_rappel/__manifest__.py @@ -17,6 +17,4 @@ "views/res_config_settings_views.xml", "views/account_move_views.xml", ], - "installable": True, - "application": False, } diff --git a/purchase_rappel/models/account_move.py b/purchase_rappel/models/account_move.py index 75549c2..2502471 100644 --- a/purchase_rappel/models/account_move.py +++ b/purchase_rappel/models/account_move.py @@ -1,6 +1,8 @@ # Copyright 2024 Criptomart # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import api, fields, models +from odoo import api +from odoo import fields +from odoo import models class AccountMove(models.Model): diff --git a/purchase_rappel/models/res_company.py b/purchase_rappel/models/res_company.py index 358faf0..570339c 100644 --- a/purchase_rappel/models/res_company.py +++ b/purchase_rappel/models/res_company.py @@ -1,6 +1,7 @@ # Copyright 2024 Criptomart # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import fields, models +from odoo import fields +from odoo import models class ResCompany(models.Model): diff --git a/purchase_rappel/models/res_config_settings.py b/purchase_rappel/models/res_config_settings.py index afe0f54..c29fd2b 100644 --- a/purchase_rappel/models/res_config_settings.py +++ b/purchase_rappel/models/res_config_settings.py @@ -1,6 +1,7 @@ # Copyright 2024 Criptomart # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import api, fields, models +from odoo import fields +from odoo import models class ResConfigSettings(models.TransientModel): diff --git a/purchase_rappel/models/res_partner.py b/purchase_rappel/models/res_partner.py index 7698d9c..8c57e35 100644 --- a/purchase_rappel/models/res_partner.py +++ b/purchase_rappel/models/res_partner.py @@ -1,6 +1,7 @@ # Copyright 2024 Criptomart # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import fields, models +from odoo import fields +from odoo import models class ResPartner(models.Model): diff --git a/purchase_rappel/tests/test_purchase_rappel.py b/purchase_rappel/tests/test_purchase_rappel.py index 2bea079..0b43dee 100644 --- a/purchase_rappel/tests/test_purchase_rappel.py +++ b/purchase_rappel/tests/test_purchase_rappel.py @@ -4,6 +4,7 @@ from odoo import fields from odoo.exceptions import UserError from odoo.tests import tagged + from odoo.addons.account.tests.common import AccountTestInvoicingCommon diff --git a/purchase_rappel/wizard/account_move_rappel_wizard.py b/purchase_rappel/wizard/account_move_rappel_wizard.py index fdf9f22..baeb7f5 100644 --- a/purchase_rappel/wizard/account_move_rappel_wizard.py +++ b/purchase_rappel/wizard/account_move_rappel_wizard.py @@ -2,7 +2,10 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from collections import defaultdict -from odoo import _, api, fields, models +from odoo import _ +from odoo import api +from odoo import fields +from odoo import models from odoo.exceptions import UserError @@ -52,8 +55,7 @@ class AccountMoveRappelWizard(models.TransientModel): # Filter only posted vendor bills and refunds valid_moves = moves.filtered( - lambda m: m.move_type in ("in_invoice", "in_refund") - and m.state == "posted" + lambda m: m.move_type in ("in_invoice", "in_refund") and m.state == "posted" ) if not valid_moves: raise UserError( diff --git a/website_membership_associate/__manifest__.py b/website_membership_associate/__manifest__.py index f7e4e81..246551a 100644 --- a/website_membership_associate/__manifest__.py +++ b/website_membership_associate/__manifest__.py @@ -13,5 +13,4 @@ "membership", "website_membership", ], - "data": [], } diff --git a/website_membership_associate/controllers/main.py b/website_membership_associate/controllers/main.py index e4629b5..bac3f4d 100644 --- a/website_membership_associate/controllers/main.py +++ b/website_membership_associate/controllers/main.py @@ -1,7 +1,8 @@ # Copyright 2026 Criptomart # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import fields, http +from odoo import fields +from odoo import http from odoo.http import request from odoo.tools.translate import _ @@ -28,9 +29,7 @@ class WebsiteMembership(BaseWebsiteMembership): self._add_associate_members(response, membership_id, country_id, page, post) return response - def _add_associate_members( - self, response, membership_id, country_id, page, post - ): + def _add_associate_members(self, response, membership_id, country_id, page, post): values = response.qcontext memberships_partner_ids = values.get("memberships_partner_ids", {}) if not memberships_partner_ids: @@ -67,25 +66,19 @@ class WebsiteMembership(BaseWebsiteMembership): if associate.id not in new_memberships_partner_ids[key]: new_memberships_partner_ids[key].append(associate.id) values["memberships_partner_ids"] = { - key: ids - for key, ids in new_memberships_partner_ids.items() - if ids + key: ids for key, ids in new_memberships_partner_ids.items() if ids } - displayed_ids = set( - pid - for ids in values["memberships_partner_ids"].values() - for pid in ids - ) + displayed_ids = { + pid for ids in values["memberships_partner_ids"].values() for pid in ids + } values["partners"] = { p.id: p - for p in request.env["res.partner"] - .sudo() - .browse(list(displayed_ids)) + for p in request.env["res.partner"].sudo().browse(list(displayed_ids)) } - all_host_ids, all_free_host_ids, all_associates = ( - self._get_all_visible_ids(membership_id, country_id, post_name) + all_host_ids, all_free_host_ids, all_associates = self._get_all_visible_ids( + membership_id, country_id, post_name ) self._recompute_countries( values, all_host_ids, all_free_host_ids, all_associates, country_id @@ -134,9 +127,7 @@ class WebsiteMembership(BaseWebsiteMembership): if membership_id == "free": all_host_ids = set() else: - all_host_ids = self._get_all_host_ids( - membership_id, country_id, post_name - ) + all_host_ids = self._get_all_host_ids(membership_id, country_id, post_name) all_free_host_ids = ( self._get_all_free_host_ids(country_id, post_name) if free_selected @@ -214,8 +205,7 @@ class WebsiteMembership(BaseWebsiteMembership): if country_id: current_country = Country.browse(country_id).read(["id", "name"])[0] if not any( - x["country_id"] and x["country_id"][0] == country_id - for x in countries + x["country_id"] and x["country_id"][0] == country_id for x in countries ): countries.append( { @@ -243,16 +233,12 @@ class WebsiteMembership(BaseWebsiteMembership): return partners = request.env["res.partner"].sudo().browse(map_ids) if country_id: - partners = partners.filtered( - lambda p: p.country_id.id == country_id - ) + partners = partners.filtered(lambda p: p.country_id.id == country_id) if post_name: partners = partners.filtered( lambda p: self._partner_matches_search(p, post_name) ) - values["google_map_partner_ids"] = ",".join( - str(pid) for pid in partners.ids - ) + values["google_map_partner_ids"] = ",".join(str(pid) for pid in partners.ids) def _update_pager( self, @@ -268,9 +254,7 @@ class WebsiteMembership(BaseWebsiteMembership): if membership_id == "free": displayed_host_ids = set() else: - host_records = request.env["res.partner"].sudo().browse( - list(all_host_ids) - ) + host_records = request.env["res.partner"].sudo().browse(list(all_host_ids)) displayed_host_ids = { host.id for host in host_records @@ -278,7 +262,7 @@ class WebsiteMembership(BaseWebsiteMembership): } count_members = len(displayed_host_ids) + len(all_free_host_ids) limit = self._references_per_page - base_url = "/members%s%s" % ( + base_url = "/members{}{}".format( "/association/%s" % membership_id if membership_id else "", "/country/%s" % country_id if country_id else "", ) diff --git a/website_membership_associate/models/membership.py b/website_membership_associate/models/membership.py index fbad52c..9f4e030 100644 --- a/website_membership_associate/models/membership.py +++ b/website_membership_associate/models/membership.py @@ -1,7 +1,8 @@ # Copyright 2026 Criptomart # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import api, models +from odoo import api +from odoo import models from odoo.osv import expression @@ -41,10 +42,7 @@ class MembershipLine(models.Model): ("website_description", "ilike", post_name), ] host_ids = ( - self.env["res.partner"] - .sudo() - .search(associate_domain) - .associate_member.ids + self.env["res.partner"].sudo().search(associate_domain).associate_member.ids ) if not host_ids: return domain @@ -84,8 +82,7 @@ class MembershipLine(models.Model): AND p.is_company AND m.id IN %s ORDER BY p.id - """ - + limit_clause, + """ + limit_clause, (tuple(self.ids),), ) return [row[0] for row in self.env.cr.fetchall()] diff --git a/website_membership_associate/tests/test_website_membership_associate.py b/website_membership_associate/tests/test_website_membership_associate.py index d18a44c..7d3dd2e 100644 --- a/website_membership_associate/tests/test_website_membership_associate.py +++ b/website_membership_associate/tests/test_website_membership_associate.py @@ -2,7 +2,8 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import fields -from odoo.tests import HttpCase, tagged +from odoo.tests import HttpCase +from odoo.tests import tagged from odoo.addons.membership.tests.common import TestMembershipCommon @@ -125,9 +126,7 @@ class TestWebsiteMembershipAssociatePagination(HttpCase, TestMembershipCommon): for i in range(20) ] cls.paginated_hosts = cls.env["res.partner"].create(host_vals) - invoices = cls.paginated_hosts.create_membership_invoice( - cls.membership_1, 75.0 - ) + invoices = cls.paginated_hosts.create_membership_invoice(cls.membership_1, 75.0) invoices.action_post() cls.env["account.payment.register"].with_context( active_model="account.move", active_ids=invoices.ids diff --git a/website_membership_signup_required/README.md b/website_membership_signup_required/README.md index d55760f..585dcc3 100644 --- a/website_membership_signup_required/README.md +++ b/website_membership_signup_required/README.md @@ -120,4 +120,4 @@ Para reforzar la seguridad se podría sustituir el query param estático por un token HMAC firmado (vinculado a la sesión y al producto objetivo), de modo que solo el flujo de `cart_update` pueda generar un redirect válido y el acceso directo a `/web/signup` fuera de ese flujo quede restringido. Ver -`requirements.md` §7 R-01. \ No newline at end of file +`requirements.md` §7 R-01. diff --git a/website_membership_signup_required/__init__.py b/website_membership_signup_required/__init__.py index 85e41b1..c55325e 100644 --- a/website_membership_signup_required/__init__.py +++ b/website_membership_signup_required/__init__.py @@ -1,4 +1,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import controllers -from . import models \ No newline at end of file +from . import models diff --git a/website_membership_signup_required/__manifest__.py b/website_membership_signup_required/__manifest__.py index 0ec11e6..f7d5a4b 100644 --- a/website_membership_signup_required/__manifest__.py +++ b/website_membership_signup_required/__manifest__.py @@ -6,7 +6,7 @@ "version": "18.0.1.0.1", "category": "Website/Sale", "summary": "Force a clean /web/signup step before adding membership " - "products to cart on a b2b website (EMES-style flow).", + "products to cart on a b2b website (EMES-style flow).", "author": "Criptomart", "website": "https://git.criptomart.net/criptomart/addons-cm", "license": "AGPL-3", @@ -25,7 +25,4 @@ "website_membership_signup_required/static/src/js/website_membership_signup_required.js", ], }, - "external_dependencies": {}, - "installable": True, - "application": False, -} \ No newline at end of file +} diff --git a/website_membership_signup_required/controllers/__init__.py b/website_membership_signup_required/controllers/__init__.py index b584e45..9cb8279 100644 --- a/website_membership_signup_required/controllers/__init__.py +++ b/website_membership_signup_required/controllers/__init__.py @@ -1,4 +1,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import main -from . import website_sale \ No newline at end of file +from . import website_sale diff --git a/website_membership_signup_required/controllers/main.py b/website_membership_signup_required/controllers/main.py index b6de11b..581ae35 100644 --- a/website_membership_signup_required/controllers/main.py +++ b/website_membership_signup_required/controllers/main.py @@ -1,11 +1,13 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import _, http -from odoo.addons.auth_signup.controllers.main import AuthSignupHome -from odoo.addons.web.controllers.home import SIGN_UP_REQUEST_PARAMS +from odoo import _ +from odoo import http from odoo.exceptions import UserError from odoo.http import request +from odoo.addons.auth_signup.controllers.main import AuthSignupHome +from odoo.addons.web.controllers.home import SIGN_UP_REQUEST_PARAMS + # partner_firstname exposes `firstname` / `lastname` on res.users and # res.partner; we need them as accepted qcontext params so the signup form # roundtrips them transparently through `get_auth_signup_qcontext`. @@ -18,7 +20,10 @@ SIGN_UP_REQUEST_PARAMS.add("lastname") SIGN_UP_REQUEST_PARAMS.add("accepted_terms") # Session keys used by the membership signup flow. -MEMBERSHIP_SIGNUP_SESSION_KEYS = ("membership_signup_active", "membership_signup_product_id") +MEMBERSHIP_SIGNUP_SESSION_KEYS = ( + "membership_signup_active", + "membership_signup_product_id", +) class AuthSignupHomeMembership(AuthSignupHome): @@ -71,9 +76,7 @@ class AuthSignupHomeMembership(AuthSignupHome): values["firstname"] = firstname values["lastname"] = lastname partner_model = request.env["res.partner"] - values["name"] = partner_model._get_computed_name( - lastname, firstname - ) + values["name"] = partner_model._get_computed_name(lastname, firstname) return values @http.route() @@ -134,4 +137,4 @@ class AuthSignupHomeMembership(AuthSignupHome): # Drop the markers: the membership flow is now complete. for key in MEMBERSHIP_SIGNUP_SESSION_KEYS: request.session.pop(key, None) - return request.redirect("/shop/cart") \ No newline at end of file + return request.redirect("/shop/cart") diff --git a/website_membership_signup_required/controllers/website_sale.py b/website_membership_signup_required/controllers/website_sale.py index 16d0194..27bb446 100644 --- a/website_membership_signup_required/controllers/website_sale.py +++ b/website_membership_signup_required/controllers/website_sale.py @@ -3,9 +3,10 @@ from urllib.parse import urlencode from odoo import http -from odoo.addons.website_sale.controllers.main import WebsiteSale from odoo.http import request +from odoo.addons.website_sale.controllers.main import WebsiteSale + SIGNUP_FLAG = "membership_signup=1" @@ -36,24 +37,37 @@ class WebsiteSaleMembershipSignup(WebsiteSale): @http.route() def cart_update( - self, product_id, add_qty=1, set_qty=0, + self, + product_id, + add_qty=1, + set_qty=0, product_custom_attribute_values=None, - no_variant_attribute_value_ids=None, **kwargs + no_variant_attribute_value_ids=None, + **kwargs, ): if self._membership_signup_is_required(product_id): self._membership_signup_mark_session(product_id) return request.redirect(self._membership_signup_redirect_url(product_id)) return super().cart_update( - product_id=product_id, add_qty=add_qty, set_qty=set_qty, + product_id=product_id, + add_qty=add_qty, + set_qty=set_qty, product_custom_attribute_values=product_custom_attribute_values, no_variant_attribute_value_ids=no_variant_attribute_value_ids, - **kwargs + **kwargs, ) @http.route() def cart_update_json( - self, product_id, line_id=None, add_qty=None, set_qty=None, display=True, - product_custom_attribute_values=None, no_variant_attribute_value_ids=None, **kwargs + self, + product_id, + line_id=None, + add_qty=None, + set_qty=None, + display=True, + product_custom_attribute_values=None, + no_variant_attribute_value_ids=None, + **kwargs, ): if self._membership_signup_is_required(product_id): self._membership_signup_mark_session(product_id) @@ -90,11 +104,14 @@ class WebsiteSaleMembershipSignup(WebsiteSale): "notification_info": {"lines": [], "warning": ""}, } return super().cart_update_json( - product_id=product_id, line_id=line_id, add_qty=add_qty, - set_qty=set_qty, display=display, + product_id=product_id, + line_id=line_id, + add_qty=add_qty, + set_qty=set_qty, + display=display, product_custom_attribute_values=product_custom_attribute_values, no_variant_attribute_value_ids=no_variant_attribute_value_ids, - **kwargs + **kwargs, ) def _get_mandatory_address_fields(self, country_sudo): @@ -122,4 +139,4 @@ class WebsiteSaleMembershipSignup(WebsiteSale): # So even after we drop 'phone' from the address set above, the # portal set re-adds it for billing. We need to drop it here too. fields = list(super()._get_mandatory_fields()) - return [f for f in fields if f != "phone"] \ No newline at end of file + return [f for f in fields if f != "phone"] diff --git a/website_membership_signup_required/models/__init__.py b/website_membership_signup_required/models/__init__.py index c77ee6b..330b5ad 100644 --- a/website_membership_signup_required/models/__init__.py +++ b/website_membership_signup_required/models/__init__.py @@ -3,4 +3,4 @@ from . import website from . import res_config_settings from . import res_partner -from . import res_users \ No newline at end of file +from . import res_users diff --git a/website_membership_signup_required/models/res_config_settings.py b/website_membership_signup_required/models/res_config_settings.py index aa463e8..f4f39d6 100644 --- a/website_membership_signup_required/models/res_config_settings.py +++ b/website_membership_signup_required/models/res_config_settings.py @@ -1,6 +1,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import fields, models +from odoo import fields +from odoo import models class ResConfigSettings(models.TransientModel): @@ -9,4 +10,4 @@ class ResConfigSettings(models.TransientModel): membership_signup_required = fields.Boolean( related="website_id.membership_signup_required", readonly=False, - ) \ No newline at end of file + ) diff --git a/website_membership_signup_required/models/res_partner.py b/website_membership_signup_required/models/res_partner.py index 84e1c2d..a8792da 100644 --- a/website_membership_signup_required/models/res_partner.py +++ b/website_membership_signup_required/models/res_partner.py @@ -1,6 +1,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, models +from odoo import api +from odoo import models class ResPartner(models.Model): @@ -29,4 +30,4 @@ class ResPartner(models.Model): if partner: res["firstname"] = partner.firstname or "" res["lastname"] = partner.lastname or "" - return res \ No newline at end of file + return res diff --git a/website_membership_signup_required/models/res_users.py b/website_membership_signup_required/models/res_users.py index 8986981..11f2393 100644 --- a/website_membership_signup_required/models/res_users.py +++ b/website_membership_signup_required/models/res_users.py @@ -1,6 +1,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, models +from odoo import api +from odoo import models from odoo.http import request @@ -19,4 +20,4 @@ class ResUsers(models.Model): return "b2c" if request.session.get("membership_signup_active"): return "b2c" - return super()._get_signup_invitation_scope() \ No newline at end of file + return super()._get_signup_invitation_scope() diff --git a/website_membership_signup_required/models/website.py b/website_membership_signup_required/models/website.py index 2ecaf97..26372e3 100644 --- a/website_membership_signup_required/models/website.py +++ b/website_membership_signup_required/models/website.py @@ -1,6 +1,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import fields, models +from odoo import fields +from odoo import models class Website(models.Model): @@ -9,8 +10,8 @@ class Website(models.Model): membership_signup_required = fields.Boolean( string="Force signup for membership products", help="When enabled, anonymous users trying to add a membership product " - "to cart are redirected to /web/signup before the product is " - "added. Only affects the e-commerce flow.", + "to cart are redirected to /web/signup before the product is " + "added. Only affects the e-commerce flow.", default=True, ) @@ -30,4 +31,4 @@ class Website(models.Model): if not product_id: return False product = self.env["product.product"].sudo().browse(int(product_id)).exists() - return bool(product and product.product_tmpl_id.membership) \ No newline at end of file + return bool(product and product.product_tmpl_id.membership) diff --git a/website_membership_signup_required/static/src/js/website_membership_signup_required.js b/website_membership_signup_required/static/src/js/website_membership_signup_required.js index af8fa28..37ce6be 100644 --- a/website_membership_signup_required/static/src/js/website_membership_signup_required.js +++ b/website_membership_signup_required/static/src/js/website_membership_signup_required.js @@ -38,4 +38,4 @@ publicWidget.registry.WebsiteSale.include({ } return data; }, -}); \ No newline at end of file +}); diff --git a/website_membership_signup_required/static/tests/tours/website_membership_signup_required.js b/website_membership_signup_required/static/tests/tours/website_membership_signup_required.js index 173b806..e3640e0 100644 --- a/website_membership_signup_required/static/tests/tours/website_membership_signup_required.js +++ b/website_membership_signup_required/static/tests/tours/website_membership_signup_required.js @@ -64,8 +64,9 @@ registry.category("web_tour.tours").add("website_membership_signup_required_tour run: "click", }, { - content: "Submit the signup form -> redirect straight to cart " - + "with the product already added (post-login auto-add)", + content: + "Submit the signup form -> redirect straight to cart " + + "with the product already added (post-login auto-add)", trigger: ".oe_signup_form button[type='submit']", run: "click", expectUnloadPage: true, @@ -108,4 +109,4 @@ registry.category("web_tour.tours").add("website_membership_signup_required_non_ tourUtils.goToCart(), tourUtils.assertCartContains({ productName: "Regular Test Product" }), ], -}); \ No newline at end of file +}); diff --git a/website_membership_signup_required/tests/__init__.py b/website_membership_signup_required/tests/__init__.py index 712de60..93ab6e1 100644 --- a/website_membership_signup_required/tests/__init__.py +++ b/website_membership_signup_required/tests/__init__.py @@ -1,4 +1,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import test_membership_signup -from . import test_membership_signup_tour \ No newline at end of file +from . import test_membership_signup_tour diff --git a/website_membership_signup_required/tests/test_membership_signup.py b/website_membership_signup_required/tests/test_membership_signup.py index b3d059b..d7cb61f 100644 --- a/website_membership_signup_required/tests/test_membership_signup.py +++ b/website_membership_signup_required/tests/test_membership_signup.py @@ -4,15 +4,16 @@ import werkzeug.datastructures from odoo.exceptions import UserError from odoo.fields import Date +from odoo.tests import TransactionCase +from odoo.tests import tagged +from odoo.addons.website.tools import MockRequest from odoo.addons.website_membership_signup_required.controllers.main import ( AuthSignupHomeMembership as AuthSignupHome, ) from odoo.addons.website_membership_signup_required.controllers.website_sale import ( WebsiteSaleMembershipSignup, ) -from odoo.addons.website.tools import MockRequest -from odoo.tests import TransactionCase, tagged @tagged("post_install", "-at_install") @@ -24,22 +25,28 @@ class TestMembershipSignup(TransactionCase): cls.website = cls.env.ref("website.default_website") cls.website.auth_signup_uninvited = "b2b" today = Date.to_date(Date.today()) - cls.membership_product = cls.env["product.product"].create({ - "name": "Membership Card", - "membership": True, - "list_price": 50.0, - "membership_date_from": today.replace(month=1, day=1), - "membership_date_to": today.replace(month=12, day=31), - }) - cls.regular_product = cls.env["product.product"].create({ - "name": "Regular Mug", - "membership": False, - "list_price": 10.0, - }) - cls.no_user_partner = cls.env["res.partner"].create({ - "firstname": "Backend", - "lastname": "Partner", - }) + cls.membership_product = cls.env["product.product"].create( + { + "name": "Membership Card", + "membership": True, + "list_price": 50.0, + "membership_date_from": today.replace(month=1, day=1), + "membership_date_to": today.replace(month=12, day=31), + } + ) + cls.regular_product = cls.env["product.product"].create( + { + "name": "Regular Mug", + "membership": False, + "list_price": 10.0, + } + ) + cls.no_user_partner = cls.env["res.partner"].create( + { + "firstname": "Backend", + "lastname": "Partner", + } + ) # -- RF-07 / RF-default ------------------------------------------------ @@ -198,13 +205,21 @@ class TestMembershipSignup(TransactionCase): # -- RF-08 ------------------------------------------------------------- def test_08_backend_sale_order_with_membership_product_without_user(self): - order = self.env["sale.order"].create({ - "partner_id": self.no_user_partner.id, - "order_line": [(0, 0, { - "product_id": self.membership_product.id, - "product_uom_qty": 1.0, - })], - }) + order = self.env["sale.order"].create( + { + "partner_id": self.no_user_partner.id, + "order_line": [ + ( + 0, + 0, + { + "product_id": self.membership_product.id, + "product_uom_qty": 1.0, + }, + ) + ], + } + ) self.assertTrue(order) self.assertEqual(order.order_line.product_id, self.membership_product) # Confirming the SO drives membership creation (core `membership`). @@ -276,12 +291,14 @@ class TestMembershipSignup(TransactionCase): The invited partner must NOT have a user yet, otherwise the token payload check (`user_ids`) won't match. """ - partner = self.env["res.partner"].create({ - "firstname": "Invited", - "lastname": "Researcher", - "email": "invited.researcher@example.com", - "signup_type": "signup", - }) + partner = self.env["res.partner"].create( + { + "firstname": "Invited", + "lastname": "Researcher", + "email": "invited.researcher@example.com", + "signup_type": "signup", + } + ) self.assertFalse(partner.user_ids, "test precondition: no user yet") token = partner._generate_signup_token() info = self.env["res.partner"]._signup_retrieve_info(token) @@ -297,11 +314,13 @@ class TestMembershipSignup(TransactionCase): still works — the form will show empty readonly inputs for the split fields, which is the best we can do without re-splitting. """ - partner = self.env["res.partner"].create({ - "name": "Legacy Researcher", - "email": "legacy@example.com", - "signup_type": "signup", - }) + partner = self.env["res.partner"].create( + { + "name": "Legacy Researcher", + "email": "legacy@example.com", + "signup_type": "signup", + } + ) # partner_firstname's post_create hook may or may not have split # `name` automatically; we just check the override does not crash # and returns the contract keys. @@ -309,4 +328,4 @@ class TestMembershipSignup(TransactionCase): info = self.env["res.partner"]._signup_retrieve_info(token) self.assertIn("firstname", info) self.assertIn("lastname", info) - self.assertEqual(info.get("name"), partner.name) \ No newline at end of file + self.assertEqual(info.get("name"), partner.name) diff --git a/website_membership_signup_required/tests/test_membership_signup_tour.py b/website_membership_signup_required/tests/test_membership_signup_tour.py index 5326e2d..7011147 100644 --- a/website_membership_signup_required/tests/test_membership_signup_tour.py +++ b/website_membership_signup_required/tests/test_membership_signup_tour.py @@ -7,8 +7,8 @@ import time from urllib.parse import unquote from odoo.fields import Date - -from odoo.tests import HttpCase, tagged +from odoo.tests import HttpCase +from odoo.tests import tagged @tagged("post_install", "-at_install") @@ -21,28 +21,34 @@ class TestMembershipSignupTour(HttpCase): cls.website.auth_signup_uninvited = "b2b" cls.website.membership_signup_required = True today = Date.to_date(Date.today()) - cls.membership_product = cls.env["product.template"].create({ - "name": "Membership Test Product", - "membership": True, - "list_price": 60.0, - "is_published": True, - "sale_ok": True, - "website_id": cls.website.id, - "membership_date_from": today.replace(month=1, day=1), - "membership_date_to": today.replace(month=12, day=31), - }) - cls.regular_product = cls.env["product.template"].create({ - "name": "Regular Test Product", - "membership": False, - "list_price": 5.0, - "is_published": True, - "sale_ok": True, - "website_id": cls.website.id, - }) + cls.membership_product = cls.env["product.template"].create( + { + "name": "Membership Test Product", + "membership": True, + "list_price": 60.0, + "is_published": True, + "sale_ok": True, + "website_id": cls.website.id, + "membership_date_from": today.replace(month=1, day=1), + "membership_date_to": today.replace(month=12, day=31), + } + ) + cls.regular_product = cls.env["product.template"].create( + { + "name": "Regular Test Product", + "membership": False, + "list_price": 5.0, + "is_published": True, + "sale_ok": True, + "website_id": cls.website.id, + } + ) # Enable the custom/transfer payment provider if it is installed so the # cart page renders fully (no checkout/payment is exercised by the # tours, but it avoids warnings during cart rendering). - provider = cls.env.ref("payment.payment_provider_transfer", raise_if_not_found=False) + provider = cls.env.ref( + "payment.payment_provider_transfer", raise_if_not_found=False + ) if provider and provider.state != "enabled": provider.write({"state": "enabled", "is_published": True}) @@ -54,20 +60,24 @@ class TestMembershipSignupTour(HttpCase): ) # The tour submitted the signup form, so a new portal user must exist # and the cart of that user must contain the membership product. - users = self.env["res.users"].search([ - ("login", "like", "membership.tour.%@test.example.com"), - ]) + users = self.env["res.users"].search( + [ + ("login", "like", "membership.tour.%@test.example.com"), + ] + ) self.assertTrue(users, "Tour should have created a user via /web/signup") first = users.sorted("create_date")[-1] self.assertTrue(first.partner_id.firstname) self.assertTrue(first.partner_id.lastname) - carts = self.env["sale.order"].search([ - ("partner_id", "=", first.partner_id.id), - ("state", "=", "draft"), - ]) + carts = self.env["sale.order"].search( + [ + ("partner_id", "=", first.partner_id.id), + ("state", "=", "draft"), + ] + ) self.assertTrue(carts) order_lines = carts.mapped("order_line").filtered( - lambda l: l.product_id.product_tmpl_id == self.membership_product + lambda line: line.product_id.product_tmpl_id == self.membership_product ) self.assertTrue(order_lines, "Cart must contain the membership product") @@ -93,7 +103,7 @@ class TestMembershipSignupTour(HttpCase): return None secret = self.env["ir.config_parameter"].sudo().get_param("database.secret") max_ts = int(time.time() + 31536000) # 1y, matches Odoo's default. - msg = f"{sid}{max_ts}".encode("utf-8") + msg = f"{sid}{max_ts}".encode() hm = hmac.new(secret.encode("ascii"), msg, hashlib.sha1).hexdigest() return f"{hm}o{max_ts}" @@ -106,8 +116,10 @@ class TestMembershipSignupTour(HttpCase): product_url = "/shop/%s" % self.env["ir.http"]._slug(product) page = self.url_open(product_url, allow_redirects=True) self.assertEqual( - page.status_code, 200, - "Product page must render (got %s for %s)" % (page.status_code, product_url), + page.status_code, + 200, + "Product page must render (got %s for %s)" + % (page.status_code, product_url), ) match = self._CSRF_RE.search(page.text) csrf_token = match.group(1) if match else self._compute_csrf_token() @@ -199,4 +211,4 @@ class TestMembershipSignupTour(HttpCase): self.assertIn("/shop/cart", location) self.assertNotIn("/web/signup", location) finally: - self.website.membership_signup_required = True \ No newline at end of file + self.website.membership_signup_required = True diff --git a/website_membership_signup_required/views/auth_signup_templates.xml b/website_membership_signup_required/views/auth_signup_templates.xml index f584d7a..028af2d 100644 --- a/website_membership_signup_required/views/auth_signup_templates.xml +++ b/website_membership_signup_required/views/auth_signup_templates.xml @@ -37,4 +37,4 @@ - \ No newline at end of file + diff --git a/website_membership_signup_required/views/res_config_settings_views.xml b/website_membership_signup_required/views/res_config_settings_views.xml index 973078e..42c90b5 100644 --- a/website_membership_signup_required/views/res_config_settings_views.xml +++ b/website_membership_signup_required/views/res_config_settings_views.xml @@ -15,4 +15,4 @@ - \ No newline at end of file + diff --git a/website_sale_disable_cart/__manifest__.py b/website_sale_disable_cart/__manifest__.py index 3140b2a..a011fe1 100644 --- a/website_sale_disable_cart/__manifest__.py +++ b/website_sale_disable_cart/__manifest__.py @@ -18,5 +18,4 @@ "views/res_config_settings_views.xml", "views/website_sale_disable_cart_templates.xml", ], - "installable": True, } diff --git a/website_sale_lazy_loading/__manifest__.py b/website_sale_lazy_loading/__manifest__.py index fdfe3c9..26aa326 100644 --- a/website_sale_lazy_loading/__manifest__.py +++ b/website_sale_lazy_loading/__manifest__.py @@ -24,5 +24,4 @@ "website_sale_lazy_loading/static/src/js/website_sale_lazy_loading.js", ], }, - "installable": True, } diff --git a/website_sale_lazy_loading/controllers/website_sale.py b/website_sale_lazy_loading/controllers/website_sale.py index a113421..32bc7af 100644 --- a/website_sale_lazy_loading/controllers/website_sale.py +++ b/website_sale_lazy_loading/controllers/website_sale.py @@ -76,7 +76,7 @@ class WebsiteSaleLazyLoading(WebsiteSale): query = urlencode(params, doseq=True) if not query: return LAZY_LOADING_ROUTE - return "%s?%s" % (LAZY_LOADING_ROUTE, query) + return f"{LAZY_LOADING_ROUTE}?{query}" # ------------------------------------------------------------------ # AJAX endpoint diff --git a/website_sale_lazy_loading/tests/test_lazy_loading.py b/website_sale_lazy_loading/tests/test_lazy_loading.py index e6f4b6f..47bea99 100644 --- a/website_sale_lazy_loading/tests/test_lazy_loading.py +++ b/website_sale_lazy_loading/tests/test_lazy_loading.py @@ -47,10 +47,10 @@ class TestLazyLoading(HttpCase): def _get_lazy_page(self, page, **params): """Call the AJAX endpoint and return its decoded payload.""" - query = "&".join("%s=%s" % (key, value) for key, value in params.items()) + query = "&".join(f"{key}={value}" for key, value in params.items()) url = "/shop/lazy_products?page=%s" % page if query: - url = "%s&%s" % (url, query) + url = f"{url}&{query}" response = self.url_open(url) self.assertEqual(response.status_code, 200) return response.json()