├── blog ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0002_blogpostpage_body.py │ └── 0001_initial.py ├── admin.py ├── tests.py ├── views.py ├── apps.py ├── templates │ └── blog │ │ └── blog_post_page.html └── models.py ├── home ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0003_homepage_subtitle.py │ ├── 0004_homepage_image.py │ ├── 0005_homepage_linked_page.py │ ├── 0001_initial.py │ └── 0002_create_homepage.py ├── templatetags │ ├── __init__.py │ └── global_navigation.py ├── models.py ├── templates │ └── home │ │ ├── home_page.html │ │ └── welcome_page.html └── static │ └── css │ └── welcome_page.css ├── search ├── __init__.py ├── views.py └── templates │ └── search │ └── search.html ├── mystore ├── __init__.py ├── settings │ ├── __init__.py │ ├── production.py │ ├── dev.py │ └── base.py ├── static │ ├── js │ │ └── mystore.js │ └── css │ │ └── mystore.css ├── templates │ ├── blocks │ │ ├── title_block.html │ │ ├── image_block.html │ │ └── title_and_subtitle_block.html │ ├── 404.html │ ├── 500.html │ ├── blog │ │ └── blog_index_page.html │ └── base.html ├── wsgi.py └── urls.py ├── products ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0005_productdetailpage_category.py │ ├── 0003_productdetailpage_body.py │ ├── 0004_productcategory.py │ ├── 0001_initial.py │ └── 0002_productimages.py ├── admin.py ├── tests.py ├── views.py ├── apps.py ├── blocks.py ├── templates │ └── products │ │ └── product_detail_page.html └── models.py ├── .gitignore ├── requirements.txt ├── manage.py ├── .dockerignore └── Dockerfile /blog/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /home/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /search/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mystore/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /products/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /home/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mystore/settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mystore/static/js/mystore.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /home/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mystore/static/css/mystore.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /products/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mystore/templates/blocks/title_block.html: -------------------------------------------------------------------------------- 1 |

{{ self }}

2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | db.sqlite3 2 | .venv/ 3 | venv/ 4 | *.pyc 5 | media/ 6 | .cache/ 7 | -------------------------------------------------------------------------------- /blog/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /blog/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /blog/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /products/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /products/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /products/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django>=4.0,<4.1 2 | wagtail==3.0rc3 3 | ipython==8.3.0 4 | django-extensions==3.1.5 5 | -------------------------------------------------------------------------------- /mystore/settings/production.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | 3 | DEBUG = False 4 | 5 | try: 6 | from .local import * 7 | except ImportError: 8 | pass 9 | -------------------------------------------------------------------------------- /mystore/templates/blocks/image_block.html: -------------------------------------------------------------------------------- 1 | {% load wagtailimages_tags %} 2 | 3 | {% image self fill-250x250 as img %} 4 | 5 | -------------------------------------------------------------------------------- /blog/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BlogConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'blog' 7 | -------------------------------------------------------------------------------- /mystore/templates/blocks/title_and_subtitle_block.html: -------------------------------------------------------------------------------- 1 |

{{ self.title }}

2 | {% if self.subtitle %} 3 |

{{ self.subtitle }}

4 | {% endif %} 5 | -------------------------------------------------------------------------------- /products/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ProductsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'products' 7 | -------------------------------------------------------------------------------- /mystore/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Page not found{% endblock %} 4 | 5 | {% block body_class %}template-404{% endblock %} 6 | 7 | {% block content %} 8 |

Page not found

9 | 10 |

Sorry, this page could not be found.

11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mystore.settings.dev") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /products/blocks.py: -------------------------------------------------------------------------------- 1 | from wagtail.core import blocks 2 | 3 | 4 | class TitleAndSubtitleBlock(blocks.StructBlock): 5 | title = blocks.CharBlock(max_length=100, required=True) 6 | subtitle = blocks.CharBlock(max_length=250, required=False) 7 | 8 | class Meta: 9 | template = "blocks/title_and_subtitle_block.html" 10 | icon = "edit" 11 | label = "Title & Text" 12 | -------------------------------------------------------------------------------- /mystore/templates/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Internal server error 6 | 7 | 8 | 9 |

Internal server error

10 | 11 |

Sorry, there seems to be an error. Please try again soon.

12 | 13 | 14 | -------------------------------------------------------------------------------- /mystore/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mystore 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/4.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mystore.settings.dev") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /home/migrations/0003_homepage_subtitle.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-29 18:05 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('home', '0002_create_homepage'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='homepage', 15 | name='subtitle', 16 | field=models.CharField(blank=True, default='', max_length=100), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Django project 2 | /media/ 3 | /static/ 4 | *.sqlite3 5 | 6 | # Python and others 7 | __pycache__ 8 | *.pyc 9 | .DS_Store 10 | *.swp 11 | /venv/ 12 | /tmp/ 13 | /.vagrant/ 14 | /Vagrantfile.local 15 | node_modules/ 16 | /npm-debug.log 17 | /.idea/ 18 | .vscode 19 | coverage 20 | .python-version 21 | 22 | # Distribution / packaging 23 | .Python 24 | env/ 25 | build/ 26 | develop-eggs/ 27 | dist/ 28 | downloads/ 29 | eggs/ 30 | .eggs/ 31 | lib/ 32 | lib64/ 33 | parts/ 34 | sdist/ 35 | var/ 36 | wheels/ 37 | *.egg-info/ 38 | .installed.cfg 39 | *.egg 40 | -------------------------------------------------------------------------------- /home/templatetags/global_navigation.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.core.cache import cache 3 | 4 | from home.models import HomePage 5 | 6 | register = template.Library() 7 | 8 | 9 | @register.simple_tag 10 | def global_nav_items(): 11 | CACHE_NAME = "MENU_PAGES" 12 | if cache.get(CACHE_NAME): 13 | menu_pages = cache.get(CACHE_NAME) 14 | else: 15 | homepage = HomePage.objects.first() 16 | menu_pages = homepage.get_children().in_menu().live() 17 | cache.set(CACHE_NAME, menu_pages, 25200) 18 | return menu_pages 19 | -------------------------------------------------------------------------------- /blog/templates/blog/blog_post_page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load wagtailcore_tags %} 3 | 4 | {% block hero %} 5 |
6 |
7 | {{ page.get_parent.title }} 8 |

{{ page.title }}

9 |
10 |
11 | {% endblock %} 12 | 13 | {% block content %} 14 |

{{ page.title }}

15 | {% include_block page.body %} 16 | {% endblock content %} 17 | -------------------------------------------------------------------------------- /mystore/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 = "django-insecure-j(68f)@#93jw$hpoe2)(ndzes8dlqo3^^89)2##l%x*+dc!%l%" 8 | 9 | # SECURITY WARNING: define the correct hosts in production! 10 | ALLOWED_HOSTS = ["*"] 11 | 12 | EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" 13 | 14 | INSTALLED_APPS = INSTALLED_APPS + [ 15 | 'django_extensions', 16 | ] 17 | 18 | try: 19 | from .local import * 20 | except ImportError: 21 | pass 22 | -------------------------------------------------------------------------------- /products/migrations/0005_productdetailpage_category.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-29 19:34 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('products', '0004_productcategory'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='productdetailpage', 16 | name='category', 17 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='products.productcategory'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /home/migrations/0004_homepage_image.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-29 18:09 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('wagtailimages', '0024_index_image_file_hash'), 11 | ('home', '0003_homepage_subtitle'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='homepage', 17 | name='image', 18 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /home/migrations/0005_homepage_linked_page.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-29 18:12 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('wagtailcore', '0069_log_entry_jsonfield'), 11 | ('home', '0004_homepage_image'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='homepage', 17 | name='linked_page', 18 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailcore.page'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /products/migrations/0003_productdetailpage_body.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-29 19:23 2 | 3 | from django.db import migrations 4 | import wagtail.blocks 5 | import wagtail.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('products', '0002_productimages'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='productdetailpage', 17 | name='body', 18 | field=wagtail.fields.StreamField([('title_and_subtitle', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(max_length=100, required=True)), ('subtitle', wagtail.blocks.CharBlock(max_length=250, required=False))]))], blank=True, null=True, use_json_field=None), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /blog/migrations/0002_blogpostpage_body.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-29 18:34 2 | 3 | from django.db import migrations 4 | import wagtail.blocks 5 | import wagtail.fields 6 | import wagtail.images.blocks 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('blog', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='blogpostpage', 18 | name='body', 19 | field=wagtail.fields.StreamField([('title', wagtail.blocks.CharBlock(form_classname='full title', template='blocks/title_block.html')), ('richtext', wagtail.blocks.RichTextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock())], blank=True, null=True, use_json_field=None), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /home/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from wagtail.admin.panels import FieldPanel 4 | from wagtail.models import Page 5 | 6 | 7 | class HomePage(Page): 8 | subtitle = models.CharField(max_length=100, default='', blank=True) 9 | image = models.ForeignKey( 10 | "wagtailimages.Image", 11 | null=True, 12 | blank=True, 13 | on_delete=models.SET_NULL, 14 | related_name="+" 15 | ) 16 | linked_page = models.ForeignKey( 17 | "wagtailcore.Page", 18 | null=True, 19 | blank=True, 20 | on_delete=models.SET_NULL, 21 | related_name="+" 22 | ) 23 | 24 | content_panels = Page.content_panels + [ 25 | FieldPanel('subtitle'), 26 | FieldPanel('image'), 27 | FieldPanel('linked_page'), 28 | ] 29 | -------------------------------------------------------------------------------- /home/templates/home/home_page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load wagtailimages_tags %} 3 | 4 | {% block body_class %}template-homepage{% endblock %} 5 | 6 | {% block hero %} 7 |
8 |
9 |
10 |

{{ page.title }}

11 |
12 |
13 | {% if page.subtitle %}{{ page.subtitle }}{% endif %} 14 |
15 |
16 |
17 | {% endblock %} 18 | 19 | {% block content %} 20 | {{ page.title }} - {{ page.subtitle }} 21 | 22 | {% image page.image fill-250x250 %} 23 | 24 | {{ page.linked_page.url }} 25 | {% endblock content %} 26 | -------------------------------------------------------------------------------- /products/migrations/0004_productcategory.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-29 19:33 2 | 3 | from django.db import migrations, models 4 | import wagtail.fields 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('products', '0003_productdetailpage_body'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='ProductCategory', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('name', models.CharField(max_length=30)), 19 | ('description', wagtail.fields.RichTextField(blank=True)), 20 | ('slug', models.SlugField(unique=True)), 21 | ], 22 | options={ 23 | 'verbose_name': 'Product category', 24 | 'verbose_name_plural': 'Product categories', 25 | }, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /home/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.db import migrations, models 3 | 4 | 5 | class Migration(migrations.Migration): 6 | 7 | dependencies = [ 8 | ("wagtailcore", "0040_page_draft_title"), 9 | ] 10 | 11 | operations = [ 12 | migrations.CreateModel( 13 | name="HomePage", 14 | fields=[ 15 | ( 16 | "page_ptr", 17 | models.OneToOneField( 18 | on_delete=models.CASCADE, 19 | parent_link=True, 20 | auto_created=True, 21 | primary_key=True, 22 | serialize=False, 23 | to="wagtailcore.Page", 24 | ), 25 | ), 26 | ], 27 | options={ 28 | "abstract": False, 29 | }, 30 | bases=("wagtailcore.page",), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /products/templates/products/product_detail_page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load wagtailimages_tags wagtailcore_tags cache %} 3 | 4 | 5 | {% block content %} 6 | {% if request.is_preview %} 7 | {% comment %} #A 8 | Show your preview in here. #A 9 | {% endcomment %} #A 10 | {% else %} 11 | {% cache 604800 product_page_cache page.id %} 12 | Category: {{ page.category.name }} 13 | 14 | {% for product_image in page.product_images.all %} 15 | {% image product_image.image fill-250x250 as img %} 16 |
17 |  {{product_image.alt_text }}
18 | {% if product_image.short_description %} 19 | {{ product_image.short_description }} 20 | {% endif %} 21 |
22 | {% endfor %} 23 | 24 | {% include_block page.body %} 25 | {% endcache %} 26 | {% endif %} 27 | {% endblock content %} 28 | -------------------------------------------------------------------------------- /blog/models.py: -------------------------------------------------------------------------------- 1 | from wagtail.admin.panels import FieldPanel 2 | from wagtail.core.models import Page 3 | from wagtail.core import blocks 4 | from wagtail.core.fields import StreamField 5 | from wagtail.images.blocks import ImageChooserBlock 6 | 7 | 8 | class BlogIndexPage(Page): 9 | max_count = 1 10 | subpage_types = ['blog.BlogPostPage'] 11 | 12 | def get_context(self, request, *args, **kwargs): 13 | context = super().get_context(request, *args, **kwargs) 14 | context['blog_posts'] = BlogPostPage.objects.live() 15 | return context 16 | 17 | 18 | class BlogPostPage(Page): 19 | parent_page_types = ['blog.BlogIndexPage'] 20 | 21 | body = StreamField([ 22 | ("title", blocks.CharBlock( 23 | form_classname="full title", 24 | template="blocks/title_block.html" 25 | )), 26 | ("richtext", blocks.RichTextBlock()), 27 | ("image", ImageChooserBlock()) 28 | ], blank=True, null=True) 29 | 30 | content_panels = Page.content_panels + [ 31 | FieldPanel('body') 32 | ] 33 | -------------------------------------------------------------------------------- /mystore/templates/blog/blog_index_page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block hero %} 4 |
5 |
6 |

{{ page.title }}

7 |
8 |
9 | {% endblock %} 10 | 11 | {% block content %} 12 |
13 | {% for child in blog_posts %} 14 |
15 | 16 | {{ child.title }} 17 | 18 | {% comment %} 19 | You could add a subtitle and image in here, too. 20 | Don't forget to add those fields to your models.py 21 | file, run migrations, and then you can access them 22 | with {{ child.specific.subtitle }} 23 | {% endcomment %} 24 |
25 | {% endfor %} 26 |
27 | {% endblock content %} 28 | -------------------------------------------------------------------------------- /search/views.py: -------------------------------------------------------------------------------- 1 | from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator 2 | from django.template.response import TemplateResponse 3 | 4 | from wagtail.models import Page 5 | from wagtail.search.models import Query 6 | 7 | 8 | def search(request): 9 | search_query = request.GET.get("query", None) 10 | page = request.GET.get("page", 1) 11 | 12 | # Search 13 | if search_query: 14 | search_results = Page.objects.live().search(search_query) 15 | query = Query.get(search_query) 16 | 17 | # Record hit 18 | query.add_hit() 19 | else: 20 | search_results = Page.objects.none() 21 | 22 | # Pagination 23 | paginator = Paginator(search_results, 10) 24 | try: 25 | search_results = paginator.page(page) 26 | except PageNotAnInteger: 27 | search_results = paginator.page(1) 28 | except EmptyPage: 29 | search_results = paginator.page(paginator.num_pages) 30 | 31 | return TemplateResponse( 32 | request, 33 | "search/search.html", 34 | { 35 | "search_query": search_query, 36 | "search_results": search_results, 37 | }, 38 | ) 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /blog/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-29 18: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', '0069_log_entry_jsonfield'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='BlogIndexPage', 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 | migrations.CreateModel( 27 | name='BlogPostPage', 28 | fields=[ 29 | ('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')), 30 | ], 31 | options={ 32 | 'abstract': False, 33 | }, 34 | bases=('wagtailcore.page',), 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /products/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-29 19:11 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', '0069_log_entry_jsonfield'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='ProductDetailPage', 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 | migrations.CreateModel( 27 | name='ProductIndexPage', 28 | fields=[ 29 | ('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')), 30 | ], 31 | options={ 32 | 'abstract': False, 33 | }, 34 | bases=('wagtailcore.page',), 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /mystore/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.urls import include, path 3 | from django.contrib import admin 4 | 5 | from wagtail.admin import urls as wagtailadmin_urls 6 | from wagtail import urls as wagtail_urls 7 | from wagtail.documents import urls as wagtaildocs_urls 8 | 9 | from search import views as search_views 10 | 11 | urlpatterns = [ 12 | path("django-admin/", admin.site.urls), 13 | path("admin/", include(wagtailadmin_urls)), 14 | path("documents/", include(wagtaildocs_urls)), 15 | path("search/", search_views.search, name="search"), 16 | ] 17 | 18 | 19 | if settings.DEBUG: 20 | from django.conf.urls.static import static 21 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 22 | 23 | # Serve static and media files from development server 24 | urlpatterns += staticfiles_urlpatterns() 25 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 26 | 27 | urlpatterns = urlpatterns + [ 28 | # For anything not caught by a more specific rule above, hand over to 29 | # Wagtail's page serving mechanism. This should be the last pattern in 30 | # the list: 31 | path("", include(wagtail_urls)), 32 | # Alternatively, if you want Wagtail pages to be served from a subpath 33 | # of your site, rather than the site root: 34 | # path("pages/", include(wagtail_urls)), 35 | ] 36 | -------------------------------------------------------------------------------- /products/migrations/0002_productimages.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-29 19:17 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import modelcluster.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('wagtailimages', '0024_index_image_file_hash'), 12 | ('products', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='ProductImages', 18 | fields=[ 19 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('sort_order', models.IntegerField(blank=True, editable=False, null=True)), 21 | ('alt_text', models.CharField(default='', max_length=100)), 22 | ('short_description', models.CharField(blank=True, max_length=255)), 23 | ('image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image')), 24 | ('page', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='product_images', to='products.productdetailpage')), 25 | ], 26 | options={ 27 | 'ordering': ['sort_order'], 28 | 'abstract': False, 29 | }, 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /home/migrations/0002_create_homepage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.db import migrations 3 | 4 | 5 | def create_homepage(apps, schema_editor): 6 | # Get models 7 | ContentType = apps.get_model("contenttypes.ContentType") 8 | Page = apps.get_model("wagtailcore.Page") 9 | Site = apps.get_model("wagtailcore.Site") 10 | HomePage = apps.get_model("home.HomePage") 11 | 12 | # Delete the default homepage 13 | # If migration is run multiple times, it may have already been deleted 14 | Page.objects.filter(id=2).delete() 15 | 16 | # Create content type for homepage model 17 | homepage_content_type, __ = ContentType.objects.get_or_create( 18 | model="homepage", app_label="home" 19 | ) 20 | 21 | # Create a new homepage 22 | homepage = HomePage.objects.create( 23 | title="Home", 24 | draft_title="Home", 25 | slug="home", 26 | content_type=homepage_content_type, 27 | path="00010001", 28 | depth=2, 29 | numchild=0, 30 | url_path="/home/", 31 | ) 32 | 33 | # Create a site with the new homepage set as the root 34 | Site.objects.create(hostname="localhost", root_page=homepage, is_default_site=True) 35 | 36 | 37 | def remove_homepage(apps, schema_editor): 38 | # Get models 39 | ContentType = apps.get_model("contenttypes.ContentType") 40 | HomePage = apps.get_model("home.HomePage") 41 | 42 | # Delete the default homepage 43 | # Page and Site objects CASCADE 44 | HomePage.objects.filter(slug="home", depth=2).delete() 45 | 46 | # Delete content type for homepage model 47 | ContentType.objects.filter(model="homepage", app_label="home").delete() 48 | 49 | 50 | class Migration(migrations.Migration): 51 | 52 | run_before = [ 53 | ("wagtailcore", "0053_locale_model"), 54 | ] 55 | 56 | dependencies = [ 57 | ("home", "0001_initial"), 58 | ] 59 | 60 | operations = [ 61 | migrations.RunPython(create_homepage, remove_homepage), 62 | ] 63 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Python runtime based on Debian 10 "buster" as a parent image. 2 | FROM python:3.8.1-slim-buster 3 | 4 | # Add user that will be used in the container. 5 | RUN useradd wagtail 6 | 7 | # Port used by this container to serve HTTP. 8 | EXPOSE 8000 9 | 10 | # Set environment variables. 11 | # 1. Force Python stdout and stderr streams to be unbuffered. 12 | # 2. Set PORT variable that is used by Gunicorn. This should match "EXPOSE" 13 | # command. 14 | ENV PYTHONUNBUFFERED=1 \ 15 | PORT=8000 16 | 17 | # Install system packages required by Wagtail and Django. 18 | RUN apt-get update --yes --quiet && apt-get install --yes --quiet --no-install-recommends \ 19 | build-essential \ 20 | libpq-dev \ 21 | libmariadbclient-dev \ 22 | libjpeg62-turbo-dev \ 23 | zlib1g-dev \ 24 | libwebp-dev \ 25 | && rm -rf /var/lib/apt/lists/* 26 | 27 | # Install the application server. 28 | RUN pip install "gunicorn==20.0.4" 29 | 30 | # Install the project requirements. 31 | COPY requirements.txt / 32 | RUN pip install -r /requirements.txt 33 | 34 | # Use /app folder as a directory where the source code is stored. 35 | WORKDIR /app 36 | 37 | # Set this directory to be owned by the "wagtail" user. This Wagtail project 38 | # uses SQLite, the folder needs to be owned by the user that 39 | # will be writing to the database file. 40 | RUN chown wagtail:wagtail /app 41 | 42 | # Copy the source code of the project into the container. 43 | COPY --chown=wagtail:wagtail . . 44 | 45 | # Use user "wagtail" to run the build commands below and the server itself. 46 | USER wagtail 47 | 48 | # Collect static files. 49 | RUN python manage.py collectstatic --noinput --clear 50 | 51 | # Runtime command that executes when "docker run" is called, it does the 52 | # following: 53 | # 1. Migrate the database. 54 | # 2. Start the application server. 55 | # WARNING: 56 | # Migrating database at the same time as starting the server IS NOT THE BEST 57 | # PRACTICE. The database should be migrated manually or using the release 58 | # phase facilities of your hosting platform. This is used only so the 59 | # Wagtail instance can be started with a simple "docker run" command. 60 | CMD set -xe; python manage.py migrate --noinput; gunicorn mystore.wsgi:application 61 | -------------------------------------------------------------------------------- /products/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.core.cache import cache 3 | from django.core.cache.utils import make_template_fragment_key 4 | 5 | from modelcluster.fields import ParentalKey 6 | 7 | from wagtail.admin.panels import FieldPanel, InlinePanel 8 | from wagtail.core.fields import RichTextField, StreamField 9 | from wagtail.core.models import Page, Orderable 10 | from wagtail.search import index 11 | from wagtail.snippets.models import register_snippet 12 | 13 | from products import blocks 14 | 15 | 16 | class ProductIndexPage(Page): 17 | max_count = 1 18 | subpage_types = ['products.ProductDetailPage'] 19 | 20 | 21 | class ProductDetailPage(Page): 22 | parent_page_types = ['products.ProductIndexPage'] 23 | subpage_types = [] 24 | 25 | body = StreamField([ 26 | ("title_and_subtitle", blocks.TitleAndSubtitleBlock()), 27 | ], null=True, blank=True) 28 | category = models.ForeignKey( 29 | 'products.ProductCategory', 30 | null=True, 31 | blank=False, 32 | on_delete=models.SET_NULL, 33 | related_name='+', 34 | ) 35 | 36 | content_panels = Page.content_panels + [ 37 | InlinePanel( 38 | "product_images", 39 | max_num=5, 40 | min_num=1, 41 | label="Product Images" 42 | ), 43 | FieldPanel("body"), 44 | FieldPanel("category"), 45 | ] 46 | 47 | def save(self, *args, **kwargs): 48 | key = make_template_fragment_key("product_page_cache", [self.id]) 49 | cache.delete(key) 50 | return super().save(*args, **kwargs) 51 | 52 | 53 | class ProductImages(Orderable): 54 | page = ParentalKey('products.ProductDetailPage', related_name="product_images") 55 | image = models.ForeignKey( 56 | "wagtailimages.Image", 57 | null=True, 58 | blank=True, 59 | on_delete=models.SET_NULL, 60 | related_name="+" 61 | ) 62 | alt_text = models.CharField(max_length=100, blank=False, default='') 63 | short_description = models.CharField(max_length=255, blank=True) 64 | 65 | panels = [ 66 | FieldPanel("image"), 67 | FieldPanel("alt_text"), 68 | FieldPanel("short_description"), 69 | ] 70 | 71 | 72 | @register_snippet 73 | class ProductCategory(index.Indexed, models.Model): 74 | name = models.CharField(max_length=30) 75 | description = RichTextField(blank=True, features=[]) 76 | slug = models.SlugField(unique=True) 77 | 78 | search_fields = [ 79 | index.SearchField('name', partial_match=True), 80 | ] 81 | 82 | def __str__(self): 83 | return self.name 84 | 85 | class Meta: 86 | verbose_name = "Product category" 87 | verbose_name_plural = "Product categories" 88 | -------------------------------------------------------------------------------- /mystore/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static wagtailcore_tags wagtailuserbar global_navigation %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block title %} 9 | {% if page.seo_title %}{{ page.seo_title }}{% else %}{{ page.title }}{% endif %} 10 | {% endblock %} 11 | {% block title_suffix %} 12 | {% wagtail_site as current_site %} 13 | {% if current_site and current_site.site_name %}- {{ current_site.site_name }}{% endif %} 14 | {% endblock %} 15 | 16 | 17 | 18 | 19 | {# Global stylesheets #} 20 | 21 | 22 | {% block extra_css %} 23 | {# Override this in templates to add extra stylesheets #} 24 | {% endblock %} 25 | 26 | 27 | 28 | 29 | 30 | {% wagtailuserbar %} 31 | 32 | 48 | 49 | {% block hero %}{% endblock %} 50 | 51 |
52 | {% block content %}{% endblock %} 53 |
54 | 55 | {% block footer %} 56 | 71 | {% endblock %} 72 | 73 | {# Global javascript #} 74 | 75 | 76 | {% block extra_js %} 77 | {# Override this in templates to add extra javascript #} 78 | {% endblock %} 79 | 80 | 81 | -------------------------------------------------------------------------------- /home/static/css/welcome_page.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | } 4 | 5 | *, 6 | *:before, 7 | *:after { 8 | box-sizing: inherit; 9 | } 10 | 11 | body { 12 | max-width: 960px; 13 | min-height: 100vh; 14 | margin: 0 auto; 15 | padding: 0 15px; 16 | color: #231f20; 17 | font-family: 'Helvetica Neue', 'Segoe UI', Arial, sans-serif; 18 | line-height: 1.25; 19 | } 20 | 21 | a { 22 | background-color: transparent; 23 | color: #308282; 24 | text-decoration: underline; 25 | } 26 | 27 | a:hover { 28 | color: #ea1b10; 29 | } 30 | 31 | h1, 32 | h2, 33 | h3, 34 | h4, 35 | h5, 36 | p, 37 | ul { 38 | padding: 0; 39 | margin: 0; 40 | font-weight: 400; 41 | } 42 | 43 | svg:not(:root) { 44 | overflow: hidden; 45 | } 46 | 47 | .header { 48 | display: flex; 49 | justify-content: space-between; 50 | align-items: center; 51 | padding-top: 20px; 52 | padding-bottom: 10px; 53 | border-bottom: 1px solid #e6e6e6; 54 | } 55 | 56 | .logo { 57 | width: 150px; 58 | margin-inline-end: 20px; 59 | } 60 | 61 | .logo a { 62 | display: block; 63 | } 64 | 65 | .figure-logo { 66 | max-width: 150px; 67 | max-height: 55.1px; 68 | } 69 | 70 | .release-notes { 71 | font-size: 14px; 72 | } 73 | 74 | .main { 75 | padding: 40px 0; 76 | margin: 0 auto; 77 | text-align: center; 78 | } 79 | 80 | .figure-space { 81 | max-width: 265px; 82 | } 83 | 84 | @keyframes pos { 85 | 0%, 100% { 86 | transform: rotate(-6deg); 87 | } 88 | 50% { 89 | transform: rotate(6deg); 90 | } 91 | } 92 | 93 | .egg { 94 | fill: #43b1b0; 95 | animation: pos 3s ease infinite; 96 | transform: translateY(50px); 97 | transform-origin: 50% 80%; 98 | } 99 | 100 | .main-text { 101 | max-width: 400px; 102 | margin: 5px auto; 103 | } 104 | 105 | .main-text h1 { 106 | font-size: 22px; 107 | } 108 | 109 | .main-text p { 110 | margin: 15px auto 0; 111 | } 112 | 113 | .footer { 114 | display: flex; 115 | flex-wrap: wrap; 116 | justify-content: space-between; 117 | border-top: 1px solid #e6e6e6; 118 | padding: 10px; 119 | } 120 | 121 | .option { 122 | display: block; 123 | padding: 10px 10px 10px 34px; 124 | position: relative; 125 | text-decoration: none; 126 | } 127 | 128 | .option svg { 129 | width: 24px; 130 | height: 24px; 131 | fill: gray; 132 | border: 1px solid #d9d9d9; 133 | padding: 5px; 134 | border-radius: 100%; 135 | top: 10px; 136 | /* Remove once we drop support for Safari 13. */ 137 | /* stylelint-disable-next-line property-disallowed-list */ 138 | left: 0; 139 | inset-inline-start: 0; 140 | position: absolute; 141 | } 142 | 143 | .option h2 { 144 | font-size: 19px; 145 | text-decoration: underline; 146 | } 147 | 148 | .option p { 149 | padding-top: 3px; 150 | color: #231f20; 151 | font-size: 15px; 152 | font-weight: 300; 153 | } 154 | 155 | @media (max-width: 996px) { 156 | body { 157 | max-width: 780px; 158 | } 159 | } 160 | 161 | @media (max-width: 767px) { 162 | .option { 163 | flex: 0 0 50%; 164 | } 165 | } 166 | 167 | @media (max-width: 599px) { 168 | .main { 169 | padding: 20px 0; 170 | } 171 | 172 | .figure-space { 173 | max-width: 200px; 174 | } 175 | 176 | .footer { 177 | display: block; 178 | width: 300px; 179 | margin: 0 auto; 180 | } 181 | } 182 | 183 | @media (max-width: 360px) { 184 | .header-link { 185 | max-width: 100px; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /mystore/settings/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for mystore project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.0.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.0/ref/settings/ 11 | """ 12 | 13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 14 | import os 15 | 16 | PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | BASE_DIR = os.path.dirname(PROJECT_DIR) 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ 22 | 23 | 24 | # Application definition 25 | 26 | INSTALLED_APPS = [ 27 | "home", 28 | "search", 29 | "blog", 30 | "products", 31 | "wagtail.contrib.forms", 32 | "wagtail.contrib.redirects", 33 | "wagtail.embeds", 34 | "wagtail.sites", 35 | "wagtail.users", 36 | "wagtail.snippets", 37 | "wagtail.documents", 38 | "wagtail.images", 39 | "wagtail.search", 40 | "wagtail.admin", 41 | "wagtail", 42 | "modelcluster", 43 | "taggit", 44 | "django.contrib.admin", 45 | "django.contrib.auth", 46 | "django.contrib.contenttypes", 47 | "django.contrib.sessions", 48 | "django.contrib.messages", 49 | "django.contrib.staticfiles", 50 | ] 51 | 52 | MIDDLEWARE = [ 53 | "django.contrib.sessions.middleware.SessionMiddleware", 54 | "django.middleware.common.CommonMiddleware", 55 | "django.middleware.csrf.CsrfViewMiddleware", 56 | "django.contrib.auth.middleware.AuthenticationMiddleware", 57 | "django.contrib.messages.middleware.MessageMiddleware", 58 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 59 | "django.middleware.security.SecurityMiddleware", 60 | "wagtail.contrib.redirects.middleware.RedirectMiddleware", 61 | ] 62 | 63 | ROOT_URLCONF = "mystore.urls" 64 | 65 | TEMPLATES = [ 66 | { 67 | "BACKEND": "django.template.backends.django.DjangoTemplates", 68 | "DIRS": [ 69 | os.path.join(PROJECT_DIR, "templates"), 70 | ], 71 | "APP_DIRS": True, 72 | "OPTIONS": { 73 | "context_processors": [ 74 | "django.template.context_processors.debug", 75 | "django.template.context_processors.request", 76 | "django.contrib.auth.context_processors.auth", 77 | "django.contrib.messages.context_processors.messages", 78 | ], 79 | }, 80 | }, 81 | ] 82 | 83 | WSGI_APPLICATION = "mystore.wsgi.application" 84 | 85 | 86 | # Database 87 | # https://docs.djangoproject.com/en/4.0/ref/settings/#databases 88 | 89 | DATABASES = { 90 | "default": { 91 | "ENGINE": "django.db.backends.sqlite3", 92 | "NAME": os.path.join(BASE_DIR, "db.sqlite3"), 93 | } 94 | } 95 | 96 | 97 | # Password validation 98 | # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators 99 | 100 | AUTH_PASSWORD_VALIDATORS = [ 101 | { 102 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 103 | }, 104 | { 105 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 106 | }, 107 | { 108 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 109 | }, 110 | { 111 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 112 | }, 113 | ] 114 | 115 | 116 | # Internationalization 117 | # https://docs.djangoproject.com/en/4.0/topics/i18n/ 118 | 119 | LANGUAGE_CODE = "en-us" 120 | 121 | TIME_ZONE = "UTC" 122 | 123 | USE_I18N = True 124 | 125 | USE_L10N = True 126 | 127 | USE_TZ = True 128 | 129 | 130 | # Static files (CSS, JavaScript, Images) 131 | # https://docs.djangoproject.com/en/4.0/howto/static-files/ 132 | 133 | STATICFILES_FINDERS = [ 134 | "django.contrib.staticfiles.finders.FileSystemFinder", 135 | "django.contrib.staticfiles.finders.AppDirectoriesFinder", 136 | ] 137 | 138 | STATICFILES_DIRS = [ 139 | os.path.join(PROJECT_DIR, "static"), 140 | ] 141 | 142 | # ManifestStaticFilesStorage is recommended in production, to prevent outdated 143 | # JavaScript / CSS assets being served from cache (e.g. after a Wagtail upgrade). 144 | # See https://docs.djangoproject.com/en/4.0/ref/contrib/staticfiles/#manifeststaticfilesstorage 145 | STATICFILES_STORAGE = "django.contrib.staticfiles.storage.ManifestStaticFilesStorage" 146 | 147 | STATIC_ROOT = os.path.join(BASE_DIR, "static") 148 | STATIC_URL = "/static/" 149 | 150 | MEDIA_ROOT = os.path.join(BASE_DIR, "media") 151 | MEDIA_URL = "/media/" 152 | 153 | 154 | # Wagtail settings 155 | 156 | WAGTAIL_SITE_NAME = "mystore" 157 | 158 | # Search 159 | # https://docs.wagtail.org/en/stable/topics/search/backends.html 160 | WAGTAILSEARCH_BACKENDS = { 161 | "default": { 162 | "BACKEND": "wagtail.search.backends.database", 163 | } 164 | } 165 | 166 | # Base URL to use when referring to full URLs within the Wagtail admin backend - 167 | # e.g. in notification emails. Don't include '/admin' or a trailing slash 168 | WAGTAILADMIN_BASE_URL = "http://example.com" 169 | 170 | cwd = os.getcwd() 171 | 172 | CACHES = { 173 | "default": { 174 | "BACKEND": "django.core.cache.backends.filebased.FileBasedCache", 175 | "LOCATION": f"{cwd}/.cache" 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /home/templates/home/welcome_page.html: -------------------------------------------------------------------------------- 1 | {% load i18n wagtailcore_tags %} 2 | 3 |
4 | 9 | 17 |
18 |
19 |
20 | 24 |
25 |
26 |

{% trans "Welcome to your new Wagtail site!" %}

27 |

{% trans 'Please feel free to join our community on Slack, or get started with one of the links below.' %}

28 |
29 |
30 | 53 | --------------------------------------------------------------------------------