diff --git a/.copier-answers.yml b/.copier-answers.yml index c7361787..819bc1e4 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,24 +1,26 @@ # Do NOT update manually; changes here will be overwritten by Copier -_commit: v1.29 +_commit: v1.38 _src_path: gh:oca/oca-addons-repo-template -ci: Travis +ci: GitHub convert_readme_fragments_to_markdown: false -dependency_installation_mode: PIP enable_checklog_odoo: false generate_requirements_txt: true +github_check_license: true +github_ci_extra_env: {} +github_enable_codecov: true +github_enable_makepot: true github_enable_stale_action: true +github_enforce_dev_status_compatibility: true include_wkhtmltopdf: false odoo_test_flavor: Both odoo_version: 14.0 org_name: Odoo Community Association (OCA) org_slug: OCA rebel_module_groups: [] -repo_description: 'TODO: add repo description.' +repo_description: Apps store, used on odoo-community.org repo_name: apps-store repo_slug: apps-store repo_website: https://github.com/OCA/apps-store -travis_apt_packages: [] -travis_apt_sources: [] use_pyproject_toml: false use_ruff: false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..e0d56685 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +test-requirements.txt merge=union diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 10b8acad..904d3bd0 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -17,6 +17,7 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.11" + cache: 'pip' - name: Get python version run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV - uses: actions/cache@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..9344f65c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,78 @@ +name: tests + +on: + pull_request: + branches: + - "14.0*" + push: + branches: + - "14.0" + - "14.0-ocabot-*" + +jobs: + unreleased-deps: + runs-on: ubuntu-latest + name: Detect unreleased dependencies + steps: + - uses: actions/checkout@v4 + - run: | + for reqfile in requirements.txt test-requirements.txt ; do + if [ -f ${reqfile} ] ; then + result=0 + # reject non-comment lines that contain a / (i.e. URLs, relative paths) + grep "^[^#].*/" ${reqfile} || result=$? + if [ $result -eq 0 ] ; then + echo "Unreleased dependencies found in ${reqfile}." + exit 1 + fi + fi + done + test: + runs-on: ubuntu-22.04 + container: ${{ matrix.container }} + name: ${{ matrix.name }} + strategy: + fail-fast: false + matrix: + include: + - container: ghcr.io/oca/oca-ci/py3.6-odoo14.0:latest + name: test with Odoo + - container: ghcr.io/oca/oca-ci/py3.6-ocb14.0:latest + name: test with OCB + makepot: "true" + services: + postgres: + image: postgres:9.6 + env: + POSTGRES_USER: odoo + POSTGRES_PASSWORD: odoo + POSTGRES_DB: odoo + ports: + - 5432:5432 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Install addons and dependencies + run: oca_install_addons + - name: Check licenses + run: manifestoo -d . check-licenses + - name: Check development status + run: manifestoo -d . check-dev-status --default-dev-status=Beta + - name: Initialize test db + run: oca_init_test_database + - name: Run tests + run: oca_run_tests + - name: Upload screenshots from JS tests + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: Screenshots of failed JS tests - ${{ matrix.name }}${{ join(matrix.include) }} + path: /tmp/odoo_tests/${{ env.PGDATABASE }} + if-no-files-found: ignore + - uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + - name: Update .pot files + run: oca_export_and_push_pot https://x-access-token:${{ secrets.GIT_PUSH_TOKEN }}@github.com/${{ github.repository }} + if: ${{ matrix.makepot == 'true' && github.event_name == 'push' && github.repository_owner == 'OCA' }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 99425a3e..e3f1ef8b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,7 +39,7 @@ repos: language: fail files: '[a-zA-Z0-9_]*/i18n/en\.po$' - repo: https://github.com/oca/maintainer-tools - rev: d5fab7ee87fceee858a3d01048c78a548974d935 + rev: f9b919b9868143135a9c9cb03021089cabba8223 hooks: # update the NOT INSTALLABLE ADDONS section above - id: oca-update-pre-commit-excluded-addons @@ -104,6 +104,7 @@ repos: additional_dependencies: - "eslint@7.8.1" - "eslint-plugin-jsdoc@" + - "globals@" - repo: https://github.com/pre-commit/pre-commit-hooks rev: v3.2.0 hooks: @@ -140,7 +141,7 @@ repos: - --settings=. exclude: /__init__\.py$ - repo: https://github.com/acsone/setuptools-odoo - rev: 3.1.8 + rev: 3.3.2 hooks: - id: setuptools-odoo-make-default - id: setuptools-odoo-get-requirements diff --git a/.pylintrc b/.pylintrc index d1f72970..6c0a7829 100644 --- a/.pylintrc +++ b/.pylintrc @@ -25,19 +25,25 @@ disable=all enable=anomalous-backslash-in-string, api-one-deprecated, api-one-multi-together, - assignment-from-none, - attribute-deprecated, class-camelcase, - dangerous-default-value, dangerous-view-replace-wo-priority, - development-status-allowed, duplicate-id-csv, - duplicate-key, duplicate-xml-fields, duplicate-xml-record-id, eval-referenced, - eval-used, incoherent-interpreter-exec-perm, + openerp-exception-warning, + redundant-modulename-xml, + relative-import, + rst-syntax-error, + wrong-tabs-instead-of-spaces, + xml-syntax-error, + assignment-from-none, + attribute-deprecated, + dangerous-default-value, + development-status-allowed, + duplicate-key, + eval-used, license-allowed, manifest-author-string, manifest-deprecated-key, @@ -48,40 +54,28 @@ enable=anomalous-backslash-in-string, method-inverse, method-required-super, method-search, - openerp-exception-warning, pointless-statement, pointless-string-statement, print-used, redundant-keyword-arg, - redundant-modulename-xml, reimported, - relative-import, return-in-init, - rst-syntax-error, sql-injection, too-few-format-args, translation-field, translation-required, unreachable, use-vim-comment, - wrong-tabs-instead-of-spaces, - xml-syntax-error, + missing-manifest-dependency, + too-complex, # messages that do not cause the lint step to fail consider-merging-classes-inherited, - create-user-wo-reset-password, - dangerous-filter-wo-user, deprecated-module, - file-not-used, invalid-commit, - missing-manifest-dependency, - missing-newline-extrafiles, missing-readme, - no-utf8-coding-comment, odoo-addons-relative-import, - old-api7-method-defined, redefined-builtin, - too-complex, - unnecessary-utf8-coding-comment + manifest-external-assets [REPORTS] diff --git a/.pylintrc-mandatory b/.pylintrc-mandatory index 3bf8ceef..e444cb7e 100644 --- a/.pylintrc-mandatory +++ b/.pylintrc-mandatory @@ -17,19 +17,25 @@ disable=all enable=anomalous-backslash-in-string, api-one-deprecated, api-one-multi-together, - assignment-from-none, - attribute-deprecated, class-camelcase, - dangerous-default-value, dangerous-view-replace-wo-priority, - development-status-allowed, duplicate-id-csv, - duplicate-key, duplicate-xml-fields, duplicate-xml-record-id, eval-referenced, - eval-used, incoherent-interpreter-exec-perm, + openerp-exception-warning, + redundant-modulename-xml, + relative-import, + rst-syntax-error, + wrong-tabs-instead-of-spaces, + xml-syntax-error, + assignment-from-none, + attribute-deprecated, + dangerous-default-value, + development-status-allowed, + duplicate-key, + eval-used, license-allowed, manifest-author-string, manifest-deprecated-key, @@ -40,24 +46,18 @@ enable=anomalous-backslash-in-string, method-inverse, method-required-super, method-search, - openerp-exception-warning, pointless-statement, pointless-string-statement, print-used, redundant-keyword-arg, - redundant-modulename-xml, reimported, - relative-import, return-in-init, - rst-syntax-error, sql-injection, too-few-format-args, translation-field, translation-required, unreachable, - use-vim-comment, - wrong-tabs-instead-of-spaces, - xml-syntax-error + use-vim-comment [REPORTS] msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5e8e3a66..00000000 --- a/.travis.yml +++ /dev/null @@ -1,42 +0,0 @@ -language: python -cache: - directories: - - $HOME/.cache/pip - - $HOME/.cache/pre-commit - -python: - - "3.6" - -addons: - postgresql: "9.6" - apt: - packages: - - expect-dev # provides unbuffer utility - -stages: - - test - -jobs: - include: - - stage: test - env: - - TESTS=1 ODOO_REPO="odoo/odoo" MAKEPOT="1" - - stage: test - env: - - TESTS=1 ODOO_REPO="OCA/OCB" -env: - global: - - VERSION="14.0" TESTS="0" LINT_CHECK="0" MAKEPOT="0" - - MQT_DEP=PIP - -install: - - git clone --depth=1 https://github.com/OCA/maintainer-quality-tools.git - ${HOME}/maintainer-quality-tools - - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} - - travis_install_nightly - -script: - - travis_run_tests - -after_success: - - travis_after_tests_success diff --git a/README.md b/README.md index 39602af6..2c52002b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ +[![Support the OCA](https://odoo-community.org/readme-banner-image)](https://odoo-community.org/get-involved?utm_source=repo-readme) + +# apps-store [![Runboat](https://img.shields.io/badge/runboat-Try%20me-875A7B.png)](https://runboat.odoo-community.org/builds?repo=OCA/apps-store&target_branch=14.0) -[![Build Status](https://travis-ci.com/OCA/apps-store.svg?branch=14.0)](https://travis-ci.com/OCA/apps-store) +[![Pre-commit Status](https://github.com/OCA/apps-store/actions/workflows/pre-commit.yml/badge.svg?branch=14.0)](https://github.com/OCA/apps-store/actions/workflows/pre-commit.yml?query=branch%3A14.0) +[![Build Status](https://github.com/OCA/apps-store/actions/workflows/test.yml/badge.svg?branch=14.0)](https://github.com/OCA/apps-store/actions/workflows/test.yml?query=branch%3A14.0) [![codecov](https://codecov.io/gh/OCA/apps-store/branch/14.0/graph/badge.svg)](https://codecov.io/gh/OCA/apps-store) [![Translation Status](https://translation.odoo-community.org/widgets/apps-store-14-0/-/svg-badge.svg)](https://translation.odoo-community.org/engage/apps-store-14-0/?utm_source=widget) -# apps-store - -TODO: add repo description. +Apps store, used on odoo-community.org diff --git a/website_apps_store/controllers/main.py b/website_apps_store/controllers/main.py index 2e891db1..8984925f 100644 --- a/website_apps_store/controllers/main.py +++ b/website_apps_store/controllers/main.py @@ -4,6 +4,8 @@ import base64 import logging +from werkzeug.exceptions import NotFound + from odoo import _, http from odoo.exceptions import UserError, ValidationError from odoo.http import request @@ -20,7 +22,7 @@ class WebsiteSaleCustom(WebsiteSale): def _get_search_domain( - self, search, category, attrib_values, search_in_description=True + self, search, category, attrib_values, search_in_description=True, **post ): domain = request.website.sale_product_domain() if search: @@ -67,26 +69,60 @@ def _get_search_domain( if attrib: domain += [("attribute_line_ids.value_ids", "in", ids)] + if post.get("version"): + field_name = ( + "product_variant_ids.product_template_attribute_value_ids" + + ".product_attribute_value_id.id" + ) + domain += [ + ( + field_name, + "=", + post.get("version"), + ) + ] + if post.get("author"): + domain += [ + ("product_variant_ids.app_author_ids.id", "=", post.get("author")) + ] + if post.get("maturity", False): + domain += [ + ( + "product_variant_ids.app_development_status", + "=", + post.get("maturity"), + ) + ] return domain @http.route() def shop(self, page=0, category=None, search="", ppg=False, **post): - res = super().shop(page=page, category=category, search=search, ppg=ppg, **post) + add_qty = int(post.get("add_qty", 1)) + Category = request.env["product.public.category"] + if category: + category = Category.search([("id", "=", int(category))], limit=1) + if not category or not category.can_access_from_current_website(): + raise NotFound() + else: + category = Category if ppg: try: ppg = int(ppg) + post["ppg"] = ppg except ValueError: - ppg = PPG - post["ppg"] = ppg - else: - ppg = PPG + ppg = False + + if not ppg: + ppg = request.env["website"].get_current_website().shop_ppg or PPG + + ppr = request.env["website"].get_current_website().shop_ppr or 4 attrib_list = request.httprequest.args.getlist("attrib") attrib_values = [[int(x) for x in v.split("-")] for v in attrib_list if v] attributes_ids = {v[0] for v in attrib_values} attrib_set = {v[1] for v in attrib_values} - domain = self._get_search_domain(search, category, attrib_values) + domain = self._get_search_domain(search, category, attrib_values, **post) keep = QueryURL( "/shop", @@ -98,30 +134,11 @@ def shop(self, page=0, category=None, search="", ppg=False, **post): version=post.get("version"), author=post.get("author"), ) - if post.get("version"): - field_name = ( - "product_variant_ids.product_template_attribute_value_ids" - + ".product_attribute_value_id.id" - ) - domain += [ - ( - field_name, - "=", - post.get("version"), - ) - ] - if post.get("author"): - domain += [ - ("product_variant_ids.app_author_ids.id", "=", post.get("author")) - ] - if post.get("maturity", False): - domain += [ - ( - "product_variant_ids.app_development_status", - "=", - post.get("maturity"), - ) - ] + + pricelist_context, pricelist = self._get_pricelist_context() + request.context = dict( + request.context, pricelist=pricelist.id, partner=request.env.user.partner_id + ) url = "/shop" if search: @@ -129,60 +146,86 @@ def shop(self, page=0, category=None, search="", ppg=False, **post): if attrib_list: post["attrib"] = attrib_list - category = request.env["product.public.category"].browse(int(category or 0)) + Product = request.env["product.template"].with_context(bin_size=True) + + search_product = Product.search(domain, order=self._get_search_order(post)) + website_domain = request.website.website_domain() + categs_domain = [("parent_id", "=", False)] + website_domain + if search: + search_categories = Category.search( + [("product_tmpl_ids", "in", search_product.ids)] + website_domain + ).parents_and_self + categs_domain.append(("id", "in", search_categories.ids)) + else: + search_categories = Category + categs = Category.search(categs_domain) + if category: url = "/shop/category/%s" % slug(category) - attribute_id = request.env.ref("apps_product_creator.attribute_odoo_version") - category_all = request.env["product.public.category"].search([]) - versions = request.env["product.attribute.value"].search( - [("attribute_id", "=", attribute_id.id)] - ) - authors = request.env["odoo.author"].search([]) - Product = request.env["product.template"] - - product_count = Product.search_count(domain) + product_count = len(search_product) pager = request.website.pager( - url=url, total=product_count, page=page, step=ppg, scope=7, url_args=post - ) - products = Product.search( - domain, - limit=ppg, - offset=pager["offset"], - order=self._get_search_order(post), + url=url, total=product_count, page=page, step=ppg, scope=5, url_args=post ) + offset = pager["offset"] + products = search_product[offset : offset + ppg] ProductAttribute = request.env["product.attribute"] if products: # get all products without limit - selected_products = Product.search(domain, limit=False) attributes = ProductAttribute.search( - [("attribute_line_ids.product_tmpl_id", "in", selected_products.ids)] + [("product_tmpl_ids", "in", search_product.ids)] ) else: attributes = ProductAttribute.browse(attributes_ids) - res.qcontext.update( - { - "search": search, - "category": category, - "attrib_values": attrib_values, - "attrib_set": attrib_set, - "pager": pager, - "products": products, - "search_count": product_count, # common for all searchbox - "bins": TableCompute().process(products, ppg), - "category_all": category_all, - "versions": versions, - "authors": authors, - "version": post.get("version"), - "author": post.get("author"), - "attributes": attributes, - "keep": keep, - "maturity": post.get("maturity"), - } - ) - return res + layout_mode = request.session.get("website_sale_shop_layout_mode") + if not layout_mode: + if request.website.viewref("website_sale.products_list_view").active: + layout_mode = "list" + else: + layout_mode = "grid" + + values = { + "search": search, + "order": post.get("order", ""), + "category": category, + "attrib_values": attrib_values, + "attrib_set": attrib_set, + "pager": pager, + "pricelist": pricelist, + "add_qty": add_qty, + "products": products, + "search_count": product_count, # common for all searchbox + "bins": TableCompute().process(products, ppg, ppr), + "ppg": ppg, + "ppr": ppr, + "categories": categs, + "attributes": attributes, + "keep": keep, + "search_categories_ids": search_categories.ids, + "layout_mode": layout_mode, + "maturity": post.get("maturity"), + "version": post.get("version"), + "author": post.get("author"), + # should we filter according current product result list? + "versions": request.env["product.attribute.value"].search( + [ + ( + "attribute_id", + "=", + request.env.ref( + "apps_product_creator.attribute_odoo_version" + ).id, + ) + ] + ), + "authors": request.env["odoo.author"].search([]), + "category_all": request.env["product.public.category"].search([]), + } + if category: + values["main_object"] = category + return request.render("website_sale.products", values) @http.route( [