Skip to content

Commit

Permalink
[IMP] vuestorefront: various improvements, tests
Browse files Browse the repository at this point in the history
This is a forked module previously called ``graphql_vuestorefront``. It
is backwards incompatible module, so it should not be installed
alongside ``graphql_vuestorefront``. Custom changes different than in
forked module:

* ``get_product_list`` in ``schemas.product`` now uses offset/limit in
  standard
  search instead of searching for all possible products and then slicing
  (which has terrible performance).
* ``website_slug``: (renamed to ``slug``) got rid of translate awareness
  (to simplify it), and not adding
  slashes in slug, because slug is not supposed to have that. (on
  category, removed validation as it was forcing to use slash. Added
  unique constraint, because category slug can be entered manually).
* Split odoo model files into more appropriate, to make it more readable
  and maintainable.
* Removed ``public_categ_slug_ids`` field as it was confusing and
  redundant field that was very slow to compute for large amount of
  products.
* implemented ``products`` ``category_slug`` filter.
* moved Product schema domain method to ``product.template`` to be
  extendable.

Addresses
---------

* Moved address domain methods on ``res.partner`` to be extendable.
* Moved preparation of partner (address) creation/update on ``res.partner
  to be extendable.

Sale Order
----------

* Added UpdateOrder mutation to be able to update some data on sale
  order.

Users
-----

* Can pass extra data when signing up (only ``vat`` for now).
  • Loading branch information
oerp-odoo committed Dec 14, 2023
1 parent e59a075 commit 8cdc527
Show file tree
Hide file tree
Showing 32 changed files with 1,272 additions and 672 deletions.
209 changes: 132 additions & 77 deletions vuestorefront/README.rst
Original file line number Diff line number Diff line change
@@ -1,116 +1,171 @@
======================
Graphql Vue Storefront
======================
==============
Vue Storefront
==============

This is a forked module previously called ``graphql_vuestorefront``. It
is backwards incompatible module, so it should not be installed
alongside ``graphql_vuestorefront``. Custom changes different than in
forked module:

* ``get_product_list`` in ``schemas.product`` now uses offset/limit in standard
search instead of searching for all possible products and then slicing (
which has terrible performance).
* ``website_slug``: (renamed to ``slug``) got rid of translate awareness (
to simplify it), and not adding
slashes in slug, because slug is not supposed to have that. (on category, removed
validation as it was forcing to use slash. Added unique constraint, because
category slug can be entered manually).
* Split odoo model files into more appropriate, to make it more readable and
maintainable.
* Removed ``public_categ_slug_ids`` field as it was confusing and redundant
field that was very slow to compute for large amount of products.
* implemented ``products`` ``category_slug`` filter.
* moved Product schema domain method to ``product.template`` to be extendable.

Addresses
---------

* Moved address domain methods on ``res.partner`` to be extendable.
* Moved preparation of partner (address) creation/update on ``res.partner to
be extendable.
Sale Order
----------
* Added UpdateOrder mutation to be able to update some data on sale order.
Users
-----
* Can pass extra data when signing up (only ``vat`` for now).

Login
=====

To authenticate, use the default /web/session/authenticate endpoint.
Example using axios:

axios.post('<domain>/web/session/authenticate', {
"jsonrpc": "2.0",
"method": "call",
"params": {
"db": <database_name>,
"login": <user_login>,
"password": <user_password>
}}, {
"withCredentials": true
})
.. code-block::
axios.post('<domain>/web/session/authenticate', {
"jsonrpc": "2.0",
"method": "call",
"params": {
"db": <database_name>,
"login": <user_login>,
"password": <user_password>
}}, {
"withCredentials": true
})
Logout
======

axios.post('<domain>/web/session/destroy', {
"jsonrpc": "2.0",
"method": "call"
}, {
"withCredentials": true
})
.. code-block::
axios.post('<domain>/web/session/destroy', {
"jsonrpc": "2.0",
"method": "call"
}, {
"withCredentials": true
})
Add to Cart
===========

axios.post('<domain>/shop/cart/update_json', {
"jsonrpc": "2.0",
"method": "call",
"params": {
"product_id": <product_id>,
"add_qty": <qty>
}}, {
"withCredentials": true
})
.. code-block::
axios.post('<domain>/shop/cart/update_json', {
"jsonrpc": "2.0",
"method": "call",
"params": {
"product_id": <product_id>,
"add_qty": <qty>
}}, {
"withCredentials": true
})
Add to wishlist
===============

axios.post('<domain>/shop/wishlist/add', {
"jsonrpc": "2.0",
"method": "call",
"params": {
"product_id": <product_id>,
}}, {
"withCredentials": true
})
.. code-block::
axios.post('<domain>/shop/wishlist/add', {
"jsonrpc": "2.0",
"method": "call",
"params": {
"product_id": <product_id>,
}}, {
"withCredentials": true
})
Remove from wishlist
====================

axios.post('<domain>/shop/wishlist/remove/<product_wishlist_id>', {
"jsonrpc": "2.0",
"method": "call"
}, {
"withCredentials": true
})
.. code-block::
axios.post('<domain>/shop/wishlist/remove/<product_wishlist_id>', {
"jsonrpc": "2.0",
"method": "call"
}, {
"withCredentials": true
})
Get the rate for a shipping method
==================================

axios.post('<domain>/shop/carrier_rate_shipment', {
"jsonrpc": "2.0",
"method": "call"
"params": {
"carrier_id": <ShippingMethod.ID>,
}}, {
"withCredentials": true
})
.. code-block::
axios.post('<domain>/shop/carrier_rate_shipment', {
"jsonrpc": "2.0",
"method": "call"
"params": {
"carrier_id": <ShippingMethod.ID>,
}}, {
"withCredentials": true
})
Get all product template attributes for product template page
=============================================================

axios.post('<domain>/shop/get_combinations/<int:product_template_id>', {
"jsonrpc": "2.0",
"method": "call"
}, {
"withCredentials": true
})
.. code-block::
axios.post('<domain>/shop/get_combinations/<int:product_template_id>', {
"jsonrpc": "2.0",
"method": "call"
}, {
"withCredentials": true
})
Get product id and price after selecting the combination on the product template page
=====================================================================================

axios.post('<domain>/shop/get_combination_info/<int:product_template_id>', {
"jsonrpc": "2.0",
"method": "call"
"params": {
"combination_ids": [1, 2],
add_qty=1
}}, {
"withCredentials": true
})
.. code-block::
axios.post('<domain>/shop/get_combination_info/<int:product_template_id>', {
"jsonrpc": "2.0",
"method": "call"
"params": {
"combination_ids": [1, 2],
add_qty=1
}}, {
"withCredentials": true
})
Get products for shop with search, category, sort, count, pagination and attributes filtering
=============================================================================================

axios.post('<domain>/shop/products', {
"jsonrpc": "2.0",
"method": "call"
"params": {
"search": "",
"category_id": 1,
"offset": 0,
"ppg": 20,
"attrib_list": []
}}, {
"withCredentials": true
})
.. code-block::
axios.post('<domain>/shop/products', {
"jsonrpc": "2.0",
"method": "call"
"params": {
"search": "",
"category_id": 1,
"offset": 0,
"ppg": 20,
"attrib_list": []
}}, {
"withCredentials": true
})
10 changes: 5 additions & 5 deletions vuestorefront/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,13 @@ def vsf_categories(self):

if website.default_lang_id:
lang_code = website.default_lang_id.code
domain = [("website_slug", "!=", False)]
domain = [("slug", "!=", False)]

for category in (
request.env["product.public.category"].sudo().search(domain)
):
category = category.with_context(lang=lang_code)
categories.append(category.website_slug)
categories.append(category.slug)

return Response(
json.dumps(categories),
Expand All @@ -223,14 +223,14 @@ def vsf_products(self):

if website.default_lang_id:
lang_code = website.default_lang_id.code
domain = [("website_published", "=", True), ("website_slug", "!=", False)]
domain = [("website_published", "=", True), ("slug", "!=", False)]

for product in request.env["product.template"].sudo().search(domain):
product = product.with_context(lang=lang_code)

url_parsed = urlparse(product.website_slug)
url_parsed = urlparse(product.slug)
name = os.path.basename(url_parsed.path)
path = product.website_slug.replace(name, "")
path = product.slug.replace(name, "")

products.append(
{
Expand Down
21 changes: 13 additions & 8 deletions vuestorefront/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# Copyright 2023 ODOOGAP/PROMPTEQUATION LDA
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from . import invalidate_cache
from . import ir_http
from . import website
from . import product
from . import res_config_settings
from . import res_users
from . import payment_transaction
from . import (
invalidate_cache,
ir_http,
website,
product_public_category,
product_template,
product_product,
res_config_settings,
res_users,
payment_transaction,
res_partner,
sale_order,
)
31 changes: 21 additions & 10 deletions vuestorefront/models/invalidate_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,26 +125,37 @@ def request_vsf_cache_invalidation(self):
# FIXME: this probably should not be done.
self.env.cr.commit() # pylint: disable=invalid-commit

# TODO: these methods don't make sense. They have same code. Need
# to redesign this, when its clear what they actually should return.
def _get_product_tags(self, product_ids):
tags = ",".join(f"P{product_id}" for product_id in product_ids)
category_ids = (
categories = (
self.env["product.template"]
.search([("id", "in", product_ids)])
.mapped("public_categ_slug_ids")
.ids
.mapped("public_categ_ids")
)
if category_ids:
tags += "," + ",".join(f"C{category_id}" for category_id in category_ids)
ancestor_categs = self.env["product.public.category"]
for parent in categories.get_ancestors():
ancestor_categs |= parent
categories |= ancestor_categs
if categories:
tags += "," + ",".join(f"C{category_id}" for category_id in categories.ids)
return tags

# TODO: this method most likely is incorrect. Should not expect product_ids as
# it looks like when its called, it actually calls with category_ids
# (product.public.category).
def _get_category_tags(self, product_ids):
tags = ",".join(f"P{product_id}" for product_id in product_ids)
category_ids = (
categories = (
self.env["product.template"]
.search([("id", "in", product_ids)])
.mapped("public_categ_slug_ids")
.ids
.mapped("public_categ_ids")
)
if category_ids:
tags += "," + ",".join(f"C{category_id}" for category_id in category_ids)
ancestor_categs = self.env["product.public.category"]
for parent in categories.get_ancestors():
ancestor_categs |= parent
categories |= ancestor_categs
if categories:
tags += "," + ",".join(f"C{category_id}" for category_id in categories.ids)
return tags
Loading

0 comments on commit 8cdc527

Please sign in to comment.