├── 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 |
12 | 13 | 14 |
15 | 16 | {% if search_results %} 17 | 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 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 | 13 | 14 | 15 | 16 | 17 | {% for item in basket %} 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% empty %} 25 | 26 | 27 | 28 | {% endfor %} 29 | {% if basket.count > 0 %} 30 | 31 | 35 | 36 | 37 | 41 | 42 | {% endif %} 43 | 44 |
QuantityPrice
{% image item.variant.product.first_image.image max-75x75 %}{{item.variant.product.title}} - {{item.variant.ref}}{{item.quantity}}{{item.total}}

There is nothing in your basket!

32 | 33 | 34 | {{total_price}}
38 | 39 | 40 | Checkout
45 | 46 | 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 | --------------------------------------------------------------------------------