kopia lustrzana https://github.com/longclawshop/longclaw
Add to basket template tag & docs (#82)
* fixes #61 * Update travis * Update changelog for 0.2 * Add alex as contributor * Docs update * tests in longclawcore package * Remove eggs dir * update ignores * Add last entry to changelog * fixes #79pull/153/head 0.2.0
rodzic
7e67fcc9cd
commit
bcec8915c1
|
@ -52,4 +52,5 @@ docs/_build
|
|||
|
||||
|
||||
webpack-stats.json
|
||||
*bundle.js*
|
||||
*bundle.js*
|
||||
.eggs/
|
|
@ -5,7 +5,7 @@ language: python
|
|||
python:
|
||||
- "3.5"
|
||||
|
||||
env:
|
||||
env:
|
||||
- TOX_ENV=py35-django-18
|
||||
- TOX_ENV=py34-django-18
|
||||
- TOX_ENV=py33-django-18
|
||||
|
@ -13,12 +13,14 @@ env:
|
|||
- TOX_ENV=py35-django-19
|
||||
- TOX_ENV=py34-django-19
|
||||
- TOX_ENV=py27-django-19
|
||||
- TOX_ENV=py35-django-110
|
||||
- TOX_ENV=py35-django-111
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
|
||||
install:
|
||||
install:
|
||||
- . $HOME/.nvm/nvm.sh
|
||||
- nvm install stable
|
||||
- nvm use stable
|
||||
|
|
|
@ -10,4 +10,5 @@ Development Lead
|
|||
Contributors
|
||||
------------
|
||||
|
||||
None yet. Why not be the first?
|
||||
* Alex (https://github.com/alexfromvl)
|
||||
|
||||
|
|
|
@ -6,6 +6,16 @@ History
|
|||
0.2.0 (In Development)
|
||||
++++++++++++++++++++++
|
||||
|
||||
* Added a template tag for easy 'Add To Basket' buttons
|
||||
* Added a template tag for shipping rates
|
||||
* Created a client side Javascript library for the REST API
|
||||
* We built basic views for Checkout and Basket
|
||||
* Added template tags to help simplify integration with payment backends
|
||||
* Basic checkout template in the project_template
|
||||
* Bug fixes around payment gateway integrations
|
||||
* Created a standard address form
|
||||
* Pushed test coverage past 80%
|
||||
|
||||
0.1.1 (2017-04-14)
|
||||
+++++++++++++++++++
|
||||
|
||||
|
|
|
@ -3,18 +3,18 @@
|
|||
Checkout
|
||||
========
|
||||
|
||||
Longclaw provides a simple, single checkout view.
|
||||
Longclaw provides a simple, single checkout view.
|
||||
The URL for the checkout is ``'checkout/'``.
|
||||
After a successful checkout, it is redirected to ``checkout/success/``.
|
||||
|
||||
To implement the checkout, simply provide ``'longclawcheckout/checkout.html'`` and
|
||||
``'longclawcheckout/success.html'`` templates. (Empty templates will have been created if
|
||||
``'longclawcheckout/success.html'`` templates. (Empty templates will have been created if
|
||||
you ran the longclaw CLI to start your project)
|
||||
|
||||
There are three forms provided in the checkout view:
|
||||
|
||||
:checkout_form:
|
||||
Captures the email address and optionally the shipping option for the checkout.
|
||||
Captures the email address and optionally the shipping option for the checkout.
|
||||
Also captures a boolean indicating whether a different billing address should be used
|
||||
|
||||
:shipping_form:
|
||||
|
@ -24,13 +24,13 @@ There are three forms provided in the checkout view:
|
|||
A second address form for capturing alternate billing information. If you do not submit this form
|
||||
(e.g. by not rendering it on the template), the billing and shipping addresses are assumed to be the same.
|
||||
|
||||
Generally, you may need to use a little javascript to optionally render the form if the user selects
|
||||
Generally, you may need to use a little javascript to optionally render the form if the user selects
|
||||
'different billing address'.
|
||||
|
||||
Shipping Options and Javascript
|
||||
--------------------------------
|
||||
|
||||
The shipping option dropdown has no options by default - this is because it is dependent on the shipping country.
|
||||
The shipping option dropdown has no options by default - this is because it is dependent on the shipping country.
|
||||
The checkout form includes the necessary javascript to do this - you just need to include it on the page.
|
||||
You will typically also need to include your chosen payment gateways' client javascript:
|
||||
|
||||
|
@ -46,7 +46,7 @@ You will typically also need to include your chosen payment gateways' client jav
|
|||
<script type="text/javascript">
|
||||
initShippingOption('{% longclaw_api_url_prefix %}');
|
||||
</script>
|
||||
|
||||
|
||||
The first half uses the ``gateway_client_js`` template tag to load all the payment gateway javascript. There may be one or more.
|
||||
The second half has three parts to it:
|
||||
|
||||
|
@ -68,8 +68,10 @@ It is up to you to render a payment form and then pass the token in the POST dat
|
|||
Normally, the payment gateway chosen will have a javascript integration to render a form for you
|
||||
and tokenize the payment method (e.g. braintrees 'hosted fields')
|
||||
|
||||
Longclaws' payment gateways provide some helpful utilities to load client javascript and generate tokens.
|
||||
Longclaws' payment gateways provide some helpful utilities to load client javascript and generate tokens.
|
||||
Loading ``longclawcheckout_tags`` in your template will allow you to retrieve the gateways' javascript libraries
|
||||
as script tags (``{% gateway_client_js %}`` and generate a client token (``{% gateway_token %}``).
|
||||
A little javascript is then required to setup your form and ask the gateway to tokenize the payment method for you.
|
||||
You should then add this token to the request POST data (e.g. with a hidden input field).
|
||||
A little javascript is then required to setup your form and ask the gateway to tokenize the payment method for you.
|
||||
You should then add this token to the request POST data (e.g. with a hidden input field).
|
||||
|
||||
For in-depth info on integration, see the walkthrough.
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
.. checkout-walkthrough:
|
||||
|
||||
|
||||
Checkout with Paypal Express
|
||||
Checkout with Braintree
|
||||
============================
|
||||
|
||||
Longclaw offers integration with a few payment gateways and it is also fairly easy to integrate your own.
|
||||
For this tutorial, we will use Paypal Express Checkout to process payments.
|
||||
For this tutorial, we will use Braintree to process payments.
|
||||
|
||||
Settings and Dependencies
|
||||
-------------------------
|
||||
|
@ -13,10 +13,17 @@ The payment gateway to use must be set in the settings file:
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
PAYMENT_GATEWAY = 'longclaw.longclawcheckout.gateways.braintree.PaypalVZeroPayment'
|
||||
PAYMENT_GATEWAY = 'longclaw.longclawcheckout.gateways.braintree.BraintreePayment'
|
||||
|
||||
The ``PaypalVZeroPayment`` class will allow us to take payments using Paypal Express Checkout and is dependent on the
|
||||
braintree SDK.
|
||||
|
||||
We also need to define settings for access tokens;
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
BRAINTREE_SANDBOX = False
|
||||
BRAINTREE_MERCHANT_ID = os.environ['BRAINTREE_MERCHANT_ID']
|
||||
BRAINTREE_PUBLIC_KEY = os.environ['BRAINTREE_PUBLIC_KEY']
|
||||
BRAINTREE_PRIVATE_KEY = os.environ['BRAINTREE_PRIVATE_KEY']
|
||||
|
||||
We will need to install this SDK as it is not an explicit dependency of longclaw::
|
||||
|
||||
|
@ -27,16 +34,182 @@ That is all we need to do to configure our backend!
|
|||
Front end integration
|
||||
---------------------
|
||||
|
||||
We will first show how to setup a checkout page using the Checkout view provided by longclaw.
|
||||
The code shown here is very similar to the implementation of the checkout page here: `Ramshackle Audio<https://github.com/JamesRamm/ramshacklerecording>`_
|
||||
|
||||
First, we should load some templatetags which will help us:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% load longclawcheckout_tags longclawcore_tags %}
|
||||
|
||||
As an aside - you may wish to display the items in the basket on our checkout page. The basket items queryset is available as ``basket``
|
||||
in the views' context.
|
||||
|
||||
Next, we need to setup the forms to gather customer information. There are 2 forms in the context. We will
|
||||
display and submit them as a single form. Here is an example layout:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
<form action="." method="post" id="checkout-form">
|
||||
{% csrf_token %}
|
||||
{% for field in shipping_form %}
|
||||
{% if field.is_hidden %}
|
||||
{{ field }}
|
||||
{% else %}
|
||||
{% if field.errors %}
|
||||
<div class="field error">
|
||||
{% else %}
|
||||
<div class="field">
|
||||
{% endif %}
|
||||
<label>{{ field.label_tag }}</label>
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<p class="help">{{ field.help_text|safe }}</p>
|
||||
{% endif %}
|
||||
<div class="ui error message">
|
||||
<p>{{ field.errors }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for field in checkout_form %}
|
||||
<!-- purposefully ignoring different billing address option to simplify -->
|
||||
{% if field.name == 'different_billing_address' %}
|
||||
{% else %}
|
||||
{% if field.errors %}
|
||||
<div class="field error">
|
||||
{% else %}
|
||||
<div class="field">
|
||||
{% endif %}
|
||||
<label>{{ field.label_tag }}</label>
|
||||
{{ field }}
|
||||
<div class="ui error message">
|
||||
{% for error in field.errors %}
|
||||
<p>{{ error }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
You may wish to layout the form differently. We have purposefully ignored the ``different_billing_address`` field
|
||||
since the Braintree dropin-ui will collect a billing postcode anyway, for its' own security checks.
|
||||
|
||||
Before we close our `<form>` element, there are 3 further items to add:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
<!-- hidden field for submitting the token back to the server. Name will vary depending on integration-->
|
||||
<input type="hidden" id="payment_method_nonce" name="payment_method_nonce"></input>
|
||||
<h4 class="ui dividing header">Payment Details</h4>
|
||||
<div id="dropin-container"></div>
|
||||
<input type="submit" id="submit-button" value="Place Order" class="ui button submit" />
|
||||
</form>
|
||||
|
||||
We add a hidden field. This field will contain a token (string of characters) given by braintree which represents the payment method.
|
||||
Most payment gateways require something like this, although the name of the field will change between backends.
|
||||
|
||||
We then add an empty div with the id ``dropin-container``. This will contain the Braintree Dropin UI.
|
||||
We could manually create the fields (using e.g. Hosted Fields for braintree or Elements for stripe) for payment forms, however
|
||||
most integrations offer some sort of 'dropin' which are increasingly customisable. For most purposes, this will suffice.
|
||||
|
||||
Finally, we add a submit button.
|
||||
|
||||
The Javascript
|
||||
***************
|
||||
|
||||
OK, so now we have hidden elements, empty containers....we need to get this stuff populated!
|
||||
Each payment gateway integration provides the necessary javascript libraries to interact with the gateway.
|
||||
They are made available via a template tag.
|
||||
Add them like this:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
<!--Load any client javascript provided by the payment gateway.
|
||||
I have chosen braintree as my gateway so the template tag below
|
||||
should give me a list of script tags which load the braintree
|
||||
SDK's
|
||||
-->
|
||||
{% gateway_client_js as scripts %}
|
||||
{% for js in scripts %}
|
||||
{{ js|safe }}
|
||||
{% endfor %}
|
||||
|
||||
<!--Finally add the media from the checkout form.-->
|
||||
{{ checkout_form.media }}
|
||||
|
||||
The checkout form also provides a little javascript to initialise shipping options (when the user selects a shipping country).
|
||||
|
||||
Finally, we need to add a little of our own javascript to create the braintree dropin:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
//Initialize shipping options - this function is from the
|
||||
//checkout form media.
|
||||
initShippingOption('{% longclaw_api_url_prefix %}');
|
||||
|
||||
// Initialize the braintree dropin.
|
||||
// The gateway token below is taken from the template tag provided by
|
||||
// longclaw. This is calculated depending on the chosen
|
||||
// PAYMENT_GATEWAY in the user settings.py
|
||||
var button = document.querySelector('#submit-button');
|
||||
|
||||
braintree.dropin.create({
|
||||
authorization: "{% gateway_token %}",
|
||||
container: '#dropin-container'
|
||||
}, function (createErr, instance) {
|
||||
button.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
if (instance){
|
||||
instance.requestPaymentMethod(function (err, payload) {
|
||||
// Submit payload.nonce to your server
|
||||
if (err) {
|
||||
// TODO: Handle this error
|
||||
console.log(err);
|
||||
}
|
||||
else {
|
||||
$('#payment_method_nonce').val(payload.nonce);
|
||||
document.getElementById("checkout-form").submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
Two things are happening in the above code. First, we initialise the shipping options. Note we are using a template tag
|
||||
to pass the longclaw API url prefix, since this is customisable in your settings.py
|
||||
|
||||
Secondly, we initialise the braintree dropin. Again, we use a template tag to get a token for the gateway.
|
||||
All payment backends provide the ``gateway_token`` template tag, although it is not always necessary.
|
||||
|
||||
You may wish to only show the braintree payment form if the user has anything in their basket. In which case you might qualify
|
||||
the above javascript with ``{% if basket.count > 0 %}`` in your template.
|
||||
|
||||
As you can see, setting up the checkout is one of the most involved aspects of creating your store. We have worked to simplify this
|
||||
for v0.2, but welcome any suggestions on how to make it easier!
|
||||
|
||||
If you wish to forego the templatetags & forms (e.g. if making a fully React-based frontend), read on. Otherwise, that is the end of the tutorial!
|
||||
|
||||
|
||||
Javascript-Only integration
|
||||
----------------------------
|
||||
|
||||
Below is a walkthrough of integrating a payment gateway (PayPal) without the aid of templatetags etc..
|
||||
|
||||
There is a fair amount of work to do to setup the front end when using any payment gateway. Paypal
|
||||
Express minimises this for us by taking charge of collecting and tokenizing payment data, although we
|
||||
must still configure it.
|
||||
must still configure it.
|
||||
|
||||
The basic client payment flow with Braintree is as follows:
|
||||
|
||||
1. The client requests a braintree token. Longclaw provides an API endpoint to generate tokens using the braintree SDK
|
||||
2. The client gathers payment details and turns this into a `payment method nonce` by interacting with the braintree server.
|
||||
Paypal Express Checkout will take care of this entirely.
|
||||
3. The client submits the `payment method nonce` to the server to capture the payment. Longclaw provides an API endpoint for all payment captures.
|
||||
3. The client submits the `payment method nonce` to the server to capture the payment. Longclaw provides an API endpoint for all payment captures.
|
||||
|
||||
We therefore have three things we need to do in our client-side javascript:
|
||||
|
||||
|
@ -51,9 +224,9 @@ We therefore have three things we need to do in our client-side javascript:
|
|||
}
|
||||
})
|
||||
|
||||
2. Following this, configure the paypal express checkout functionality. This actually has two steps.
|
||||
We must first create a braintree `client` using our new token. We then use this to create a braintree
|
||||
`paypal` instance.
|
||||
2. Following this, configure the paypal express checkout functionality. This actually has two steps.
|
||||
We must first create a braintree `client` using our new token. We then use this to create a braintree
|
||||
`paypal` instance.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
|
@ -70,20 +243,20 @@ We therefore have three things we need to do in our client-side javascript:
|
|||
if (err) {
|
||||
console.log("handle error creating paypal");
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.log("Paypal instance": paypalInstance);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
3. Once paypal has created the `nonce` for the entered payment details, we must submit this
|
||||
to our server so longclaw can capture the payment.
|
||||
3. Once paypal has created the `nonce` for the entered payment details, we must submit this
|
||||
to our server so longclaw can capture the payment.
|
||||
To do this, we must have a button which we want to use to launch the paypal express checkout window.
|
||||
We 'attach' the paypal instance we just created to the button like so:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
paypalButton.addEventListener(
|
||||
'click',
|
||||
'click',
|
||||
function (){
|
||||
paypalInstance.tokenize({
|
||||
flow: 'checkout',
|
||||
|
@ -161,7 +334,7 @@ We can make all these nested API calls simpler if we use ES6 Promises and the fe
|
|||
}
|
||||
|
||||
// This is where we actually setup paypal
|
||||
export function setupBraintreePaypal(totalAmount,
|
||||
export function setupBraintreePaypal(totalAmount,
|
||||
paypalButton,
|
||||
shippingAddress,
|
||||
shippingRate,
|
||||
|
@ -173,7 +346,7 @@ We can make all these nested API calls simpler if we use ES6 Promises and the fe
|
|||
return getToken()
|
||||
.then(data => braintreeClientCreate(data.token))
|
||||
.then(client => braintreePaypalCreate(client))
|
||||
.then(paypalInstance => paypalButton.addEventListener('click',
|
||||
.then(paypalInstance => paypalButton.addEventListener('click',
|
||||
function (){
|
||||
paypalInstance.tokenize({
|
||||
flow: 'checkout',
|
||||
|
@ -206,7 +379,7 @@ We can make all these nested API calls simpler if we use ES6 Promises and the fe
|
|||
let contentType = 'application/json';
|
||||
const headers = {
|
||||
Accept: 'application/json, application/json, application/coreapi+json',
|
||||
|
||||
|
||||
};
|
||||
if (!form) headers['Content-Type'] = contentType;
|
||||
const csrf = JsCookie.get('csrftoken');
|
||||
|
@ -239,4 +412,4 @@ We can make all these nested API calls simpler if we use ES6 Promises and the fe
|
|||
}
|
||||
|
||||
The total amount, shipping address, shipping rate and email address of the customer are passed into the setup function;
|
||||
it is up to the front end developer to create the necessary forms to gather these.
|
||||
it is up to the front end developer to create the necessary forms to gather these.
|
||||
|
|
|
@ -20,8 +20,7 @@ Install Longclaw into it:
|
|||
(my_project) $ pip install longclaw
|
||||
|
||||
We also need to install the client library for our payment gateway integration. We are going to
|
||||
use Paypal as our payment gateway in this walkthrough. To make things easy, we will use Paypal
|
||||
Express Checkout. For this we can use the Braintree SDK:
|
||||
use Braintree as our payment gateway in this walkthrough.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -48,18 +47,25 @@ Now we have a django project which looks like this::
|
|||
requirements.txt
|
||||
|
||||
The ``home`` and ``search`` folders are default folders used in Wagtail projects. Users of Wagtail
|
||||
will be familiar with these.
|
||||
The ``products`` folder contains a skeleton model for our product `variants` which we will come to later.
|
||||
will be familiar with these.
|
||||
The ``products`` folder contains a skeleton model for our product `variants` which we will come to later.
|
||||
|
||||
Before proceeding, we need to setup our ``settings`` file, in ``my_shop/settings/base.py``.
|
||||
|
||||
We need to configure which payment gateway we are using. Change the entry for ``PAYMENT_GATEWAY`` from
|
||||
``'longclaw.longclawcheckout.gateways.BasePayment'`` to ``'longclaw.longclawcheckout.gateways.PaypalVZero'``
|
||||
``'longclaw.longclawcheckout.gateways.BasePayment'`` to ``'longclaw.longclawcheckout.gateways.braintree.BraintreePayment'``
|
||||
|
||||
We also need to set the access token. The setting for this is ``VZERO_ACCESS_TOKEN``. Paypal access tokens
|
||||
are termed something like ``access_token$sandbox`` followed by a sequence of characters. As we have different
|
||||
access tokens for sandbox and live accounts, we will set ``VZERO_ACCESS_TOKEN`` in ``my_shop/settings/dev.py``
|
||||
for the sandbox account and ``my_shop/settings/production.py`` for the live account.
|
||||
We also need to set the access tokens for the braintree backend. Add the following settings:
|
||||
|
||||
.. codeblock:: python
|
||||
|
||||
BRAINTREE_SANDBOX = False
|
||||
BRAINTREE_MERCHANT_ID = os.environ['BRAINTREE_MERCHANT_ID']
|
||||
BRAINTREE_PUBLIC_KEY = os.environ['BRAINTREE_PUBLIC_KEY']
|
||||
BRAINTREE_PRIVATE_KEY = os.environ['BRAINTREE_PRIVATE_KEY']
|
||||
|
||||
For development/testing, you will probably want to set ``BRAINTREE_SANDBOX`` to ``True``. The above settings assume that
|
||||
you have set environment variables on your OS with the access tokens.
|
||||
|
||||
.. note: Don't forget that Longclaw is a Wagtail project. You may need to configure additional settings
|
||||
for wagtail.
|
||||
|
|
|
@ -6,7 +6,7 @@ Managing the Catalogue
|
|||
Creating the Product Index
|
||||
--------------------------
|
||||
Wagtails' ``Page`` models are organized in a tree structure. All our ``Product`` pages will therefore
|
||||
need a parent. This is provided by the ``ProductIndex`` model.
|
||||
need a parent. This is provided by the ``ProductIndex`` model.
|
||||
|
||||
.. note::
|
||||
Read more about Wagtail pages in the `Wagtail docs <http://docs.wagtail.io/en/v1.9/topics/pages.html>`_
|
||||
|
@ -25,7 +25,7 @@ We can now add ``Product`` models as children of ``ProductIndex``. Only pages of
|
|||
Adding a Product
|
||||
----------------
|
||||
|
||||
Under the explorer homepage, we should now see our newly created ``ProductIndex``. We can select ``Add child page`` to add our first
|
||||
Under the explorer homepage, we should now see our newly created ``ProductIndex``. We can select ``Add child page`` to add our first
|
||||
``Product``. The ``Product`` model is fairly minimal. It has:
|
||||
|
||||
- A title
|
||||
|
@ -40,10 +40,10 @@ Customising Variants
|
|||
--------------------
|
||||
|
||||
The ``ProductVariant`` model is where we can customise the attributes of our model. Running ``longclaw start``
|
||||
provided a ``products`` with a minimal implementation of a custom ``ProductVariant`` model.
|
||||
provided a ``products`` with a minimal implementation of a custom ``ProductVariant`` model.
|
||||
We can further customise this now by opening ``my_shop/products/models.py`` in a text editor.
|
||||
|
||||
``ProductVariant`` inherits from ``ProductVariantBase`` which provides the ``price``, ``ref`` and ``slug`` fields.
|
||||
``ProductVariant`` inherits from ``ProductVariantBase`` which provides the ``price``, ``ref`` and ``slug`` fields.
|
||||
|
||||
The ``ref`` field is intended to be used as a short description or sub-title to help distinguish a particular variant.
|
||||
The ``slug`` field is autogenerated from the ``ref`` and the parent ``Product`` title.
|
||||
|
@ -51,7 +51,7 @@ The ``slug`` field is autogenerated from the ``ref`` and the parent ``Product``
|
|||
As we are creating a music shop, we are going to add a ``music_format`` field to the model. We will also
|
||||
remove the ``description`` field as we dont have any real need for it at the moment:
|
||||
|
||||
.. code:: python
|
||||
.. code-block:: python
|
||||
|
||||
class ProductVariant(ProductVariantBase):
|
||||
_MUSIC_FORMAT_CHOICES = (
|
||||
|
@ -59,7 +59,7 @@ remove the ``description`` field as we dont have any real need for it at the mom
|
|||
(2, 'Vinyl'),
|
||||
)
|
||||
|
||||
music_format = models.IntegerField(max_length=3, choices=_MUSIC_FORMAT_CHOICES)
|
||||
music_format = models.IntegerField(max_length=3, choices=_MUSIC_FORMAT_CHOICES)
|
||||
|
||||
After making and running migrations, we can now select the format for each variant:
|
||||
|
||||
|
@ -84,12 +84,25 @@ For a more complete template, take a look at the `demo project <https://github.c
|
|||
Adding Products to the Basket
|
||||
-----------------------------
|
||||
|
||||
An important detail of the product template is providing the ability to add or remove a product to the basket.
|
||||
This is done by making AJAX calls to the longclaw API.
|
||||
Longclaw offers a helpful template tag to create an ``Add To Basket`` button for your products.
|
||||
In your template, load the longclawbasket tags::
|
||||
|
||||
In the product template, we would like to provide a means to select a variant and add it to the basket.
|
||||
For t-shirts, our variants are going to represent different sizes, so we would like a single ``Add`` button
|
||||
and a drop down of sizes.
|
||||
.. code-block:: django
|
||||
|
||||
{% load longclawbasket_tags %}
|
||||
|
||||
You can now use the tag to render a button for each product variant:
|
||||
|
||||
.. code-block:: django
|
||||
|
||||
{% add_to_basket_btn variant.id btn_text="Add To Basket" btn_class="btn btn-default" %}
|
||||
|
||||
|
||||
If you wish to create a button manually, you can handle the click event by making an AJAX call to the longclaw API.
|
||||
Situations where you would prefer this over the tempaltetag might be to support non-button elements, such as
|
||||
dropdown buttons, or for React-based frontends.
|
||||
|
||||
Here is an example with a single button whose 'variant id' will change depending on the selection in a dropdown box.
|
||||
We can acheive the drop down like this:
|
||||
|
||||
.. code-block:: django
|
||||
|
@ -127,4 +140,5 @@ We can then write a jquery function to handle the click event:
|
|||
|
||||
This is a basic example of integrating with the basket. You will likely need to incorporate more
|
||||
complex designs such as displaying a count of items in the basket, allowing the user to increase/decrease
|
||||
quantity and so on. The :ref:`basket API <basket>` allows all such interactions and all front end design decisions such as these are left up to the developer
|
||||
quantity and so on. The :ref:`basket API <basket>` allows all such interactions and all front end design decisions such as these are left up to the developer.
|
||||
It is worthwhile looking at the longclaw demo source code to see how e.g. a basket & item count in the page header is implemented.
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{% load longclawcore_tags %}
|
||||
<button id="btn-add-to-basket" class="{{btn_class}}" data-variant-id="{{variant_id}}">
|
||||
{{btn_text}}
|
||||
</button>
|
||||
{% longclaw_vendors_bundle %}
|
||||
{% longclaw_client_bundle %}
|
||||
<script type="text/javascript">
|
||||
var btn = document.getElementById('btn-add-to-basket');
|
||||
btn.addEventListener("click", function (e) {
|
||||
longclawclient.basketList.post({
|
||||
prefix: "{% longclaw_api_url_prefix %}",
|
||||
data: {
|
||||
variant_id: e.target.dataset.variantId
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
|
@ -10,3 +10,14 @@ def basket(context):
|
|||
'''
|
||||
items, _ = get_basket_items(context["request"])
|
||||
return items
|
||||
|
||||
|
||||
@register.inclusion_tag('longclawbasket/add_to_basket.html')
|
||||
def add_to_basket_btn(variant_id, btn_class="btn btn-default", btn_text="Add To Basket"):
|
||||
'''Button to add an item to the basket
|
||||
'''
|
||||
return {
|
||||
'btn_class': btn_class,
|
||||
'variant_id': variant_id,
|
||||
'btn_text': btn_text
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.core.urlresolvers import reverse
|
|||
|
||||
from longclaw.tests.utils import LongclawTestCase, BasketItemFactory, ProductVariantFactory
|
||||
from longclaw.longclawbasket.utils import basket_id
|
||||
from longclaw.longclawbasket.templatetags import longclawbasket_tags
|
||||
|
||||
|
||||
class BasketTest(LongclawTestCase):
|
||||
|
@ -51,6 +52,13 @@ class BasketTest(LongclawTestCase):
|
|||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
|
||||
def test_add_to_cart_btn(self):
|
||||
'''Test the add to cart tag responds
|
||||
'''
|
||||
result = longclawbasket_tags.add_to_basket_btn(1)
|
||||
self.assertIsNotNone(result)
|
||||
|
||||
|
||||
class BasketModelTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
|
|
@ -1,3 +1,27 @@
|
|||
import os
|
||||
from django.test import TestCase
|
||||
from django.contrib.staticfiles import finders
|
||||
|
||||
# Create your tests here.
|
||||
from longclaw import settings
|
||||
from longclaw.longclawcore.templatetags import longclawcore_tags
|
||||
|
||||
class TagTests(TestCase):
|
||||
|
||||
def _test_static_file(self, pth):
|
||||
result = finders.find(pth)
|
||||
print(result)
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_vendors_bundle(self):
|
||||
ctx = longclawcore_tags.longclaw_vendors_bundle()
|
||||
self._test_static_file(ctx['path'])
|
||||
|
||||
def test_client_bundle(self):
|
||||
ctx = longclawcore_tags.longclaw_client_bundle()
|
||||
self._test_static_file(ctx['path'])
|
||||
|
||||
def test_api_url_prefix(self):
|
||||
self.assertEqual(
|
||||
settings.API_URL_PREFIX,
|
||||
longclawcore_tags.longclaw_api_url_prefix()
|
||||
)
|
||||
|
|
|
@ -45,6 +45,7 @@ INSTALLED_APPS = [
|
|||
'rest_framework',
|
||||
'django_extensions',
|
||||
|
||||
'longclaw.longclawcore',
|
||||
'longclaw.longclawsettings',
|
||||
'longclaw.longclawshipping',
|
||||
'longclaw.longclawproducts',
|
||||
|
|
13
tox.ini
13
tox.ini
|
@ -1,20 +1,23 @@
|
|||
[tox]
|
||||
envlist =
|
||||
{py27,py33,py34,py35}-django-18
|
||||
{py27,py34,py35}-django-19
|
||||
{py27,py34,py35}-django-110
|
||||
{py27,py33,py34,py35,py36}-django-18
|
||||
{py27,py34,py35,py36}-django-19
|
||||
{py27,py34,py35,py36}-django-110
|
||||
{py27,py34,py35,py36}-django-111
|
||||
|
||||
[testenv]
|
||||
setenv =
|
||||
PYTHONPATH = {toxinidir}:{toxinidir}/longclaw
|
||||
commands = coverage run --source longclaw runtests.py
|
||||
coverage xml --omit=*/apps.py,*/migrations/*,*/__init__.py,*/gateways/braintree.py,*/gateways/stripe.py
|
||||
coverage xml --omit=*/apps.py,*/migrations/*,*/__init__.py,*/gateways/braintree.py,*/gateways/stripe.py,*/bin/longclaw.py
|
||||
deps =
|
||||
django-18: Django>=1.8,<1.9
|
||||
django-19: Django>=1.9,<1.10
|
||||
django-110: Django>=1.10
|
||||
django-110: Django>=1.10,<1.11
|
||||
django-111: Django>=1.11
|
||||
-r{toxinidir}/requirements_test.txt
|
||||
basepython =
|
||||
py36: python3.6
|
||||
py35: python3.5
|
||||
py34: python3.4
|
||||
py33: python3.3
|
||||
|
|
Ładowanie…
Reference in New Issue