Skip to content

Commit

Permalink
upgrade documentaion to version 1.2
Browse files Browse the repository at this point in the history
  • Loading branch information
jrief committed May 15, 2020
1 parent 5e12b9e commit 52127ee
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 133 deletions.
13 changes: 10 additions & 3 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
Changelog for django-SHOP
=========================

1.2
===
* Add support for Django-3.0.
* For full-text searching, replace Haystack against elasticsearch-dsl.
* Drop support for Python 2.7 and 3.4.
* Drop support for Django-1.11 and 2.0.


1.1.4
=====
* Fix rendering bug in Product Gallery plugin.
Expand Down Expand Up @@ -443,7 +451,7 @@ Changelog for django-SHOP
* Added method ``post_process_cart_item`` to the Cart Modifiers.
* In ``CartItem`` the ``product_code`` is mandatory now. It moves from being optionally kept in dict
``CartItem.extra`` into the ``CartItem`` model itself. This simplifies a lot of boilerplate code,
otherwise required by the merchant implementation. Please read :ref:`release-notes/0.10` for details.
otherwise required by the merchant implementation.
* In :class:`shop.models.product.BaseProduct` added a hook method ``get_product_variant(self, **kwargs)``
which can be overridden by products with variations to return a product variant.

Expand All @@ -470,8 +478,7 @@ Changelog for django-SHOP
* Minimum required version of django-filer is now 1.2.5.
* Minimum required version of djangocms-cascade is now 0.10.2.
* Minimum required version of djangoshop-stripe is now 0.2.0.
* Changed the default address models to be more generic. Please read the
:ref:`release-notes/0.9` if you are upgrading from 0.9.0 or 0.9.1.
* Changed the default address models to be more generic.
* Fixed :py:meth:`shop.money.fields.decontruct` to avoid repetitive useless generation of migration
files.
* Using cached_property decoration for methods ``unit_price`` and ``line_total`` in
Expand Down
22 changes: 9 additions & 13 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@ Django-SHOP documentation
features


Upgrading
=========
If you are upgrading from an earlier version, please be sure to read the
:ref:`release-notes`.


Tutorial
========

Expand Down Expand Up @@ -92,12 +86,15 @@ Django/Python compatibility table
=========== === === ==== ==== === === === === ===
django-SHOP Django Python
----------- ------------------------- ------------------
\ 1.8 1.9 1.10 1.11 2.0 2.7 3.4 3.5 3.6
=========== === === ==== ==== === === === === ===
0.10.x ✓ ✓ ⨯ ⨯ ⨯ ✓ ✓ ✓ ⨯
0.11.x ⨯ ✓ ✓ ⨯ ⨯ ✓ ✓ ✓ ✓
0.12.x ⨯ ⨯ ⨯ ✓ ⨯ ✓ ✓ ✓ ✓
0.13.x ⨯ ⨯ ⨯ ✓ ⨯ ✓ ✓ ✓ ✓
\ 1.8 1.9 1.10 1.11 2.0 2.7 3.4 3.5 3.6 3.7 3.8
=========== === === ==== ==== === === === === === === ===
0.10.x ✓ ✓ ⨯ ⨯ ⨯ ✓ ✓ ✓ ⨯
0.11.x ⨯ ✓ ✓ ⨯ ⨯ ✓ ✓ ✓ ✓
0.12.x ⨯ ⨯ ⨯ ✓ ⨯ ✓ ✓ ✓ ✓
0.13.x ⨯ ⨯ ⨯ ✓ ⨯ ✓ ✓ ✓ ✓
1.0.x ⨯ ⨯ ⨯ ✓ ⨯ ✓ ✓ ✓ ✓
1.1.x ⨯ ⨯ ⨯ ✓ ⨯ ✓ ✓ ✓ ✓
1.2.x ⨯ ⨯ ⨯ ✓ ⨯ ⨯ ⨯ ✓ ✓
=========== === === ==== ==== === === === === ===


Expand All @@ -108,7 +105,6 @@ Development and Community
:maxdepth: 1

changelog
release-notes/index
faq
contributing
authors
Expand Down
155 changes: 109 additions & 46 deletions docs/reference/catalog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,25 @@
Catalog
=======

The catalog presumably is that part, where customers of our e-commerce site hopefully spend most of
their time. Often it even makes sense, to start the :ref:`reference/catalog-list` on the main
landing page.
The catalog presumably is that part, where customers of our e-commerce site spend most of their
time. Often it even makes sense, to start the :ref:`reference/catalog-list` on the main landing
page.

In this documentation we presume that categories of products are built up using specially tagged
CMS pages in combination with a `django-CMS apphook`_. This works perfectly well for most
implementation, but some sites may require categories implemented independently of the CMS.
**django-CMS** pages in combination with a `django-CMS apphook`_. This works perfectly well for most
implementations, but some sites may require categories implemented independently of the CMS.

Using an external **django-SHOP** plugin for managing categories is a very conceivable solution,
and we will see separate implementations for this feature request. Using such an external category
plugin can make sense, if this e-commerce site requires hundreds of hierarchical levels and/or
these categories require a set of attributes which are not available in CMS pages. If you are
going to use externally implemented categories, please refer to their documentation, since in this
document, we proceed using CMS pages as categories.
plugin can make sense, if an e-commerce site requires hundreds of hierarchical levels and/or
these category implementations can provide functionality, which is not available in **django-CMS**
pages. If you are going to use externally implemented categories, please refer to their
documentation, since in this document, we proceed using standard CMS pages as product categories.

It should be emphasized, that nowadays the classical hierarchy of categories is no longer
contemporary. Instead many merchants tag their products with different attributes. This provides a
better browsing experience, since customers usually filter by product characteristics, rather than
categories.

A nice aspect of **django-SHOP** is, that it doesn't require the programmer to write any special
Django Views in order to render the catalog. Instead all merchant dependent business logic goes
Expand All @@ -29,11 +34,15 @@ into a serializer, which in this documentation is referred as ``ProductSerialize
Catalog List View
=================

In this documentation, the catalog list view is implemented as a **django-CMS** page. Depending on
In this documentation, the *Catalog List View* is implemented as a **django-CMS** page. Depending on
whether the e-commerce aspect of that site is the most prominent part or just a niche of the CMS,
select an appropriate location in the page tree and create a new page. This will become the root
of our catalog list.

.. note::
If required, we can add as many catalog list views as we want, and distribute them accross the
CMS page tree.

But first we must :ref:`reference/create-CatalogListApp`.


Expand All @@ -42,9 +51,9 @@ But first we must :ref:`reference/create-CatalogListApp`.
Create the ``CatalogListApp``
-----------------------------

To retrieve a list of product models, the Catalog List View requires a `django-CMS apphook`_. The
class ``CatalogListApp`` must be added into a file named ``cms_apps.py`` and located in the root
folder of the merchant's project:
To retrieve a list of product models, the *Catalog List View* requires a `django-CMS apphook`_. For
this, we must inherit from :class:`shop.cms_apphooks.CatalogListCMSApp` and add that class
declaration to a file named ``cms_apps.py``, located in the root folder of our merchant's project:

.. code-block:: python
:caption: myshop/cms_apps.py
Expand All @@ -57,17 +66,19 @@ folder of the merchant's project:
def get_urls(self, page=None, language=None, **kwargs):
from shop.views.catalog import AddToCartView, ProductListView, ProductRetrieveView
filter_backends = [CMSPagesFilterBackend]
filter_backends.extend(api_settings.DEFAULT_FILTER_BACKENDS)
return [
url(r'^(?P<slug>[\w-]+)/add-to-cart', AddToCartView.as_view()),
url(r'^(?P<slug>[\w-]+)', ProductRetrieveView.as_view()),
url(r'^', ProductListView.as_view(
filter_backends=[CMSPagesFilterBackend] + list(api_settings.DEFAULT_FILTER_BACKENDS),
filter_backends=filter_backends,
)),
]
apphook_pool.register(CatalogListApp)
In the page tree editor of **django-CMS**, create a new page at an appropriate location. As the
In the page tree editor of **django-CMS**, we create a new page at an appropriate node. As the
page title and slug we should use something describing our product catalog in a way, both meaningful
to the customers as well as to search engines.

Expand All @@ -82,10 +93,10 @@ Change into the **Advanced Settings** of the CMS page, which shall act as the ca

Then we go into the page's **Preview** mode and open the **Structure menu** on the right side of the
**django-CMS** toolbar. Now locate the placeholder named **Main Content**. Add a Container plugin,
followed by a Row and then a Column plugin. As the child of this column, choose the **Catalog List
View** plugin from section **Shop**.
followed by a Row and then by a Column plugin. As the child of this column, choose the *Catalog List
View* plugin from section **Shop**.

Finally we publish the page, it probably doesn't contain any products yet. To fill it we first have
Finally we publish the page, it probably doesn't contain any products yet. To fill it, we first have
to :ref:`reference/assign-products-to-cms-page`.

Remember to repeat this procedure, and add one CMS pages per category, in order to create a
Expand All @@ -100,7 +111,7 @@ Assign Products to CMS Pages
Here the :class:`shop.views.catalog.ProductListView` is configured to render the catalog list of
products assigned to one or more CMS pages. For this purpose we use the filter backend
:class:`shop.rest.filters.CMSPagesFilterBackend`. In order to decide to which CMS page a product is
assigned to, our Product Model must inherit from :class:`shop.models.product.CMSPageReferenceMixin`.
assigned to, our product model must inherit from :class:`shop.models.product.CMSPageReferenceMixin`.
This is because we must add a reference to the CMS pages our products are assigned to. A typical
product might be declared as:

Expand Down Expand Up @@ -129,15 +140,15 @@ product might be declared as:
objects = BaseProductManager()
An important part of this product model is the category ``cms_pages = ManyToManyField(...)``.
Mapping a relationship between the CMS pages and itself, the merchant can emulate categories by
An important part of this product model is the field ``cms_pages = ManyToManyField(...)``.
Mapping a relationship between CMS pages and products, the merchant can emulate categories by
assigning a product to one ore more CMS pages. Products added to those CMS pages, then shall be
visible in the **Catalog List View** plugin.
visible in the *Catalog List View* plugin.

As we work with deferred models, we can not use the mapping table, which normally is generated
automatically for Many-to-Many fields by the Django framework. Instead, we must reference the
mapping table :class:`shop.models.defaults.mapping.ProductPage` using the ``though`` parameter, when
declaring the field ``cms_pages``.
automatically for many-to-many fields by the Django framework. Instead, we must refer to the
mapping table :class:`shop.models.defaults.mapping.ProductPage` explicitely, using the ``though``
parameter, when declaring the field ``cms_pages``.


.. _reference/product-summary-serializer:
Expand All @@ -147,12 +158,13 @@ Product Summary Serializer

In order to render the list view, we need to identify the fields common to all offered products.
This is because when rendering a list view, we usually want do have a consistent representation for
all products in our catalog. Since this catalog list can be rendered either by the server (using
``CMSPageRenderer``), or by the AngularJS directive ``shop-catalog-list`` on the client (using
``JSONRenderer``), we must provide some functionality to serialize a summary representation for all
the products we want to list. This separation is important, so that we can reuse the same Django
View (``ProductListView``), whenever we switch from the server-side rendered catalog list into
infinite scroll mode.
all products in our catalog. Since this catalog list can be rendered either by the server using the
class :class:`shop.rest.renderers.CMSPageRenderer`, or by the client using the AngularJS directive
``shop-catalog-list``, we must provide some functionality to serialize a summary representation for
all the products we want to list. This separation is important, so that we can reuse the view class
:class:`shop.views.catalog.ProductListView`, whenever we switch from the server-side rendered
catalog list into infinite scroll mode, which for technical reasons can only be rendered by the
client.

For this purpose, we have to declare a product summary serializer using the configuration directive
``SHOP_PRODUCT_SUMMARY_SERIALIZER``. Remember that **django-SHOP** does not impose which fields a
Expand All @@ -179,21 +191,22 @@ into a HTML snippet, which here we name ``media``. Using method
serializer itself, looking for a Django template following these rules:

* look for a template named :samp:`{app_label}/products/catalog-{product-model-name}-{field-name}.html`
[1]_ [2]_ [3]_, otherwise
* look for a template named :samp:`{app_label}/products/catalog-product-{field-name}.html`` [1]_ [3]_,
[#app_label]_ [#product-model-name]_ [#field-name]_, otherwise
* look for a template named :samp:`{app_label}/products/catalog-product-{field-name}.html``
[#app_label]_ [#field-name]_,
otherwise
* use the template ``shop/product/catalog-product-media.html``.

.. [1] :samp:`{app_label}` is the app label of the project in lowercase.
.. [2] :samp:`{product-model-name}` is the class name of the product model in lowercase.
.. [3] :samp:`{field-name}` can be any lowercased identifier, but by convenience shall be the name
.. [#app_label] :samp:`{app_label}` is the app label of the project in lowercase.
.. [#product-model-name] :samp:`{product-model-name}` is the class name of the product model in lowercase.
.. [#field-name] :samp:`{field-name}` can be any lowercased identifier, but by convenience shall be the name
of the serializer field. In this example we use ``media`` as field name.
.. note::
When rendering images, we have to create a thumbnailed version and put its URL into a
``<img src="..." />`` tag. This means that we then have to know the thumbnailed size of the
final image, so that the templatetag `thumb`_ from the easythumbnail library knows what to do.
Otherwise we would have to refer to the original, often much bigger image and thumbnail it
Otherwise we would have to refer to the original, often much heavier image and thumbnail it
on the fly, which would be pretty inefficient.

To test if that serializer works properly, we can examine the raw content of the declared fields by
Expand All @@ -203,14 +216,16 @@ representation of the context as JSON.
.. _thumb: https://easy-thumbnails.readthedocs.io/en/latest/usage/#thumbnail-tag


.. _reference/customized-product-serializer:

Customizing the Product Summary Serializer
..........................................

In case we need :ref:`reference/additional-serializer-fields`, let's add them to a customized
product serializer class using the `serializer fields`_ from the Django RESTFramework library. This
can be useful for product serializers which shall provide additional information on our catalog list
view. If we have to map fields from our product model, just add them to the list of fields in the
``Meta``-class. For example as:
In case we need serialized content from other fields of our product model, let's add them to a
customized product serializer class: For this we use the `serializer fields`_ from the Django's
RESTFramework library. This can be useful for product serializers, which shall provide additional
information on our catalog list view. If we have to map fields from our product model, just add
them to the list of fields in the ``Meta``-class. For example as:

.. code-block:: python
Expand Down Expand Up @@ -262,7 +277,7 @@ A product detail view is rendered by the :class:`shop.views.catalog.ProductRetri
*not* managed by **django-CMS**. Instead, this product detail view behaves like a normal Django
view, with its own context objects and rendered by a specifc template. This is because we often
have thousands of different products and creating one CMS page for each of them, would be a far
bigger effort, rather than creating a specific template for each product type.
bigger effort, rather than handcrafting a specific template for each product type.
When rendering a product's detail page, the ``ProductRetrieveView`` looks for a template suitable
for the given product type, following these rules:
Expand Down Expand Up @@ -294,8 +309,8 @@ a video, or whatever else is available from the **django-CMS** plugin system.
.. note::
The built-in product model :class:`shop.models.defaults.commodity.Commodity` makes heavy
use of that placeholder field. The commodity model actually doesn't offer any other fields,
other than the product's code, name and price. So all relevant information must be added to the
product's detail view using the **django-CMS** structure editor.
other than the product's code, its name and price. So all relevant information must be added to
the product's detail view using the **django-CMS** structure editor.
Customizing the Product Detail Serializer
Expand Down Expand Up @@ -330,7 +345,7 @@ detail view, there is an extra endpoint ending in ``.../add-to-cart``. Its URL p
:class:`shop.views.catalog.AddToCartView`. This view handles the communication between the control
form for adding the given product to the cart on the client, and the REST endpoints on the server.
Each product's detail page shall implement an element containing the AngularJS directive
Each product's detail page shall implement a HTML element containing the AngularJS directive
``shop-add-to-cart``. This directive fetches the availability, price and cart status, and fills out
the "add to cart" form. If the customer submits that form data, the item is added either to the
cart, or the watch-list.
Expand All @@ -345,6 +360,54 @@ For products with a **django-CMS** placeholder field, the merchant can also use
page. Products of type "Commodity" make use of this plugin.
Products with variations
------------------------
In some situations, it might be neccessary to use a custom endpoint for adding a product to the
cart. This for instance is required, when the product to be added contains variations. We then
rewrite our ``CatalogListApp`` to use this url pattern:
.. code-block:: python
class CatalogListApp(CatalogListCMSApp):
def get_urls(self, page=None, language=None, **kwargs):
...
return [
...
url(r'^(?P<slug>[\w-]+)/add-product-to-cart', AddToCartView.as_view(
serializer_class=AddProductWithVariationsSerializer,
)),
...
]
We then create a special serializer for that view:
.. code-block:: python
from shop.models.cart import CartModel
from shop.serializers.defaults.catalog import AddToCartSerializer
class AddProductWithVariationsSerializer(AddToCartSerializer):
def get_instance(self, context, data, extra_args):
product = context['product']
cart = CartModel.objects.get_from_request(context['request'])
variant = product.get_product_variant(product_code=data['product_code'])
is_in_cart = bool(product.is_in_cart(cart, product_code=variant.product_code))
instance = {
'product': product.id,
'product_code': variant.product_code,
'unit_price': variant.unit_price,
'is_in_cart': is_in_cart,
}
return instance
This serializer is adopted to a product with variations. Each variation of the product provides
its own product code and a price. Additionally we want to know, whether the same variation of
that product is already in the cart (increasing the quantity), or if it has to be considered as
different product (adding a new one to the cart). For indicating this state, the serializer returns
a flag, named ``is_in_cart``.
Admin Integration
=================
Expand Down
Loading

0 comments on commit 52127ee

Please sign in to comment.