longclawshop / longclaw Goto Github PK
View Code? Open in Web Editor NEWA shop for Wagtail CMS
License: MIT License
A shop for Wagtail CMS
License: MIT License
On my own site (at http://104.236.209.115/) I have functionality to allow customers to request/register interest in products that are out of stock.
It is easily enabled by adding a request_count
field to the ProductVariant
and adding an api endpoint to augment it.
If there is interest this could be incorporated into longclaw. I would imagine it actually being a separate model so we can record the date of each request (and potentially other fields - user or ip etc..)
Product review model & API.
Something along the lines of:
class ProductReview(models.Model):
product = models.ForeignKey('longclawproducts.Product')
rating = models.IntegerField(max=5)
comments = models.CharField(max_length=128)
Couple of question to answer:
Product
model or ProductVariant
model?when running longclaw start
you should be able to specify the payment gateway to use (base
, braintree
, stripe
, paypal_vzero
)
Provide a general 'basket' view which will provide the current basket items in the context
Document the checkout API.
Express payments (using javascript to create payments outside of longclaw (i.e. paypal express) then passing the transaction ID to create an order)
Endpoint is the same regardless of gateway
A payment is authorized & captured in one step
an Order
containing OrderItem
's is created - for both successful and unsuccessful payments (in the case of unsuccessful, the Order
status is set to CANCELLED
with a status_note
stating payment failed
After successful payment the basket is destroyed (since the Order
now carries all relevant info).
create_token
endpoint
Add to default SHIPPING ADDRESS - Goods shall be shipped to the Buyer on Ex–warehouse delivery terms.
The problem is that the shipping address field is mandatory. If there is not a single field, then it's impossible to create an order.
Adding an address will solve the problem of creating test orders.
This issue is just to decide upon and iron out final details for a 0.1 release.
Ideally I will have the demo site fully functional before releasing, though I am hoping to be not more than 1 week away.
A few things that need ironing out:
Would be great if there is anyone out there willing to have a look through the docs and raise issues against anything missing/nonsensical
Docs need updating to reflect that longclaw can be installed from pypi (and the assets wont need building).
Build and upload to pypi (I'll do this)
Tag the master branch with the 0.1 release
Any other requirements for a 0.1 release?
A dummy payment backend that requires no account with stripe/braintree/paypal would be useful for testing.
Idea here is to use http://docs.wagtail.io/en/v1.8.1/reference/hooks.html#construct-homepage-panels to add useful info about longclaw to the wagtail admin homepage.
Such info could be:
Dependencies need properly documenting, as mentioned in #4.
Streamlining the process of setting up longclaw also needs to be addressed.
There are a few things to consider:
requirements.txt vs Pipfile vs setup.py. Longclaw is currently setup with requirements.txt, although this doesnt automatically get run when using setup.py
. Wagtail itself seems to prefer listing dependencies entirely in in setup.py
. Using a 'Pipfile', particularly with 'pipenv' is a growing, moden approach and I would certainly be in favour of exploring it.
Optional dependencies. Some libraries like stripe, braintree are required only if chosen as the payment backend. Need to properly document this
Get travis actually installing/running something to verify that the setup works & all dependencies are pulled in.
Consider providing a CLI to setup a project like in wagtail. Essentially this would run the wagtail setup and plonk longclaw into it (INSTALLED_APPS
and urls.py
). We can also setup a products
app since I imagine 90% of users will want to customise the very basic ProductVariant
model. @alexfromvl has already provided example templates in the project_template
directory which can be expanded to include this.
We could consider exporting all the app names to a single LONGCLAW_APPS
variable, so a user can do INSTALLED_APPS += LONGCLAW_APPS
...this I believe is what django-oscar does. It may help since many of the apps are interdependent.
Consider doing the same thing for urls - perhaps a longclaw.urls
file which gathers the urls from individual apps
The checkout and basket apps are currently written as a REST api, the primary motivation being that it allows total customisation of how you integrate it into your website (I was thinking react apps and nice popup modals for easy checkouts).
It would also be useful to provide generic views and templates for separate-page checkouts/basket views.
Paypal (And likely other providers) often have an 'express checkout' service, where all details of payment are handled by paypal and the payment is submitted via javascript.
It would be very useful to support this as an easy way of getting set up.
Since paypal handles the payment, this will require an api endpoint which will accept the completed transaction id and create the order (i.e. capture_payment
without any payment handling!)
This endpoint should also accept the shipping address ID as this will have to be created separately.
Exposing a graphql API (using graphene) would be useful and make for a richer experience when making react-based apps.
I think it should be optional (enabled in the settings) so that extra dependencies are are only required if it is enabled.
Django rest framework is specified wrong in the INSTALLED_APPS
section of the project template settings, causing an import error.
In my own project, I have created a small JS file to call all the longclaw API endpoints:
/**
* Helper functions to be used when making requests to the api.
*/
import JsCookie from 'js-cookie';
import fetch from 'isomorphic-fetch';
/**
* Check the response status and raise an error if it's no good.
* @param {object} response - the http response object as provided by fetch
* @returns {object} - the http rsponse object or throws an error
*/
function checkStatus(response) {
if (response.ok) {
return response;
}
return response.json().then(json => {
const error = new Error(response.statusText)
throw Object.assign(error, { response, json })
})
}
/**
* Return an object given an http json response
* @param {object} response - json encoded response object as provided by fetch
* @returns {object} - The parsed json
*/
function parseJSON(response) {
return response.json();
}
/**
* Return the headers needed for put, post and delete requests
* @returns {{Accept: string, Content-Type: string, X-CSRFToken: *}}
*/
function getRequestHeaders(form = false) {
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');
if (csrf) headers['X-CSRFToken'] = csrf;
return headers;
}
function get(url) {
return fetch(
url,
{
method: 'GET',
headers: getRequestHeaders(),
credentials: 'include' }
)
.then(checkStatus)
.then(parseJSON);
}
function post(url, data, isForm = false) {
return fetch(
url,
{
method: 'POST',
headers: getRequestHeaders(isForm),
credentials: 'include',
body: isForm ? data : JSON.stringify(data)
}
)
.then(checkStatus)
.then(parseJSON);
}
function del(url, data, isForm = false) {
return fetch(
url,
{
method: 'DELETE',
headers: getRequestHeaders(isForm),
credentials: 'include',
body: isForm ? data : JSON.stringify(data)
}
)
.then(checkStatus)
.then(parseJSON);
}
// ===================================================== //
// Public functions //
export function getShipping(country_code, shipping_option='standard'){
return get(`/api/shipping/cost/?country_code=${country_code}&shipping_option=${shipping_option}`, {country_code, shipping_option})
}
export function getShippingCountries() {
return get('/api/shipping/countries/');
}
/*
* data:
* data.amount: Amount (in GBP) to be paid
* data.shipping: Amount (in GBP) of shipping cost
* data.address.shipping: Object containing shipping address
* data.address.billing: Object containing billing address
* data.ip: IP address of client
* data.email: Email address of client
* data.payment_method_nonce: Stripe token of client card
*
*/
export function checkout(data){
return post('/api/checkout/', data)
}
export function getToken(){
return get('/api/checkout/token/');
}
export function getBasket() {
return get("/api/basket/");
}
export function addToBasket(variant_id){
return post('/api/basket/', {variant_id});
}
export function removeFromBasket(variant_id, quantity){
return del(`/api/basket/${variant_id}/`, {quantity});
}
export function getTotalItems(){
return get('/api/basket/count/');
}
export function requestProduct(variant_id){
return post(`/api/basket/${variant_id}/request/`, {})
}
this makes it much easier to interact with longclaw throughout the javascript code and/or create e.g. react front ends.
I.e fetching all the basket items becomes fetchBasket().then(response => console.log("Handle the returned data!"))
Could be useful to have this as a lib in the longclaw source code and either:
URL names (for resolving with reverse
) should be prefixed with longclaw
to make them unique. There are other things that can be done with URL namespacing...but this is simple and consistent.
I.e. A few names currently in use are basket
, checkout
and orders
. These would be renamed to longclaw-basket
, longclaw-checkout
and longclaw-orders
travis.yml
is just the default file created by cookiecutter atm. It needs fixing to get the travis build working.
The payment_date
field on an Order
is automatically filled whenever the order is created. This is misleading since Order
objects are created for failed payments.
We should:
payment_date
to not be auto filled & only fill it when payment successfully goes through.creation_date
field which is auto filled.A ShippingCountry
is currently a Page
; it should just be a regular model since it is not strictly 'content', more of a setting. The admin interface can be a little confusing & crowded with multiple shipping countries.
What do you think about having an IRC Channel for communication amongst developers. There has been a few things i wanted to ask @JamesRamm, but don't feel its worth filing an issue. Doesn't have to be IRC of course, Slack, Mattermost or email but im sure wed all rather not have any more email coming in.
Orders are viewable in the wagtail admin, but it is not particularly useful atm.
We could customise the admin template to allow:
pip install longclaw
will end up installing django 1.11 (if django is not already installed). Wagtail doesnt support this yet and will end up throwing an error.
Need to specify Django>=1.8,<1.11
in setup.py
Add some example images to the readme
Setup email system to allow:
Tests are needed for the checkout api.
Specifically:
create_order
in utils
create_order_with_token
api functioncapture_payment
api function, using the BasePayment
gateway initially (and ideally with some 'real' gateway)Useful forms/widgets for the checkout process:
A 'country' field which only allows selection of countries which have shipping rates, or all countries if DEFAULT_SHIPPING_ENABLED
is True
A general address form for shipping
a general payment form
In creating the longclaw-demo site, one of the most fiddly parts with working with longclaw is the whole basket/checkout process.
I think a priority for 0.2.0 is making this simpler and minimising the amount of javascript a user needs to write to get it going (while still leaving the door fully open for custom front ends (react etc)).
Create a view for the checkout. #2 and #50 already deal with some elements which will be useful for this view.
The view should provide the basket contents (like the basket view) and address form
Issue has been renamed & moved to discuss building the front end assets
Docs and hosting on readthedocs
due to changes to use django_countries, shipping_cost
endpoint now returns:
django.core.exceptions.FieldError: Cannot resolve keyword 'country' into field. Choices are: carrier, countries, description, id, name, rate
Developing longclaw requires setting a django project wagtail to run longclaw within...I wonder if we can make it simpler to setup for development by providing a dev project in the repo somehow?
Are there any django app development guidelines on this?
Build is currently failing against Django 1.10 due to an issue in wagtail: wagtail/wagtail#3526
Options:
I think the Address
model should be a wagtail snippet.
This will allow manual entry of addresses from the wagtail admin (thereby providing a solution to #42 while still allow future expansion into Customer models). The order model would be changed to have a SnippetChooserPanel
for the billing & shipping address
We can then also make addresses searchable, we could be useful when there is a large number of customers.
This is a small but useful change, so I think it could be snuck in for 0.1
Attempting to hack on the basket templates and api but seem to be unable to reach the api urls.
I have tried sending POST and GET Requests to the few api commands that i felt i could use without having items in a basket yet. Im getting 404 errors.
from what i can tell the api url is just /api/
so the format of the url i am sending data to is localhost:8000/api/add_to_basket/
I have attached some of the command history from httpie tool
http://localhost:8000/products/> cd pencil/
http://localhost:8000/products/pencil/> get
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Date: Wed, 29 Mar 2017 10:34:40 GMT
Server: WSGIServer/0.2 CPython/3.6.0
Set-Cookie: csrftoken=ong9dNCJnKzs4X9DwkFDW36jRU5dyGbs45pd6rrCndOBO7qmk4UvPk9xGB2PV8W1; expires=Wed, 28-Mar-2018 10:34:40 GMT; Max-Age=31449600; Path=/
Vary: Cookie
X-Frame-Options: SAMEORIGIN
AttributeError: 'unicode' object has no attribute 'items'
Parse tree:
<Node called "action" matching "get"> <-- *** We were here. ***
<RegexNode called "_" matching "">
<Node called "method" matching "get">
<RegexNode matching "get">
<RegexNode called "_" matching "">
<Node matching "">
<Node matching "">
<Node matching "">
<RegexNode called "_" matching "">
http://localhost:8000/products/pencil/> cd /api/add_to_basket/
http://localhost:8000/api/add_to_basket/> X-CSRFToken:ong9dNCJnKzs4X9DwkFDW36jRU5dyGbs45pd6rrCndOBO7qmk4UvPk9xGB2PV8
W1
http://localhost:8000/api/add_to_basket/> post
HTTP/1.0 404 Not Found
Content-Type: text/html
Date: Wed, 29 Mar 2017 10:35:24 GMT
Server: WSGIServer/0.2 CPython/3.6.0
Vary: Cookie
X-Frame-Options: SAMEORIGIN
http://localhost:8000/api/add_to_basket/>
When you try to display a product page, the price appears (Decimal('100.00'), Decimal('100.00')).
I address the price in the page template via variable {{ page.price_range }}.
What could be the problem? How to output only numbers?
Improve the current longclaw.checkout.models.ShippingCountry
to allow more customisation of shipping rates
The project to add templates for products and other models.
Given that the products.models is now un fixed, it would be useful to create a template for her demo.
class ProductIndex(Page):
pass
def get_context(self, request):
# Update context to include only published products, ordered by reverse-chron
context = super(ProductIndex, self).get_context(request)
product = self.get_children().live().order_by('-first_published_at')
context['product'] = product
return context
My example template:
Product_index.html
{% extends "base.html" %}
{% load wagtailcore_tags wagtailimages_tags %}
{% block body_class %}template-productindex{% endblock %}
{% block content %}
<h1>{{ page.title }}</h1>
<div class="row">
{% for post in product %}
{% with post=post.specific %}
<div><a href="{% pageurl post %}">
<div class="caption_news">
<h4>{{ post.title }}</h4>
<p>{{ post.description|richtext }}</p>
</div></a>
</div>
{% endwith %}
{% endfor %}
</div>
{% endblock %}
Some walkthrough documentation would be nice. It should cover:
product_index.html
and product.html
Basket API urls are currently:
add_to_basket/
remove_from_basket/
get_basket/
basket_total_items/
get_item_count/
These could be better expressed as:
basket/
- Accepts GET and POST requests (as replacement for get_basket
and add_to_basket
)
basket/count/
- Accepts GET (basket_total_items
)
basket/(?P<variant_id>[0-9]+)/
- Accepts GET and DEL requests (remove_from_basket
)
basket/(?P<variant_id>[0-9]+)/count/
- Accepts GET (get_item_count
)
Longclaw 0.1.0 will offer easy customisation of the product variants, but there is nothing to offer customisation of the base product itself.
There may be situations where we need extra fields which should be the same for all variants (e.g. author
or artist
fields).
A simple and easy (to the user) way to customise the base product model is required.
I see this being a 0.2.0 release (or later) since such customisation can be achieved through the product variant; it just that it requires unnecessary duplication atm.
Separating this from #28
A standard address form (using ModelForm
) to aid with capturing addresses.
The country field should be limited to countries which are shipped to (which is dependent upon the DEFAULT_SHIPPING
setting).
This may also help with discussion in #42 (although this is a front end component)
All payment gateways create some sort of transaction ID related to the payment (on stripe it is the 'Charge ID', on braintree it is the Transaction ID
, although the 'Transaction order_id' may be more useful for paypal payments made with braintree...)
This ID should be stored on the Order
object so the user can cross reference against their payment account.
Easiest way of supporting this is to simply require the payment gateway interface to return the id in the create_payment
method.
Returning some help text specific to the backend would be useful - i.e. what the ID refers to and where it can be found when the user logs into stripe/paypal etc...
Following on from #1, backend & front end functionality needed to provide refunds.
Documentation needed for basket and checkout API.
get_basket
, add_to_basket
, remove_from_basket
, get_item_count
, total_items
)Longclaw settings are currently split between various app_settings
files in each app, making it hard to document/see all the settings. They could be moved to a single settings.py
file in the root longclaw
directory.
Payments are currently handled in the checkout
app. They are:
Order
instance is created upon successful payment which contains all the order related details (essentially, what products and shipping address)We could potentially use django-payments as a more mature backend rather than continuing to develop our own, however, this requires a separate db model which most likely duplicates info in the order model and/or stores unnecessary info. It also has fairly low levels of activity but 20 odd outstanding issues...
Write documentation to describe:
Configuring payment backends
Developing new backends
BasePayment
classcreate_payment
)Idea is to provide a CLI, much the same as wagtails' wagtail start my_project
but using the longclaw template.
Running django-admin startproject --template ...
can be a bother when you need to figure out where the longclaw directory is (especially when using virtual environments)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.