Skip to content

Commit

Permalink
Merge pull request #4497 from korycins/enable_extension_manager_for_t…
Browse files Browse the repository at this point in the history
…axes

Enable extension manager for taxes
  • Loading branch information
korycins committed Jul 30, 2019
2 parents 7feb6d9 + e875d72 commit cbfafe9
Show file tree
Hide file tree
Showing 86 changed files with 1,647 additions and 1,079 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ All notable, unreleased changes to this project will be documented in this file.
- Add mutation for deleting account - #4494 by @fowczarek
- New translations:
- Greek
- Extensions Manager - #4497 by @korycins
- Migration of tax logic into a plugin architecture - #4497 by @korycins

- Fix searches and pickers - #4487 by @dominik-zeglen
- Fix dashboard menu styles - #4491 by @benekex2
- Do not allow random ids to appear in snapshots - #4495 by @dominik-zeglen
Expand Down
1 change: 1 addition & 0 deletions docs/customization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Customizing Saleor
customization/emails
customization/frontend
customization/backend
customization/extensions
customization/i18n
customization/tests
customization/ci
Expand Down
124 changes: 124 additions & 0 deletions docs/customization/extensions.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
Extensions
==========
Saleor has implemented extensions architecture.
It includes hooks for most basic operations like calculation of prices in the checkout or
calling some actions when an order has been created.


Plugin
------
Saleor has some plugins implemented by default. These plugins are located in ``saleor.core.extensions.plugins``.
The ExtensionManager needs to receive a list of enabled plugins. It can be done by including the Python plugin path in the
``settings.PLUGINS`` list.

Writing Your Own Plugin
^^^^^^^^^^^^^^^^^^^^^^^
A custom plugin has to inherit from the BasePlugin class. It should overwrite base methods. The plugin needs to be added
to the ``settings.PLUGINS``
Your own plugin can be written as a class which has callable instances, like this:


``custom/plugin.py``

.. code-block:: python
from django.conf import settings
from urllib.parse import urljoin
from ...base_plugin import BasePlugin
from .tasks import api_post_request_task
class CustomPlugin(BasePlugin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._enabled = bool(settings.CUSTOM_PLUGIN_KEYS)
def postprocess_order_creation(self, order: "Order", previous_value: Any):
if not self._enabled:
return
data = {}
transaction_url = urljoin(settings.CUSTOM_API_URL, "transactions/createoradjust")
api_post_request_task.delay(transaction_url, data)
.. note::
There is no need to implement all base methods. ``ExtensionManager`` will use default values for methods that are not implemented.

Activating Plugin
^^^^^^^^^^^^^^^^^
To activate the plugin, add it to the ``PLUGINS`` list in your Django settings:


``settings.py``

.. code-block:: python
PLUGINS = ["saleor.core.extensions.plugins.custom.CustomPlugin", ]
ExtensionsManager
-----------------
ExtensionsManager is located in ``saleor.core.extensions.base_plugin``.
It is a class responsible for handling all declared plugins and serving a response from them.
It serves a default response in case of a non-declared plugin. There is a possibility to overwrite an ExtensionsManager
class by implementing it on its own. Saleor will discover the manager class by taking the declared path from
``settings.EXTENSIONS_MANAGER``.
Each Django request object has its own manager included as the ``extensions`` field. It is attached in the Saleor middleware.


BasePlugin
----------
BasePlugin is located in ``saleor.core.extensions.base_plugin``. It is an abstract class for storing all methods
available for any plugin. All methods take the ``previous_value`` parameter. This contains a value
calculated by the previous plugin in the queue. If the plugin is first in line, it will use the default value calculated by
the manager.


Celery Tasks
------------
Some plugin operations should be done asynchronously. If Saleor has Celery enabled, it will discover all tasks
declared in ``tasks.py`` in the plugin directories.


``plugin.py``


.. code-block:: python
def postprocess_order_creation(self, order: "Order", previous_value: Any):
if not self._enabled:
return
data = {}
transaction_url = urljoin(get_api_url(), "transactions/createoradjust")
api_post_request_task.delay(transaction_url, data)
``tasks.py``

.. code-block:: python
import json
from celery import shared_task
from typing import Any, Dict
import requests
from requests.auth import HTTPBasicAuth
from django.conf import settings
@shared_task
def api_post_request(
url: str,
data: Dict[str, Any],
):
try:
username = "username"
password = "password"
auth = HTTPBasicAuth(username, password)
requests.post(url, auth=auth, data=json.dumps(data), timeout=settings.TIMEOUT)
except requests.exceptions.RequestException:
return
4 changes: 4 additions & 0 deletions saleor/celeryconf.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import os

from celery import Celery
from django.conf import settings

from .core.extensions import discover_plugins_modules

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "saleor.settings")

Expand All @@ -10,3 +13,4 @@

app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()
app.autodiscover_tasks(lambda: discover_plugins_modules(settings.PLUGINS))
9 changes: 6 additions & 3 deletions saleor/checkout/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
from django_countries.fields import Country, LazyTypedChoiceField

from ..core.exceptions import InsufficientStock
from ..core.extensions.manager import get_extensions_manager
from ..core.taxes import display_gross_prices
from ..core.taxes.interface import apply_taxes_to_shipping
from ..core.utils import format_money
from ..discount.models import NotApplicable, Voucher
from ..shipping.models import ShippingMethod, ShippingZone
Expand Down Expand Up @@ -70,7 +70,7 @@ def __init__(self, *args, **kwargs):
self.product = kwargs.pop("product")
self.discounts = kwargs.pop("discounts", ())
self.country = kwargs.pop("country", {})
self.taxes = kwargs.pop("taxes", None)
self.extensions = kwargs.pop("extensions", None)
super().__init__(*args, **kwargs)

def add_error_i18n(self, field, error_name, fmt: Any = tuple()):
Expand Down Expand Up @@ -294,12 +294,13 @@ class ShippingMethodChoiceField(forms.ModelChoiceField):
"""

shipping_address = None
extensions = None
widget = forms.RadioSelect()

def label_from_instance(self, obj):
"""Return a friendly label for the shipping method."""
if display_gross_prices():
price = apply_taxes_to_shipping(
price = self.extensions.apply_taxes_to_shipping(
obj.price, shipping_address=self.shipping_address
).gross
else:
Expand All @@ -324,6 +325,7 @@ def __init__(self, *args, **kwargs):
from .utils import get_valid_shipping_methods_for_checkout

discounts = kwargs.pop("discounts")
extensions = get_extensions_manager()
super().__init__(*args, **kwargs)
shipping_address = self.instance.shipping_address
country_code = shipping_address.country.code
Expand All @@ -332,6 +334,7 @@ def __init__(self, *args, **kwargs):
)
self.fields["shipping_method"].queryset = qs
self.fields["shipping_method"].shipping_address = shipping_address
self.fields["shipping_method"].extensions = extensions

if self.initial.get("shipping_method") is None:
shipping_methods = qs.all()
Expand Down

0 comments on commit cbfafe9

Please sign in to comment.