├── home
├── __init__.py
├── migrations
│ ├── __init__.py
│ └── 0001_initial.py
├── models.py
└── templates
│ └── home
│ └── home_page.html
├── search
├── __init__.py
├── views.py
└── templates
│ └── search
│ └── search.html
├── catalog
├── __init__.py
├── migrations
│ ├── __init__.py
│ ├── 0002_auto_20190105_1655.py
│ ├── 0003_auto_20190105_1725.py
│ ├── 0005_auto_20190116_0610.py
│ ├── 0004_auto_20190116_0609.py
│ └── 0001_initial.py
├── templates
│ └── catalog
│ │ ├── product_index.html
│ │ └── product.html
└── models.py
├── longclaw_bakery
├── __init__.py
├── settings
│ ├── __init__.py
│ ├── production.py
│ ├── dev.py
│ └── base.py
├── templates
│ ├── checkout
│ │ ├── success.html
│ │ └── checkout.html
│ ├── 404.html
│ ├── 500.html
│ ├── base.html
│ └── basket
│ │ └── basket.html
├── wsgi.py
└── urls.py
├── heroku.yml
├── requirements.txt
├── media
├── original_images
│ ├── flute.jpeg
│ └── baguette.jpg
└── images
│ ├── baguette.original.jpg
│ ├── flute.max-1000x900.jpg
│ ├── flute.max-165x165.jpg
│ ├── flute.max-300x300.jpg
│ ├── flute.max-300x400.jpg
│ ├── flute.max-320x320.jpg
│ ├── flute.max-400x400.jpg
│ ├── flute.max-500x320.jpg
│ ├── baguette.max-1000x600.jpg
│ ├── baguette.max-1000x800.jpg
│ ├── baguette.max-1000x900.jpg
│ ├── baguette.max-165x165.jpg
│ ├── baguette.max-300x300.jpg
│ ├── baguette.max-300x400.jpg
│ ├── baguette.max-320x320.jpg
│ ├── baguette.max-400x400.jpg
│ ├── baguette.max-500x320.jpg
│ ├── baguette.max-800x600.jpg
│ ├── baguette.2e16d0ba.fill-320x240.jpg
│ ├── baguette.2e16d0ba.fill-500x400.jpg
│ └── baguette.2e16d0ba.fill-800x600.jpg
├── manage.py
├── Dockerfile
├── README.md
├── .gitignore
└── LICENSE
/home/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/search/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/catalog/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/home/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/longclaw_bakery/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/catalog/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/longclaw_bakery/settings/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/heroku.yml:
--------------------------------------------------------------------------------
1 | build:
2 | docker:
3 | web: Dockerfile
--------------------------------------------------------------------------------
/longclaw_bakery/templates/checkout/success.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | longclaw>=1.0.2
2 | Django>=2.1.4
3 | wagtail>=2.4
4 | stripe>=2.17.0
--------------------------------------------------------------------------------
/home/models.py:
--------------------------------------------------------------------------------
1 | from wagtail.core.models import Page
2 |
3 |
4 | class HomePage(Page):
5 | pass
6 |
--------------------------------------------------------------------------------
/media/original_images/flute.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/original_images/flute.jpeg
--------------------------------------------------------------------------------
/media/images/baguette.original.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/baguette.original.jpg
--------------------------------------------------------------------------------
/media/images/flute.max-1000x900.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/flute.max-1000x900.jpg
--------------------------------------------------------------------------------
/media/images/flute.max-165x165.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/flute.max-165x165.jpg
--------------------------------------------------------------------------------
/media/images/flute.max-300x300.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/flute.max-300x300.jpg
--------------------------------------------------------------------------------
/media/images/flute.max-300x400.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/flute.max-300x400.jpg
--------------------------------------------------------------------------------
/media/images/flute.max-320x320.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/flute.max-320x320.jpg
--------------------------------------------------------------------------------
/media/images/flute.max-400x400.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/flute.max-400x400.jpg
--------------------------------------------------------------------------------
/media/images/flute.max-500x320.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/flute.max-500x320.jpg
--------------------------------------------------------------------------------
/media/original_images/baguette.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/original_images/baguette.jpg
--------------------------------------------------------------------------------
/media/images/baguette.max-1000x600.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/baguette.max-1000x600.jpg
--------------------------------------------------------------------------------
/media/images/baguette.max-1000x800.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/baguette.max-1000x800.jpg
--------------------------------------------------------------------------------
/media/images/baguette.max-1000x900.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/baguette.max-1000x900.jpg
--------------------------------------------------------------------------------
/media/images/baguette.max-165x165.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/baguette.max-165x165.jpg
--------------------------------------------------------------------------------
/media/images/baguette.max-300x300.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/baguette.max-300x300.jpg
--------------------------------------------------------------------------------
/media/images/baguette.max-300x400.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/baguette.max-300x400.jpg
--------------------------------------------------------------------------------
/media/images/baguette.max-320x320.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/baguette.max-320x320.jpg
--------------------------------------------------------------------------------
/media/images/baguette.max-400x400.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/baguette.max-400x400.jpg
--------------------------------------------------------------------------------
/media/images/baguette.max-500x320.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/baguette.max-500x320.jpg
--------------------------------------------------------------------------------
/media/images/baguette.max-800x600.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/baguette.max-800x600.jpg
--------------------------------------------------------------------------------
/media/images/baguette.2e16d0ba.fill-320x240.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/baguette.2e16d0ba.fill-320x240.jpg
--------------------------------------------------------------------------------
/media/images/baguette.2e16d0ba.fill-500x400.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/baguette.2e16d0ba.fill-500x400.jpg
--------------------------------------------------------------------------------
/media/images/baguette.2e16d0ba.fill-800x600.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/longclawshop/longclaw_demo/HEAD/media/images/baguette.2e16d0ba.fill-800x600.jpg
--------------------------------------------------------------------------------
/longclaw_bakery/settings/production.py:
--------------------------------------------------------------------------------
1 | from .base import *
2 |
3 | DEBUG = False
4 |
5 | try:
6 | from .local import *
7 | except ImportError:
8 | pass
9 |
--------------------------------------------------------------------------------
/longclaw_bakery/templates/404.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block body_class %}template-404{% endblock %}
4 |
5 | {% block content %}
6 |
Page not found
7 |
8 | Sorry, this page could not be found.
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 | import sys
5 |
6 | if __name__ == "__main__":
7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "longclaw_bakery.settings.dev")
8 |
9 | from django.core.management import execute_from_command_line
10 |
11 | execute_from_command_line(sys.argv)
12 |
--------------------------------------------------------------------------------
/longclaw_bakery/settings/dev.py:
--------------------------------------------------------------------------------
1 | from .base import *
2 |
3 | # SECURITY WARNING: don't run with debug turned on in production!
4 | DEBUG = True
5 |
6 | # SECURITY WARNING: keep the secret key used in production secret!
7 | SECRET_KEY = 'q%ng_7whu^xd-mqf#%xh*s!-5!=pqhk-vc0-2n*l4ixh4z7qe#'
8 |
9 |
10 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
11 |
12 |
13 | try:
14 | from .local import *
15 | except ImportError:
16 | pass
17 |
--------------------------------------------------------------------------------
/longclaw_bakery/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for longclaw_bakery project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/
8 | """
9 | import os
10 |
11 | from django.core.wsgi import get_wsgi_application
12 |
13 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "longclaw_bakery.settings.dev")
14 |
15 | application = get_wsgi_application()
16 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.6
2 |
3 | WORKDIR opt/deploy/
4 | RUN mkdir nvm
5 | ENV NVM_DIR /opt/deploy/nvm
6 |
7 | # Install nvm with node and npm
8 | RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash \
9 | && . $NVM_DIR/nvm.sh \
10 | && nvm install --lts\
11 | && nvm use --lts
12 |
13 | ADD . .
14 |
15 | RUN pip install -r requirements.txt
16 | RUN python manage.py makemigrations catalog home \
17 | && python manage.py migrate \
18 | && python manage.py loadcountries
19 |
20 | EXPOSE 8000
21 | CMD python manage.py runserver 0.0.0.0:8000
22 |
--------------------------------------------------------------------------------
/home/templates/home/home_page.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block body_class %}template-homepage{% endblock %}
4 |
5 | {% block content %}
6 | Welcome to the Longclaw Bakery!
7 | This is a demonstration site of the Longclaw E-Commerce extension for Wagtail
8 | If you want to make your own site, it usually preferable to get started with longclaw project template, by running longclaw start rather than forking this project
9 | You can access the admin interface here . Login with admin/admin.
10 |
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/catalog/migrations/0002_auto_20190105_1655.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.1.4 on 2019-01-05 16:55
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('catalog', '0001_initial'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='productvariant',
15 | name='gluten_free',
16 | field=models.BooleanField(default=False),
17 | ),
18 | migrations.AddField(
19 | model_name='productvariant',
20 | name='vegetarian',
21 | field=models.BooleanField(default=False),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Longclaw Demo
2 | =============================
3 |
4 | Demo shop for Longclaw
5 |
6 | This example shop is intended to show the capabilities of longclaw.
7 | When creating your own shop, I recommend starting afresh using the `longclaw start` command, rather than forking this project.
8 |
9 | Quickstart
10 | ----------
11 |
12 | Clone this repository
13 | ```
14 | $ git clone https://github.com/JamesRamm/longclaw_demo.git
15 | $ cd longclaw_demo
16 | ```
17 | Build the docker image
18 | ```
19 | $ docker build -t longclaw-demo .
20 | ```
21 | Start the docker container and expose port 8000
22 |
23 | ```
24 | $ docker run -it -p 8000:8000 longclaw-demo
25 | ```
26 |
27 | Navigate to localhost:8000/admin and sign in with `admin`, `admin`
28 |
--------------------------------------------------------------------------------
/longclaw_bakery/templates/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Internal server error
10 |
11 |
12 |
13 | Internal server error
14 |
15 | Sorry, there seems to be an error. Please try again soon.
16 |
17 |
18 |
--------------------------------------------------------------------------------
/catalog/templates/catalog/product_index.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% load wagtailcore_tags wagtailimages_tags %}
4 |
5 | {% block body_class %}template-productindex{% endblock %}
6 |
7 | {% block content %}
8 | {{ page.title }}
9 |
10 | {% for post in page.get_children %}
11 | {% with post=post.specific %}
12 |
13 | {% image post.images.first.image max-400x400 %}
14 |
15 |
{{ post.title }}
16 |
{{ post.description|richtext }}
17 |
From €{{ post.price_range.0 }}
18 |
19 |
20 | {% endwith %}
21 | {% endfor %}
22 |
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[cod]
2 | __pycache__
3 | .directory
4 | .vscode/
5 | tags
6 | node_modules/
7 |
8 | *.sqlite3
9 |
10 | # C extensions
11 | *.so
12 |
13 | # Packages
14 | *.egg
15 | *.egg-info
16 | dist
17 | build
18 | eggs
19 | parts
20 | var
21 | sdist
22 | develop-eggs
23 | .installed.cfg
24 | lib
25 | lib64
26 |
27 | # Installer logs
28 | pip-log.txt
29 | src/
30 |
31 | # Unit test / coverage reports
32 | .coverage
33 | .tox
34 | nosetests.xml
35 | htmlcov
36 |
37 | # Translations
38 | *.mo
39 |
40 | # Mr Developer
41 | .mr.developer.cfg
42 | .project
43 | .pydevproject
44 |
45 | # Pycharm/Intellij
46 | .idea
47 |
48 | # Complexity
49 | output/*.html
50 | output/*/index.html
51 |
52 | # Sphinx
53 | docs/_build
54 |
55 |
56 | webpack-stats.json
57 | *bundle.js*
58 |
59 | static/
--------------------------------------------------------------------------------
/catalog/migrations/0003_auto_20190105_1725.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.1.4 on 2019-01-05 17:25
2 |
3 | import django.core.validators
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('catalog', '0002_auto_20190105_1655'),
11 | ]
12 |
13 | operations = [
14 | migrations.AddField(
15 | model_name='productvariant',
16 | name='discount',
17 | field=models.BooleanField(default=False),
18 | ),
19 | migrations.AddField(
20 | model_name='productvariant',
21 | name='discount_percent',
22 | field=models.PositiveSmallIntegerField(default=20, validators=[django.core.validators.MaxValueValidator(75)]),
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/catalog/migrations/0005_auto_20190116_0610.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.1.4 on 2019-01-16 06:10
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('catalog', '0004_auto_20190116_0609'),
10 | ]
11 |
12 | operations = [
13 | migrations.RemoveField(
14 | model_name='productvariant',
15 | name='music_format',
16 | ),
17 | migrations.AddField(
18 | model_name='productvariant',
19 | name='gluten_free',
20 | field=models.BooleanField(default=False),
21 | ),
22 | migrations.AddField(
23 | model_name='productvariant',
24 | name='vegetarian',
25 | field=models.BooleanField(default=False),
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/home/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.1.4 on 2018-12-29 21:30
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = [
12 | ('wagtailcore', '0041_group_collection_permissions_verbose_name_plural'),
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='HomePage',
18 | fields=[
19 | ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
20 | ],
21 | options={
22 | 'abstract': False,
23 | },
24 | bases=('wagtailcore.page',),
25 | ),
26 | ]
27 |
--------------------------------------------------------------------------------
/catalog/migrations/0004_auto_20190116_0609.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.1.4 on 2019-01-16 06:09
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('catalog', '0003_auto_20190105_1725'),
10 | ]
11 |
12 | operations = [
13 | migrations.RemoveField(
14 | model_name='productvariant',
15 | name='gluten_free',
16 | ),
17 | migrations.RemoveField(
18 | model_name='productvariant',
19 | name='vegetarian',
20 | ),
21 | migrations.AddField(
22 | model_name='productvariant',
23 | name='music_format',
24 | field=models.CharField(choices=[('CD', 'CD'), ('Vinyl', 'Vinyl')], default='CD', max_length=10),
25 | preserve_default=False,
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/longclaw_bakery/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.conf.urls import include, url
3 | from django.contrib import admin
4 |
5 | from search import views as search_views
6 |
7 | from wagtail.admin import urls as wagtailadmin_urls
8 | from wagtail.core import urls as wagtail_urls
9 | from wagtail.documents import urls as wagtaildocs_urls
10 | from longclaw import urls as longclaw_urls
11 |
12 | urlpatterns = [
13 | url(r'^django-admin/', admin.site.urls),
14 |
15 | url(r'^admin/', include(wagtailadmin_urls)),
16 | url(r'^documents/', include(wagtaildocs_urls)),
17 |
18 | url(r'^search/$', search_views.search, name='search'),
19 |
20 | url(r'', include(longclaw_urls)),
21 | url(r'', include(wagtail_urls))
22 | ]
23 |
24 |
25 | if settings.DEBUG:
26 | from django.conf.urls.static import static
27 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns
28 |
29 | # Serve static and media files from development server
30 | urlpatterns += staticfiles_urlpatterns()
31 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 James Ramm
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/search/views.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import, unicode_literals
2 |
3 | from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
4 | from django.shortcuts import render
5 |
6 | try:
7 | from wagtail.core.models import Page
8 | from wagtail.search.models import Query
9 | except ImportError:
10 | from wagtail.wagtailcore.models import Page
11 | from wagtail.wagtailsearch.models import Query
12 |
13 |
14 | def search(request):
15 | search_query = request.GET.get('query', None)
16 | page = request.GET.get('page', 1)
17 |
18 | # Search
19 | if search_query:
20 | search_results = Page.objects.live().search(search_query)
21 | query = Query.get(search_query)
22 |
23 | # Record hit
24 | query.add_hit()
25 | else:
26 | search_results = Page.objects.none()
27 |
28 | # Pagination
29 | paginator = Paginator(search_results, 10)
30 | try:
31 | search_results = paginator.page(page)
32 | except PageNotAnInteger:
33 | search_results = paginator.page(1)
34 | except EmptyPage:
35 | search_results = paginator.page(paginator.num_pages)
36 |
37 | return render(request, 'search/search.html', {
38 | 'search_query': search_query,
39 | 'search_results': search_results,
40 | })
41 |
--------------------------------------------------------------------------------
/search/templates/search/search.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load static wagtailcore_tags %}
3 |
4 | {% block body_class %}template-searchresults{% endblock %}
5 |
6 | {% block title %}Search{% endblock %}
7 |
8 | {% block content %}
9 | Search
10 |
11 |
15 |
16 | {% if search_results %}
17 |
18 | {% for result in search_results %}
19 |
20 |
21 | {% if result.search_description %}
22 | {{ result.search_description|safe }}
23 | {% endif %}
24 |
25 | {% endfor %}
26 |
27 |
28 | {% if search_results.has_previous %}
29 | Previous
30 | {% endif %}
31 |
32 | {% if search_results.has_next %}
33 | Next
34 | {% endif %}
35 | {% elif search_query %}
36 | No results found
37 | {% endif %}
38 | {% endblock %}
39 |
--------------------------------------------------------------------------------
/longclaw_bakery/templates/checkout/checkout.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load longclawcheckout_tags longclawcore_tags %}
3 |
4 | {% block content %}
5 | {#
6 | `checkout_form`, `shipping_form`, `billing_form`, `basket` and `total_price` are the context
7 | variables available for you to build up your checkout page.
8 | `checkout_form` includes the `different_billing_address` checkbox which you can use to
9 | decide whether to display `billing_form` or not (javascript needed here!).
10 | `checkout_form` also includes the `shipping_option` dropdown which should be initialized
11 | using the `initShippingOption` javascript function.
12 | The aforementioned fields are optional (different_billing_address and shipping_option); if you dont
13 | offer shipping/it is fixed rate you can prevent these fields from being displayed.
14 | The only required field in `checkout_form` is `email`.
15 |
16 | `shipping_form` gathers the address. `billing_form` is the same (but intended to gather a billing address).
17 | `billing_form` is optional; you may not require it, or a gateway integration dropin may gather it instead.
18 |
19 | `basket` is a queryset of `BasketItem` for the current customer.
20 | `total_price` is the total cost of all items in the basket.
21 | #}
22 | {% endblock content %}
23 |
24 | {% block extra_js %}
25 |
26 | {#
27 | Load any client javascript provided by the payment gateway.
28 | This will give a list of tags which load the client SDK's.
29 | An integration might have more than 1 SDK - e.g. braintree offers
30 | 'dropin' and 'hostedfields'. A user should be able to rely on all
31 | possibilities being loaded or refer to individual gateway documentation.
32 | #}
33 | {% gateway_client_js as scripts %}
34 | {% for js in scripts %}
35 | {{ js|safe }}
36 | {% endfor %}
37 |
38 | {#
39 | Finally add the media from the checkout form. This provides the ``initShippingOption`` function
40 | which will initialize the shipping option dropdown on the checkout form
41 | #}
42 | {{ checkout_form.media }}
43 |
44 |
--------------------------------------------------------------------------------
/catalog/templates/catalog/product.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% load wagtailcore_tags wagtailimages_tags basket_tags %}
4 |
5 | {% block body_class %} template-productpage {%endblock%}
6 |
7 | {% block content %}
8 |
9 |
10 | {% if page.images %}
11 | {% for item in page.images.all %}
12 |
13 | {% image item.image max-1000x900 %}
14 |
{{ item.caption }}
15 |
16 | {% endfor %}
17 | {% else %}
18 |
19 | {% endif %}
20 |
21 |
22 |
23 |
{{ page.title }}
24 |
{{ page.description|richtext }}
25 |
26 |
27 |
28 | Type
29 | Price
30 |
31 |
32 |
33 | {% for variant in page.variants.all %}
34 |
35 | {{variant.ref}}
36 | €{{variant.price}}
37 |
38 | {% if variant.stock > 0 %}
39 | {% add_to_basket_btn variant.id btn_text="Add To Basket" %}
40 | {% else %}
41 | Sold out
42 | {% endif %}
43 |
44 |
45 | {% endfor %}
46 |
47 |
48 |
49 |
50 | {% if page.tags.all.count %}
51 |
57 | {% endif %}
58 |
59 | Return
60 |
61 | {% endblock %}
62 |
--------------------------------------------------------------------------------
/longclaw_bakery/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load static wagtailuserbar %}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {% block title %}{% if self.seo_title %}{{ self.seo_title }}{% else %}{{ self.title }}{% endif %}{% endblock %}{% block title_suffix %}{% endblock %}
12 |
13 |
14 |
15 | {# Global stylesheets #}
16 |
17 |
18 | {% block extra_css %}
19 | {# Override this in templates to add extra stylesheets #}
20 | {% endblock %}
21 |
22 |
23 |
24 |
25 | {% wagtailuserbar %}
26 |
37 |
38 |
39 | {% block content %}{% endblock %}
40 |
41 |
42 |
43 | {# Global javascript #}
44 |
45 |
46 | {% block extra_js %}
47 | {# Override this in templates to add extra javascript #}
48 | {% endblock %}
49 |
50 |
51 |
--------------------------------------------------------------------------------
/longclaw_bakery/templates/basket/basket.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load wagtailcore_tags wagtailimages_tags %}
3 |
4 |
5 | {% block content %}
6 | Your Basket
7 |
8 |
9 |
10 |
11 |
12 | Quantity
13 | Price
14 |
15 |
16 |
17 | {% for item in basket %}
18 |
19 | {% image item.variant.product.first_image.image max-75x75 %}
20 | {{item.variant.product.title}} - {{item.variant.ref}}
21 | {{item.quantity}}
22 | {{item.total}}
23 |
24 | {% empty %}
25 |
26 | There is nothing in your basket!
27 |
28 | {% endfor %}
29 | {% if basket.count > 0 %}
30 |
31 |
32 |
33 |
34 | {{total_price}}
35 |
36 |
37 |
38 |
39 |
40 | Checkout
41 |
42 | {% endif %}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
53 |
54 |
Thanks! your order ID is #
55 |
Since this is a demo we have skipped taking payment details and performing a 'real' checkout
56 |
You can view the created order in the Wagtail admin. Login with demo/demo
57 |
58 |
61 |
62 |
63 |
64 | {% endblock %}
--------------------------------------------------------------------------------
/catalog/models.py:
--------------------------------------------------------------------------------
1 | import decimal
2 | from django.db import models
3 | from django.core.validators import MaxValueValidator
4 | from django_extensions.db.fields import AutoSlugField
5 | from modelcluster.fields import ParentalKey
6 | from wagtail.core.models import Page, Orderable
7 | from wagtail.core.fields import RichTextField
8 | from wagtail.admin.edit_handlers import FieldPanel, InlinePanel
9 | from wagtail.images.edit_handlers import ImageChooserPanel
10 | from longclaw.products.models import ProductVariantBase, ProductBase
11 |
12 | class ProductIndex(Page):
13 | """Index page for all products
14 | """
15 | subpage_types = ('catalog.Product', 'catalog.ProductIndex')
16 |
17 |
18 | class Product(ProductBase):
19 | parent_page_types = ['catalog.ProductIndex']
20 | description = RichTextField()
21 | content_panels = ProductBase.content_panels + [
22 | FieldPanel('description'),
23 | InlinePanel('images', label="Images"),
24 | InlinePanel('variants', label='Product variants'),
25 |
26 | ]
27 |
28 | @property
29 | def first_image(self):
30 | return self.images.first()
31 |
32 |
33 | class ProductVariant(ProductVariantBase):
34 | """Represents a 'variant' of a product
35 | """
36 | # You *could* do away with the 'Product' concept entirely - e.g. if you only
37 | # want to support 1 'variant' per 'product'.
38 | product = ParentalKey(Product, related_name='variants')
39 |
40 | slug = AutoSlugField(
41 | separator='',
42 | populate_from=('product', 'ref'),
43 | )
44 |
45 | # Enter your custom product variant fields here
46 | # e.g. colour, size, stock and so on.
47 | # Remember, ProductVariantBase provides 'price', 'ref' and 'stock' fields
48 | description = RichTextField()
49 |
50 | gluten_free = models.BooleanField(default=False)
51 | vegetarian = models.BooleanField(default=False)
52 |
53 | discount = models.BooleanField(default=False)
54 | discount_percent = models.PositiveSmallIntegerField(
55 | default=20,
56 | validators=[MaxValueValidator(75)]
57 | )
58 |
59 | @ProductVariantBase.price.getter
60 | def price(self):
61 | if self.discount:
62 | discount_price = self.base_price * decimal.Decimal((100 - self.discount_percent) / 100.0 )
63 | return discount_price.quantize(decimal.Decimal('.01'), decimal.ROUND_HALF_UP)
64 | return self.base_price
65 |
66 |
67 | class ProductImage(Orderable):
68 | """Example of adding images related to a product model
69 | """
70 | product = ParentalKey(Product, related_name='images')
71 | image = models.ForeignKey('wagtailimages.Image', on_delete=models.CASCADE, related_name='+')
72 | caption = models.CharField(blank=True, max_length=255)
73 |
74 | panels = [
75 | ImageChooserPanel('image'),
76 | FieldPanel('caption')
77 | ]
78 |
--------------------------------------------------------------------------------
/catalog/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.1.4 on 2018-12-29 21:30
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 | import django_extensions.db.fields
6 | import modelcluster.fields
7 | import wagtail.core.fields
8 |
9 |
10 | class Migration(migrations.Migration):
11 |
12 | initial = True
13 |
14 | dependencies = [
15 | ('wagtailimages', '0001_squashed_0021'),
16 | ('wagtailcore', '0041_group_collection_permissions_verbose_name_plural'),
17 | ]
18 |
19 | operations = [
20 | migrations.CreateModel(
21 | name='Product',
22 | fields=[
23 | ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
24 | ('description', wagtail.core.fields.RichTextField()),
25 | ],
26 | options={
27 | 'abstract': False,
28 | },
29 | bases=('wagtailcore.page',),
30 | ),
31 | migrations.CreateModel(
32 | name='ProductImage',
33 | fields=[
34 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
35 | ('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
36 | ('caption', models.CharField(blank=True, max_length=255)),
37 | ('image', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtailimages.Image')),
38 | ('product', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='catalog.Product')),
39 | ],
40 | options={
41 | 'ordering': ['sort_order'],
42 | 'abstract': False,
43 | },
44 | ),
45 | migrations.CreateModel(
46 | name='ProductIndex',
47 | fields=[
48 | ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
49 | ],
50 | options={
51 | 'abstract': False,
52 | },
53 | bases=('wagtailcore.page',),
54 | ),
55 | migrations.CreateModel(
56 | name='ProductVariant',
57 | fields=[
58 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
59 | ('base_price', models.DecimalField(decimal_places=2, max_digits=12)),
60 | ('ref', models.CharField(max_length=32)),
61 | ('stock', models.IntegerField(default=0)),
62 | ('slug', django_extensions.db.fields.AutoSlugField(blank=True, editable=False, populate_from=('product', 'ref'), separator='')),
63 | ('description', wagtail.core.fields.RichTextField()),
64 | ('product', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='variants', to='catalog.Product')),
65 | ],
66 | options={
67 | 'abstract': False,
68 | },
69 | ),
70 | ]
71 |
--------------------------------------------------------------------------------
/longclaw_bakery/settings/base.py:
--------------------------------------------------------------------------------
1 |
2 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
3 | import os
4 |
5 | PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
6 | BASE_DIR = os.path.dirname(PROJECT_DIR)
7 |
8 |
9 | # Quick-start development settings - unsuitable for production
10 | # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
11 |
12 |
13 | # Application definition
14 |
15 | INSTALLED_APPS = [
16 | 'django.contrib.admin',
17 | 'django.contrib.auth',
18 | 'django.contrib.contenttypes',
19 | 'django.contrib.sessions',
20 | 'django.contrib.messages',
21 | 'django.contrib.staticfiles',
22 |
23 | 'wagtail.contrib.forms',
24 | 'wagtail.contrib.redirects',
25 | 'wagtail.embeds',
26 | 'wagtail.sites',
27 | 'wagtail.users',
28 | 'wagtail.snippets',
29 | 'wagtail.documents',
30 | 'wagtail.images',
31 | 'wagtail.search',
32 | 'wagtail.admin',
33 | 'wagtail.core',
34 | 'wagtail.contrib.modeladmin',
35 | 'wagtail.contrib.settings',
36 | 'wagtail.api.v2',
37 |
38 |
39 | 'modelcluster',
40 | 'taggit',
41 | 'rest_framework',
42 |
43 | 'longclaw.core',
44 | 'longclaw.configuration',
45 | 'longclaw.shipping',
46 | 'longclaw.products',
47 | 'longclaw.orders',
48 | 'longclaw.checkout',
49 | 'longclaw.basket',
50 | 'longclaw.stats',
51 |
52 | 'home',
53 | 'search',
54 | 'catalog'
55 |
56 | ]
57 |
58 | MIDDLEWARE = [
59 | 'django.contrib.sessions.middleware.SessionMiddleware',
60 | 'django.middleware.common.CommonMiddleware',
61 | 'django.middleware.csrf.CsrfViewMiddleware',
62 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
63 | 'django.contrib.messages.middleware.MessageMiddleware',
64 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
65 | 'django.middleware.security.SecurityMiddleware',
66 |
67 | 'wagtail.core.middleware.SiteMiddleware',
68 | 'wagtail.contrib.redirects.middleware.RedirectMiddleware',
69 | ]
70 |
71 | ROOT_URLCONF = 'longclaw_bakery.urls'
72 |
73 | TEMPLATES = [
74 | {
75 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
76 | 'DIRS': [
77 | os.path.join(PROJECT_DIR, 'templates'),
78 | ],
79 | 'APP_DIRS': True,
80 | 'OPTIONS': {
81 | 'context_processors': [
82 | 'django.template.context_processors.debug',
83 | 'django.template.context_processors.request',
84 | 'django.contrib.auth.context_processors.auth',
85 | 'django.contrib.messages.context_processors.messages',
86 | 'longclaw.configuration.context_processors.currency',
87 | ],
88 | },
89 | },
90 | ]
91 |
92 | WSGI_APPLICATION = 'longclaw_bakery.wsgi.application'
93 |
94 |
95 | # Database
96 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases
97 |
98 | DATABASES = {
99 | 'default': {
100 | 'ENGINE': 'django.db.backends.sqlite3',
101 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
102 | }
103 | }
104 |
105 |
106 | # Internationalization
107 | # https://docs.djangoproject.com/en/2.1/topics/i18n/
108 |
109 | LANGUAGE_CODE = 'en-gb'
110 |
111 | TIME_ZONE = 'UTC'
112 |
113 | USE_I18N = True
114 |
115 | USE_L10N = True
116 |
117 | USE_TZ = True
118 |
119 |
120 | # Static files (CSS, JavaScript, Images)
121 | # https://docs.djangoproject.com/en/2.1/howto/static-files/
122 |
123 | STATICFILES_FINDERS = [
124 | 'django.contrib.staticfiles.finders.FileSystemFinder',
125 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
126 | ]
127 |
128 | STATICFILES_DIRS = [
129 | os.path.join(PROJECT_DIR, 'static'),
130 | ]
131 |
132 | STATIC_ROOT = os.path.join(BASE_DIR, 'static')
133 | STATIC_URL = '/static/'
134 |
135 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
136 | MEDIA_URL = '/media/'
137 |
138 | # Wagtail settings
139 |
140 | WAGTAIL_SITE_NAME = "longclaw_bakery"
141 |
142 | # Base URL to use when referring to full URLs within the Wagtail admin backend -
143 | # e.g. in notification emails. Don't include '/admin' or a trailing slash
144 | BASE_URL = 'http://example.com'
145 |
146 | # Longclaw settings
147 |
148 | # The payment gateway to use. `BasePayment` is a dummy payment gateway for testing.
149 | # Longclaw also offers 'BraintreePayment', 'PaypalVZeroPayment' and 'StripePayment'
150 | PAYMENT_GATEWAY = 'longclaw.checkout.gateways.stripe.StripePayment'
151 | STRIPE_SECRET = os.environ.get('STRIPE_SECRET', '')
152 |
153 | PRODUCT_VARIANT_MODEL = 'catalog.ProductVariant'
154 |
--------------------------------------------------------------------------------