├── django ├── .gitattributes ├── website │ ├── wagtail_vue │ │ ├── apps │ │ │ ├── __init__.py │ │ │ └── pages │ │ │ │ ├── __init__.py │ │ │ │ ├── migrations │ │ │ │ ├── __init__.py │ │ │ │ ├── 0004_homepage_content.py │ │ │ │ ├── 0001_initial.py │ │ │ │ ├── 0003_flexpage.py │ │ │ │ ├── 0002_auto_20181106_0418.py │ │ │ │ ├── 0005_auto_20181111_0459.py │ │ │ │ ├── 0006_auto_20181111_0506.py │ │ │ │ ├── 0007_flexpage_content.py │ │ │ │ ├── 0008_auto_20181113_1657.py │ │ │ │ ├── 0009_auto_20181113_1721.py │ │ │ │ └── 0010_auto_20181114_0847.py │ │ │ │ ├── tests.py │ │ │ │ ├── admin.py │ │ │ │ ├── views.py │ │ │ │ ├── apps.py │ │ │ │ ├── models.py │ │ │ │ └── streamfields.py │ │ ├── static │ │ │ ├── css │ │ │ │ └── .gitkeep │ │ │ ├── js │ │ │ │ └── .gitkeep │ │ │ └── images │ │ │ │ └── .gitkeep │ │ ├── wagtail_vue │ │ │ ├── __init__.py │ │ │ ├── settings │ │ │ │ ├── __init__.py │ │ │ │ ├── production.py │ │ │ │ ├── database.sqlite3 │ │ │ │ ├── dev.py │ │ │ │ └── settings_base.py │ │ │ ├── api.py │ │ │ ├── urls.py │ │ │ └── wsgi.py │ │ ├── media │ │ │ ├── images │ │ │ │ ├── skateboard.original.jpg │ │ │ │ ├── skateboard.max-165x165.jpg │ │ │ │ ├── skateboard.max-800x600.jpg │ │ │ │ ├── hidethepainharold.original.jpg │ │ │ │ ├── hidethepainharold.max-165x165.jpg │ │ │ │ ├── hidethepainharold.max-800x600.jpg │ │ │ │ ├── stefan-kunze-26932.max-165x165.jpg │ │ │ │ ├── earth-608366-unsplash.max-165x165.jpg │ │ │ │ ├── adam-krowitz-389725-unsplash.max-165x165.jpg │ │ │ │ └── hidethepainharold.2e16d0ba.fill-100x100.jpg │ │ │ ├── original_images │ │ │ │ ├── skateboard.jpeg │ │ │ │ ├── hidethepainharold.jpg │ │ │ │ ├── stefan-kunze-26932.jpg │ │ │ │ ├── earth-608366-unsplash.jpg │ │ │ │ └── adam-krowitz-389725-unsplash.jpg │ │ │ └── documents │ │ │ │ └── Wagtail__Vue.js_Slides_Vue.odp │ │ ├── manage.py │ │ └── templates │ │ │ └── base.html │ ├── requirements │ │ ├── production.txt │ │ ├── req_base.txt │ │ └── dev.txt │ └── requirements.txt ├── .flake8 ├── .isort.cfg ├── README.md ├── bash_aliases ├── .editorconfig └── Dockerfile ├── vue ├── .browserslistrc ├── vue.config.js ├── babel.config.js ├── postcss.config.js ├── public │ ├── favicon.ico │ ├── loading.gif │ └── index.html ├── src │ ├── assets │ │ └── logo.png │ ├── views │ │ ├── NotFound.vue │ │ ├── FlexPage.vue │ │ ├── HomePage.vue │ │ └── WagtailPageHandler.vue │ ├── router.js │ ├── components │ │ ├── streamfields │ │ │ ├── RichTextBlock.vue │ │ │ ├── ContentBlock.vue │ │ │ ├── ImageBlock.vue │ │ │ ├── ImageGalleryBlock.vue │ │ │ ├── CallToActionBlock.vue │ │ │ └── ButtonBlock.vue │ │ ├── WagtailPage.vue │ │ ├── WagtailDocument.vue │ │ ├── WagtailImage.vue │ │ ├── Streamfield.vue │ │ └── SiteHeader.vue │ ├── main.js │ ├── App.vue │ └── api.js ├── .gitignore ├── README.md ├── .eslintrc.js ├── Dockerfile ├── NOTES.md ├── package.json ├── TODO.md └── SLIDES.md ├── .gitignore ├── docker-compose.yml ├── README.md ├── Makefile └── LICENSE /django/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/__init__.py: -------------------------------------------------------------------------------- 1 | # yup -------------------------------------------------------------------------------- /django/website/wagtail_vue/static/css/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/static/js/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/static/images/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/wagtail_vue/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/wagtail_vue/settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /vue/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | lintOnSave: false 3 | } 4 | -------------------------------------------------------------------------------- /vue/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /vue/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /vue/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/vue/public/favicon.ico -------------------------------------------------------------------------------- /vue/public/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/vue/public/loading.gif -------------------------------------------------------------------------------- /vue/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/vue/src/assets/logo.png -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /django/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = 3 | migrations, 4 | __init__.py, 5 | settings, 6 | max-line-length = 119 7 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/wagtail_vue/settings/production.py: -------------------------------------------------------------------------------- 1 | """Overwrite settings with production settings when going live.""" 2 | -------------------------------------------------------------------------------- /django/website/requirements/production.txt: -------------------------------------------------------------------------------- 1 | # production that isn't in development. ie. monitoring services. 2 | -r req_base.txt 3 | 4 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PagesConfig(AppConfig): 5 | name = 'pages' 6 | -------------------------------------------------------------------------------- /django/website/requirements/req_base.txt: -------------------------------------------------------------------------------- 1 | bpython 2 | Django==2.1 3 | django-adminactions==1.* 4 | django-extensions==1.* 5 | Pillow==4.* 6 | wagtail==2.3 7 | django-cors-headers==2.4.* 8 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/media/images/skateboard.original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/media/images/skateboard.original.jpg -------------------------------------------------------------------------------- /django/website/wagtail_vue/media/original_images/skateboard.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/media/original_images/skateboard.jpeg -------------------------------------------------------------------------------- /django/website/wagtail_vue/wagtail_vue/settings/database.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/wagtail_vue/settings/database.sqlite3 -------------------------------------------------------------------------------- /django/website/wagtail_vue/media/images/skateboard.max-165x165.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/media/images/skateboard.max-165x165.jpg -------------------------------------------------------------------------------- /django/website/wagtail_vue/media/images/skateboard.max-800x600.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/media/images/skateboard.max-800x600.jpg -------------------------------------------------------------------------------- /django/website/requirements.txt: -------------------------------------------------------------------------------- 1 | # This file is here because many Platforms as a Service look for 2 | # requirements.txt in the root directory of a project. 3 | -r requirements/production.txt 4 | -------------------------------------------------------------------------------- /django/website/requirements/dev.txt: -------------------------------------------------------------------------------- 1 | # Local development dependencies go here 2 | -r req_base.txt 3 | django-debug-toolbar==1.9.1 4 | django-debug-toolbar-template-profiler 5 | Werkzeug==0.10.* 6 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/media/images/hidethepainharold.original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/media/images/hidethepainharold.original.jpg -------------------------------------------------------------------------------- /django/website/wagtail_vue/media/original_images/hidethepainharold.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/media/original_images/hidethepainharold.jpg -------------------------------------------------------------------------------- /django/website/wagtail_vue/media/original_images/stefan-kunze-26932.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/media/original_images/stefan-kunze-26932.jpg -------------------------------------------------------------------------------- /django/website/wagtail_vue/media/documents/Wagtail__Vue.js_Slides_Vue.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/media/documents/Wagtail__Vue.js_Slides_Vue.odp -------------------------------------------------------------------------------- /django/website/wagtail_vue/media/images/hidethepainharold.max-165x165.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/media/images/hidethepainharold.max-165x165.jpg -------------------------------------------------------------------------------- /django/website/wagtail_vue/media/images/hidethepainharold.max-800x600.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/media/images/hidethepainharold.max-800x600.jpg -------------------------------------------------------------------------------- /django/website/wagtail_vue/media/images/stefan-kunze-26932.max-165x165.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/media/images/stefan-kunze-26932.max-165x165.jpg -------------------------------------------------------------------------------- /django/website/wagtail_vue/media/original_images/earth-608366-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/media/original_images/earth-608366-unsplash.jpg -------------------------------------------------------------------------------- /django/website/wagtail_vue/media/images/earth-608366-unsplash.max-165x165.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/media/images/earth-608366-unsplash.max-165x165.jpg -------------------------------------------------------------------------------- /django/website/wagtail_vue/media/original_images/adam-krowitz-389725-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/media/original_images/adam-krowitz-389725-unsplash.jpg -------------------------------------------------------------------------------- /django/website/wagtail_vue/media/images/adam-krowitz-389725-unsplash.max-165x165.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/media/images/adam-krowitz-389725-unsplash.max-165x165.jpg -------------------------------------------------------------------------------- /django/website/wagtail_vue/media/images/hidethepainharold.2e16d0ba.fill-100x100.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyshka/wagtail-vue-talk/HEAD/django/website/wagtail_vue/media/images/hidethepainharold.2e16d0ba.fill-100x100.jpg -------------------------------------------------------------------------------- /django/.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | line_length=119 3 | indent=' ' 4 | multi_line_output=5 5 | known_django=django 6 | sections=FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER 7 | skip=migrations 8 | include_trailing_comma=True 9 | -------------------------------------------------------------------------------- /django/README.md: -------------------------------------------------------------------------------- 1 | # Wagtail Vue Talk: backend setup 2 | > Django + Wagtail 3 | 4 | ## Requires Docker 5 | 6 | ## Installation 7 | 1. `make build` 8 | 2. `make up` 9 | 3. `make enter` 10 | 4. `django-admin.py runserver 0.0.0.0:8000` 11 | 5. Go to `http://localhost:8000/admin/` 12 | * login is `demo` / `demo123` 13 | -------------------------------------------------------------------------------- /vue/src/views/NotFound.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /vue/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /vue/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue" 2 | import Router from "vue-router" 3 | 4 | import WagtailPageHandler from "./views/WagtailPageHandler.vue" 5 | 6 | Vue.use(Router) 7 | 8 | export default new Router({ 9 | mode: "history", 10 | base: process.env.BASE_URL, 11 | routes: [ 12 | { 13 | path: "*", 14 | component: WagtailPageHandler, 15 | }, 16 | ], 17 | }) 18 | -------------------------------------------------------------------------------- /vue/README.md: -------------------------------------------------------------------------------- 1 | # Development 2 | ``` 3 | # install dependencies 4 | npm install 5 | 6 | # start local dev server 7 | npm run serve 8 | ``` 9 | 10 | ## Docker 11 | ``` 12 | # build image and start container 13 | make up 14 | 15 | # enter container 16 | make enter 17 | 18 | # start local dev server (inside container) 19 | npm run serve 20 | 21 | # stop and remove container 22 | make clean 23 | ``` 24 | -------------------------------------------------------------------------------- /vue/src/components/streamfields/RichTextBlock.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | 21 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django’s command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | if __name__ == '__main__': 7 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wagtail_vue.settings.production') # noqa: E501 8 | 9 | from django.core.management import execute_from_command_line 10 | 11 | execute_from_command_line(sys.argv) 12 | -------------------------------------------------------------------------------- /django/bash_aliases: -------------------------------------------------------------------------------- 1 | # Django Aliases 2 | alias dj="django-admin.py" 3 | alias djr="django-admin.py runserver 0.0.0.0:8000" 4 | 5 | # Print Message 6 | # Pass format specifies to printf which tells printf to take each argument it 7 | # gets and print it, followed by a newline. 8 | printf "%s\n" \ 9 | "Here are some common aliases for our stack:" \ 10 | 'dj="django-admin.py"' \ 11 | 'djr="django-admin.py runserver 0.0.0.0:8000"' 12 | -------------------------------------------------------------------------------- /vue/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | '@vue/prettier' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /vue/src/components/streamfields/ContentBlock.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 18 | -------------------------------------------------------------------------------- /vue/src/components/streamfields/ImageBlock.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | *.py[cod] 3 | *.egg* 4 | local_dev.py 5 | .mypy_cache 6 | 7 | # Temporary files 8 | *.swp 9 | *.swo 10 | *.tmp 11 | *~ 12 | 13 | # Tags file 14 | /tags 15 | 16 | # Packages 17 | *.7z 18 | *.dmg 19 | *.gz 20 | *.iso 21 | *.jar 22 | *.rar 23 | *.tar 24 | *.zip 25 | 26 | # Logs and databases 27 | *.log 28 | *.db 29 | 30 | # OS generated files 31 | .DS_Store* 32 | thumbs.db 33 | Thumbs.db 34 | Desktop.ini 35 | 36 | # Front-end files 37 | node_modules 38 | 39 | local_production.py 40 | local_dev.py 41 | 42 | .vimcache 43 | .vscode 44 | -------------------------------------------------------------------------------- /vue/src/components/WagtailPage.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 24 | -------------------------------------------------------------------------------- /vue/src/views/FlexPage.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /vue/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue" 2 | import VueMeta from "vue-meta" 3 | import VueLazyload from 'vue-lazyload' 4 | 5 | import App from "./App.vue" 6 | import router from "./router" 7 | import Streamfield from "@/components/Streamfield.vue" 8 | 9 | Vue.config.productionTip = false 10 | 11 | // Vue addons 12 | Vue.use(VueMeta) 13 | Vue.use(VueLazyload, { 14 | loading: '/loading.gif', 15 | }) 16 | 17 | // Register streamfield as global component 18 | Vue.component("streamfield", Streamfield) 19 | 20 | new Vue({ 21 | router, 22 | render: h => h(App), 23 | }).$mount("#app") 24 | -------------------------------------------------------------------------------- /django/.editorconfig: -------------------------------------------------------------------------------- 1 | # Defines the coding style for different editors and IDEs. 2 | # http://editorconfig.org 3 | 4 | # top-most EditorConfig file 5 | root = true 6 | 7 | # Rules for source code. 8 | [*] 9 | charset = utf-8 10 | end_of_line = lf 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | # Rules for Python code. 17 | [*.py] 18 | indent_size = 4 19 | 20 | # Rules for tool configuration. 21 | [{package.json,*.yml, *.yaml}] 22 | indent_size = 2 23 | 24 | # Rules for markdown documents. 25 | [*.md] 26 | trim_trailing_whitespace = false 27 | -------------------------------------------------------------------------------- /vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | 19 | 29 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/migrations/0004_homepage_content.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1 on 2018-11-11 04:38 2 | 3 | from django.db import migrations 4 | import pages.streamfields 5 | import wagtail.core.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pages', '0003_flexpage'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='homepage', 17 | name='content', 18 | field=wagtail.core.fields.StreamField([('richtext', pages.streamfields.RichTextBlock())], null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /vue/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Wagtail + Vue.js 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /vue/src/components/WagtailDocument.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 26 | -------------------------------------------------------------------------------- /vue/src/components/WagtailImage.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 27 | -------------------------------------------------------------------------------- /vue/src/api.js: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | 3 | // TODO: set hostname + port dynamically 4 | 5 | export function getWagtailPage(id) { 6 | return axios.get(`//localhost:8000/api/v2/pages/${id}/`) 7 | } 8 | 9 | export function getWagtailPageByPath(path) { 10 | return axios.get(`//localhost:8000/api/v2/pages/find/?html_path=${path}`) 11 | } 12 | 13 | export function getWagtailPagesInMenu() { 14 | return axios.get( 15 | "//localhost:8000/api/v2/pages/?show_in_menus=true&fields=_,html_url,title" 16 | ) 17 | } 18 | 19 | export function getWagtailImage(id) { 20 | return axios.get(`//localhost:8000/api/v2/images/${id}/`) 21 | } 22 | 23 | export function getWagtailDocument(id) { 24 | return axios.get(`//localhost:8000/api/v2/documents/${id}/`) 25 | } 26 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | backend: 4 | network_mode: "bridge" 5 | build: ./django 6 | container_name: wagtail_vue_backend 7 | image: wagtail_vue:backend 8 | ports: 9 | - "8000:8000" 10 | volumes: 11 | - ${WINDIR}./django:/app 12 | stdin_open: true 13 | command: /bin/sh -c "while true; do echo hi; sleep 1; done;" 14 | frontend: 15 | network_mode: "bridge" 16 | links: 17 | - backend 18 | build: ./vue 19 | container_name: wagtail_vue_frontend 20 | image: wagtail_vue:frontend 21 | ports: 22 | - "8080:8080" 23 | volumes: 24 | - ./vue:/app 25 | - /app/node_modules 26 | stdin_open: true 27 | command: /bin/sh -c "while true; do echo hi; sleep 1; done;" 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wagtail-vue-talk 2 | How to vue wagtail in a better light 3 | 4 | 5 | ## Development with Docker 6 | 7 | The Django server will run on port `8000`, and the Node.js server compiling the Vue.js app will run on port `8080`. 8 | 9 | To access the Wagtail admin go to http://localhost:8000/admin/, login is `demo` / `demo123`. 10 | 11 | The Vue.js app is expecting the Wagtail API to be avaialable at `localhost:8000/api/v2/`. 12 | 13 | ``` 14 | # build image and start containers 15 | make up 16 | 17 | # enter backend (python) container 18 | make enter 19 | 20 | # start django server (inside container) 21 | django-admin.py runserver 0.0.0.0:8000 22 | 23 | # enter frontend (node.js) container 24 | make enter_fe 25 | 26 | # start node server (inside container) 27 | npm run serve 28 | 29 | # stop and remove containers 30 | make clean 31 | ``` 32 | -------------------------------------------------------------------------------- /vue/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10-alpine 2 | MAINTAINER Bryan Hyshka 3 | 4 | # Set a term for terminal inside the container, can't clear without it 5 | ENV TERM xterm-256color 6 | 7 | # Prefix path with global node_modules folder 8 | # This allows npm package binaries to be available everywhere 9 | ENV PATH /app/node_modules/.bin:$PATH 10 | 11 | # Update, install, and cleanup 12 | # RUN apk update \ 13 | # && apk add git \ 14 | # && rm -rf /var/cache/apk/* 15 | 16 | # Add the project requirements 17 | # This will add the package.json and package-lock.json if it exists 18 | ADD package*.json /app/ 19 | 20 | # Install the requirements 21 | RUN /bin/sh -c 'cd /app && npm install && npm cache clean --force' 22 | 23 | # The code should be symlinked to this directory 24 | WORKDIR /app 25 | 26 | # Expose the 8080 port 27 | EXPOSE 8080 28 | -------------------------------------------------------------------------------- /vue/src/components/streamfields/ImageGalleryBlock.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 26 | 27 | 37 | -------------------------------------------------------------------------------- /vue/src/components/streamfields/CallToActionBlock.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 31 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/wagtail_vue/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Urls for app.""" 3 | 4 | from wagtail.api.v2.endpoints import PagesAPIEndpoint 5 | from wagtail.api.v2.router import WagtailAPIRouter 6 | from wagtail.images.api.v2.endpoints import ImagesAPIEndpoint 7 | from wagtail.documents.api.v2.endpoints import DocumentsAPIEndpoint 8 | 9 | # Create the router. "wagtailapi" is the URL namespace 10 | api_router = WagtailAPIRouter('wagtailapi') 11 | 12 | # Add the three endpoints using the "register_endpoint" method. 13 | # The first parameter is the name of the endpoint (eg. pages, images). This 14 | # is used in the URL of the endpoint 15 | # The second parameter is the endpoint class that handles the requests 16 | api_router.register_endpoint('pages', PagesAPIEndpoint) 17 | api_router.register_endpoint('images', ImagesAPIEndpoint) 18 | api_router.register_endpoint('documents', DocumentsAPIEndpoint) 19 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1 on 2018-11-06 04:04 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', '0040_page_draft_title'), 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 | 'verbose_name_plural': 'Home Pages', 23 | 'verbose_name': 'Home Page', 24 | }, 25 | bases=('wagtailcore.page',), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/migrations/0003_flexpage.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1 on 2018-11-10 16:51 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', '0040_page_draft_title'), 11 | ('pages', '0002_auto_20181106_0418'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='FlexPage', 17 | fields=[ 18 | ('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')), 19 | ], 20 | options={ 21 | 'verbose_name_plural': 'Flex Pages', 22 | 'verbose_name': 'Flex Page', 23 | }, 24 | bases=('wagtailcore.page',), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/migrations/0002_auto_20181106_0418.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1 on 2018-11-06 04:18 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', '0021_image_file_hash'), 11 | ('pages', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='homepage', 17 | name='banner_image', 18 | field=models.ForeignKey(help_text='An optional banner image', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.Image'), 19 | ), 20 | migrations.AddField( 21 | model_name='homepage', 22 | name='banner_subtitle', 23 | field=models.CharField(blank=True, help_text='An optional banner subtitle', max_length=50, null=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /vue/NOTES.md: -------------------------------------------------------------------------------- 1 | # Wagtail + Vue.js Notes 2 | 3 | ## Intro 4 | - Goals 5 | - Lay some foundation for using Wagtail as a headless CMS 6 | - Learn more about the inner workings of Wagtail and Vue.js 7 | - Vue is a javascript framework commonly used for building user interfaces and single page applications. 8 | 9 | ## Flavor 10 | - I'm not stopping here 11 | - How can we improve this 12 | - Library agnostic, should be able to reproduce results in any framework (React, etc.) 13 | 14 | ## Scaffolding with `vue-cli` 15 | ``` 16 | # Install vue-cli 17 | npm install -g @vue/cli 18 | 19 | # Create new project in folder "vue" 20 | vue create vue 21 | 22 | # Options: 23 | # manually select features: Babel, Router, Linter / Formatter 24 | # user history mode for router (yes) 25 | # eslint + prettier 26 | # disable lint on save, enable lint + fix on commit 27 | # in dedicated config files 28 | # save as a preset (no) 29 | 30 | # Move into project folder 31 | cd vue 32 | 33 | # Start development server 34 | npm run serve 35 | ``` 36 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | 3 | SHELL := /bin/bash 4 | CONTAINERNAME_BACKEND=wagtail_vue_backend 5 | IMAGENAME_BACKEND=wagtail_vue:backend 6 | CONTAINERNAME_FRONTEND=wagtail_vue_frontend 7 | IMAGENAME_FRONTEND=wagtail_vue:frontend 8 | 9 | build: ## Build the Docker images 10 | docker-compose -p wagtail_vue build 11 | 12 | up: build ## Bring the Docker containers up 13 | docker-compose -p wagtail_vue up -d || echo 'Already up!' 14 | 15 | upwin: ## Bring the Docker container up for bash on ubuntu folk 16 | export WINDIR="$(subst /mnt/c,//c,$(CURDIR))/" && make up 17 | 18 | lint: build ## Lint the python code. 19 | docker run -v $(CURDIR)/django:/app $(IMAGENAME_BACKEND) /bin/bash -c 'flake8 website' 20 | 21 | down: ## Stop the backend Docker container 22 | docker-compose -p wagtail_vue stop 23 | 24 | enter: ## Enter backend container 25 | docker exec -it $(CONTAINERNAME_BACKEND) /bin/bash 26 | 27 | enter_fe: ## Enter frontend container 28 | docker exec -it $(CONTAINERNAME_FRONTEND) /bin/sh 29 | 30 | clean: ## Stop and remove all Docker containers 31 | docker-compose down 32 | -------------------------------------------------------------------------------- /vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.18.0", 12 | "lodash": "^4.17.11", 13 | "tachyons": "^4.11.1", 14 | "vue": "^2.5.17", 15 | "vue-meta": "^1.5.5", 16 | "vue-router": "^3.0.1" 17 | }, 18 | "devDependencies": { 19 | "@vue/cli-plugin-babel": "^3.0.5", 20 | "@vue/cli-plugin-eslint": "^3.0.5", 21 | "@vue/cli-service": "^3.0.5", 22 | "@vue/eslint-config-prettier": "^4.0.0", 23 | "eslint": "^5.8.0", 24 | "eslint-plugin-vue": "^5.0.0-0", 25 | "lint-staged": "^7.2.2", 26 | "vue-lazyload": "^1.2.6", 27 | "vue-template-compiler": "^2.5.17" 28 | }, 29 | "gitHooks": { 30 | "pre-commit": "lint-staged" 31 | }, 32 | "lint-staged": { 33 | "*.js": [ 34 | "vue-cli-service lint", 35 | "git add" 36 | ], 37 | "*.vue": [ 38 | "vue-cli-service lint", 39 | "git add" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /django/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | MAINTAINER Kalob Taulien 3 | 4 | # Needed for better experience in container terminal 5 | ENV TERM=xterm-256color 6 | 7 | # Update and install 8 | RUN apt-get update && apt-get install -y \ 9 | git \ 10 | wget \ 11 | # Python, remove 3 for wagtail sites 12 | python3-dev \ 13 | python3-pip 14 | 15 | # Set the encoding to avoid issues with internationalization packages. 16 | ENV LANG C.UTF-8 17 | ENV LC_ALL C.UTF-8 18 | 19 | RUN pip3 install --upgrade pip 20 | 21 | # Add the project requirements 22 | ADD website/requirements /opt/requirements 23 | 24 | # Install the requirements, remove 3 for wagtail 25 | RUN /bin/bash -c 'cd /opt \ 26 | && pip3 install -r requirements/dev.txt' 27 | 28 | # Set the needed variables 29 | ENV PYTHONPATH=/app/website/wagtail_vue:/app/website/wagtail_vue/apps 30 | ENV DJANGO_SETTINGS_MODULE=wagtail_vue.settings.dev 31 | 32 | # change to /app for the working directory, you should mount the local dir volume here 33 | WORKDIR /app 34 | 35 | EXPOSE 8000 36 | 37 | # Add bash aliases 38 | ADD bash_aliases /root/.bash_aliases 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Bryan Hyshka and Kalob Taulien 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 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/migrations/0005_auto_20181111_0459.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1 on 2018-11-11 04:59 2 | 3 | from django.db import migrations 4 | import pages.streamfields 5 | import wagtail.core.blocks 6 | import wagtail.core.fields 7 | import wagtail.images.blocks 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('pages', '0004_homepage_content'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='homepage', 19 | name='content', 20 | field=wagtail.core.fields.StreamField([('richtext', pages.streamfields.RichTextBlock()), ('image', pages.streamfields.ImageBlock()), ('nested_streams', wagtail.core.blocks.StructBlock([('card', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(required=True)), ('content', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'ol', 'ul'], required=False)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('page', wagtail.core.blocks.PageChooserBlock(required=True))])))]))], null=True), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static wagtailuserbar %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {% block page_title %}{% endblock page_title %} 11 | 12 | 13 | {% block extra_meta %}{% endblock extra_meta %} 14 | 15 | 16 | 17 | {% block extra_head %}{% endblock %} 18 | 19 | 20 | {% include "includes/accessibility.html" with header=header_class %} 21 | 22 |
23 | 24 | {% block header %}{% endblock header %} 25 | 26 | {% block content %} 27 | 28 | {% block footer %}{% endblock footer %} 29 |
30 | 31 | {% wagtailuserbar %} 32 | 33 | 34 | {% block extra_js %}{% endblock extra_js %} 35 | 36 | 37 | -------------------------------------------------------------------------------- /vue/src/views/HomePage.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 40 | 41 | 46 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/wagtail_vue/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Urls for app.""" 3 | from django.conf import settings 4 | from django.conf.urls import include, static, url 5 | from django.contrib import admin 6 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 7 | 8 | from wagtail.admin import urls as wagtailadmin_urls 9 | from wagtail.core import urls as wagtail_urls 10 | from wagtail.documents import urls as wagtaildocs_urls 11 | 12 | from .api import api_router 13 | 14 | 15 | urlpatterns = [ 16 | url(r'^django-admin/', admin.site.urls), 17 | 18 | url(r'^api/v2/', api_router.urls), 19 | url(r'^admin/', include(wagtailadmin_urls)), 20 | url(r'^documents/', include(wagtaildocs_urls)), 21 | 22 | # For anything not caught by a more specific rule above, hand over to 23 | # Wagtail's page serving mechanism. This should be the last pattern in 24 | # the list: 25 | url(r'', include(wagtail_urls)), 26 | ] 27 | 28 | if settings.DEBUG: 29 | import debug_toolbar 30 | urlpatterns += staticfiles_urlpatterns() 31 | urlpatterns += static.static( 32 | settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 33 | urlpatterns += ( 34 | url(r'^__debug__/', include(debug_toolbar.urls)), 35 | ) 36 | -------------------------------------------------------------------------------- /vue/src/components/Streamfield.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 35 | 36 | 41 | -------------------------------------------------------------------------------- /vue/src/components/SiteHeader.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 43 | 44 | 49 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/wagtail_vue/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for wagtail_vue project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | from os.path import abspath, dirname 17 | from sys import path 18 | 19 | SITE_ROOT = dirname(dirname(abspath(__file__))) 20 | path.append(SITE_ROOT) 21 | 22 | # This application object is used by any WSGI server configured to use this 23 | # file. This includes Django's development server, if the WSGI_APPLICATION 24 | # setting points here. 25 | from django.core.wsgi import get_wsgi_application # noqa # isort:skip noqa 26 | application = get_wsgi_application() 27 | 28 | # Apply WSGI middleware here. 29 | # from helloworld.wsgi import HelloWorldApplication 30 | # application = HelloWorldApplication(application) 31 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/migrations/0006_auto_20181111_0506.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1 on 2018-11-11 05:06 2 | 3 | from django.db import migrations 4 | import pages.streamfields 5 | import wagtail.core.blocks 6 | import wagtail.core.fields 7 | import wagtail.images.blocks 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('pages', '0005_auto_20181111_0459'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='homepage', 19 | name='content', 20 | field=wagtail.core.fields.StreamField([('richtext', pages.streamfields.RichTextBlock()), ('image', pages.streamfields.ImageBlock()), ('nested_streams', wagtail.core.blocks.StructBlock([('card', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(required=True)), ('content', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'ol', 'ul'], required=False)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('page', wagtail.core.blocks.PageChooserBlock(required=True))])))])), ('carousel', wagtail.core.blocks.StreamBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('quotation', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('author', wagtail.core.blocks.CharBlock())]))]))], null=True), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/migrations/0007_flexpage_content.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1 on 2018-11-11 16:11 2 | 3 | from django.db import migrations 4 | import pages.streamfields 5 | import wagtail.core.blocks 6 | import wagtail.core.fields 7 | import wagtail.images.blocks 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('pages', '0006_auto_20181111_0506'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name='flexpage', 19 | name='content', 20 | field=wagtail.core.fields.StreamField([('richtext', pages.streamfields.RichTextBlock()), ('image', pages.streamfields.ImageBlock()), ('nested_streams', wagtail.core.blocks.StructBlock([('card', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(required=True)), ('content', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'ol', 'ul'], required=False)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('page', wagtail.core.blocks.PageChooserBlock(required=True))])))])), ('carousel', wagtail.core.blocks.StreamBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('quotation', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('author', wagtail.core.blocks.CharBlock())]))]))], null=True), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /vue/TODO.md: -------------------------------------------------------------------------------- 1 | # NOTES 2 | 3 | ## SSR/Prerendering 4 | TODO 5 | - https://ssr.vuejs.org/ 6 | - https://nuxtjs.org/ 7 | - https://github.com/chrisvfritz/prerender-spa-plugin 8 | 9 | ## Noteworthy packages 10 | - vue.js https://vuejs.org 11 | - vue-cli https://cli.vuejs.org/ 12 | - vue-router https://router.vuejs.org/ 13 | - vue-meta https://github.com/declandewet/vue-meta 14 | - axios https://github.com/axios/axios 15 | 16 | ## Meta data 17 | TODO: document 18 | - all code is in WagtailPageHandler 19 | 20 | ## Async components for page views 21 | TODO: document 22 | - all code is in WagtailPageHandler 23 | - loads vue components for page template as-needed 24 | - integreated into webpack so they get split into separate bundles 25 | 26 | ## Page transition 27 | TODO: document 28 | - all code is in WagtailPageHandler 29 | - using vue.js transition 30 | - page views need to be keyed (to route path) and positioned absolute 31 | 32 | ## Lazy load images 33 | TODO: document 34 | - code in main.js and WagtailImage.vue 35 | - using vue-lazyload (https://github.com/hilongjw/vue-lazyload) 36 | 37 | ## Misc 38 | - setup static host for staging 39 | - admin on subdomain 40 | - page preview 41 | - try yarn 42 | 43 | ## Gotchas 44 | - Had to register streamfield component globally 45 | - Can't assign a component dynamically from within vue-router 46 | - Had to manually remove domain + port from URLs from the API to make then a relative path we could use for the router (Improve: move this into the router logic) 47 | -------------------------------------------------------------------------------- /vue/src/components/streamfields/ButtonBlock.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 58 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/wagtail_vue/settings/dev.py: -------------------------------------------------------------------------------- 1 | """Development settings and globals.""" 2 | from .settings_base import * # noqa 3 | 4 | # ======== DEBUG CONFIGURATION 5 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#debug 6 | DEBUG = True 7 | TEMPLATES[0]['OPTIONS']['debug'] = DEBUG # noqa 8 | # ======== END DEBUG CONFIGURATION 9 | 10 | 11 | # ======== EMAIL CONFIGURATION 12 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend 13 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 14 | # ======== END EMAIL CONFIGURATION 15 | 16 | 17 | # ======== DATABASE CONFIGURATION 18 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#databases 19 | DATABASES = { 20 | 'default': { 21 | 'ENGINE': 'django.db.backends.sqlite3', 22 | 'NAME': '/app/website/wagtail_vue/wagtail_vue/settings/database.sqlite3', 23 | } 24 | } 25 | # ======== END DATABASE CONFIGURATION 26 | 27 | 28 | # ======== CACHE CONFIGURATION 29 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#caches 30 | CACHES = { 31 | 'default': { 32 | 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', 33 | } 34 | } 35 | # ======== END CACHE CONFIGURATION 36 | 37 | 38 | # ======== TOOLBAR CONFIGURATION 39 | # See: https://github.com/django-debug-toolbar/django-debug-toolbar#installation # noqa 40 | INSTALLED_APPS += ( # noqa 41 | 'debug_toolbar', 42 | ) 43 | 44 | MIDDLEWARE += ( # noqa 45 | 'debug_toolbar.middleware.DebugToolbarMiddleware', 46 | ) 47 | 48 | 49 | # See: https://github.com/django-debug-toolbar/django-debug-toolbar#installation # noqa 50 | INTERNAL_IPS = ('127.0.0.1', '172.17.0.1',) 51 | 52 | # ======== END TOOLBAR CONFIGURATION 53 | 54 | ALLOWED_HOSTS += [ # noqa 55 | 'localhost', 56 | 'backend', 57 | ] 58 | 59 | CORS_ORIGIN_ALLOW_ALL = True 60 | 61 | try: 62 | from .local_dev import * # noqa 63 | except ImportError: 64 | pass 65 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/migrations/0008_auto_20181113_1657.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1 on 2018-11-13 23:57 2 | 3 | from django.db import migrations 4 | import pages.streamfields 5 | import wagtail.core.blocks 6 | import wagtail.core.fields 7 | import wagtail.images.blocks 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('pages', '0007_flexpage_content'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='flexpage', 19 | name='content', 20 | field=wagtail.core.fields.StreamField([('richtext', pages.streamfields.RichTextBlock()), ('image', pages.streamfields.ImageBlock()), ('nested_streams', wagtail.core.blocks.StructBlock([('card', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(required=True)), ('content', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'ol', 'ul'], required=False)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('page', wagtail.core.blocks.PageChooserBlock(required=True))])))])), ('carousel', wagtail.core.blocks.StreamBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('quotation', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('author', wagtail.core.blocks.CharBlock())]))]))], blank=True, null=True), 21 | ), 22 | migrations.AlterField( 23 | model_name='homepage', 24 | name='content', 25 | field=wagtail.core.fields.StreamField([('richtext', pages.streamfields.RichTextBlock()), ('image', pages.streamfields.ImageBlock()), ('nested_streams', wagtail.core.blocks.StructBlock([('card', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(required=True)), ('content', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'ol', 'ul'], required=False)), ('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('page', wagtail.core.blocks.PageChooserBlock(required=True))])))])), ('carousel', wagtail.core.blocks.StreamBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('quotation', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.TextBlock()), ('author', wagtail.core.blocks.CharBlock())]))]))], blank=True, null=True), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Page models.""" 3 | from django.db import models 4 | from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel 5 | from wagtail.images.edit_handlers import ImageChooserPanel 6 | from wagtail.core.models import Page 7 | from wagtail.api import APIField 8 | from wagtail.images.api.fields import ImageRenditionField 9 | from wagtail.core.fields import StreamField 10 | 11 | from .streamfields import ContentBlock, ImageGalleryBlock, CallToActionBlock 12 | 13 | 14 | class HomePage(Page): 15 | """A home page class.""" 16 | 17 | template = "cms/pages/home_page.html" 18 | subpage_types = ['pages.FlexPage'] 19 | 20 | banner_subtitle = models.CharField( 21 | max_length=50, blank=True, null=True, help_text="An optional banner subtitle" 22 | ) 23 | banner_image = models.ForeignKey( 24 | "wagtailimages.Image", 25 | null=True, 26 | blank=False, 27 | on_delete=models.SET_NULL, 28 | related_name="+", 29 | help_text="An optional banner image", 30 | ) 31 | 32 | content = StreamField([ 33 | ('ContentBlock', ContentBlock()), 34 | ('ImageGalleryBlock', ImageGalleryBlock()), 35 | ('CallToActionBlock', CallToActionBlock()), 36 | ], null=True, blank=True) 37 | 38 | content_panels = [ 39 | FieldPanel("title", classname="full title"), 40 | ImageChooserPanel("banner_image"), 41 | FieldPanel("banner_subtitle"), 42 | StreamFieldPanel('content'), 43 | ] 44 | 45 | api_fields = [ 46 | APIField("title"), 47 | APIField("banner_subtitle"), 48 | APIField("banner_image"), 49 | APIField("banner_image_thumbnail", serializer=ImageRenditionField("fill-100x100", source="banner_image")), 50 | APIField("content"), 51 | ] 52 | 53 | class Meta: 54 | """Meta information.""" 55 | 56 | verbose_name = "Home Page" 57 | verbose_name_plural = "Home Pages" 58 | 59 | 60 | class FlexPage(Page): 61 | """A Flexible page class. Used for generic pages that don't have a true purpose.""" 62 | 63 | template = "cms/pages/flex_page.html" 64 | subpage_types = [] 65 | 66 | content = StreamField([ 67 | ('ContentBlock', ContentBlock()), 68 | ('ImageGalleryBlock', ImageGalleryBlock()), 69 | ('CallToActionBlock', CallToActionBlock()), 70 | ], null=True, blank=True) 71 | 72 | content_panels = [ 73 | FieldPanel("title", classname="full title"), 74 | StreamFieldPanel('content'), 75 | ] 76 | 77 | api_fields = [ 78 | APIField("title"), 79 | APIField("content"), 80 | ] 81 | 82 | class Meta: 83 | """Meta information.""" 84 | 85 | verbose_name = "Flex Page" 86 | verbose_name_plural = "Flex Pages" 87 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/streamfields.py: -------------------------------------------------------------------------------- 1 | """Streamfields for Wagtail Pages.""" 2 | from wagtail.core import blocks 3 | from wagtail.images.blocks import ImageChooserBlock 4 | from wagtail.documents.blocks import DocumentChooserBlock 5 | 6 | 7 | class RichTextBlock(blocks.RichTextBlock): 8 | """Rich text content.""" 9 | 10 | class Meta: 11 | """Provide additional meta information.""" 12 | 13 | icon = "edit" 14 | label = "Richtext" 15 | 16 | 17 | class ButtonBlock(blocks.StructBlock): 18 | """Button block.""" 19 | 20 | text = blocks.CharBlock(required=True) 21 | page = blocks.PageChooserBlock(required=False) 22 | document = DocumentChooserBlock(required=False) 23 | external_link = blocks.CharBlock(required=False) 24 | 25 | class Meta: 26 | """Provide attional meta information.""" 27 | 28 | icon = "link" 29 | label = "Button" 30 | 31 | 32 | class ImageBlock(blocks.StructBlock): 33 | """Image Content.""" 34 | POSITIONS_LRF_OPTIONS = ( 35 | ('left', 'Left'), 36 | ('right', 'Right'), 37 | ('full', 'Full'), 38 | ) 39 | 40 | image = ImageChooserBlock(required=True) 41 | caption = blocks.CharBlock(required=False) 42 | align = blocks.ChoiceBlock(choices=POSITIONS_LRF_OPTIONS) 43 | 44 | class Meta: 45 | """Provide attional meta information.""" 46 | 47 | icon = "image" 48 | label= "Image" 49 | 50 | 51 | class ContentBlock(blocks.StructBlock): 52 | """ 53 | Content - stream block example 54 | - Richtext 55 | - Image (center, left, right) 56 | - Button 57 | """ 58 | 59 | content = blocks.StreamBlock([ 60 | ('RichTextBlock', RichTextBlock()), 61 | ('ButtonBlock', ButtonBlock()), 62 | ('ImageBlock', ImageBlock()), 63 | ]) 64 | 65 | class Meta: 66 | """Provide additional meta information.""" 67 | 68 | icon = "doc-full" 69 | label = "Content" 70 | 71 | 72 | class ImageGalleryBlock(blocks.StructBlock): 73 | """ 74 | Image Gallery - list block example 75 | - Image 76 | """ 77 | 78 | images = blocks.ListBlock(blocks.StructBlock([ 79 | ('ImageBlock', ImageChooserBlock(required=True)), 80 | ])) 81 | 82 | class Meta: 83 | """Provide additional meta information.""" 84 | 85 | icon = "image" 86 | label = "Image Gallery" 87 | 88 | 89 | class CallToActionBlock(blocks.StructBlock): 90 | """ 91 | Call to Action - struct block example 92 | """ 93 | 94 | title = blocks.CharBlock(required=False) 95 | text = blocks.RichTextBlock( 96 | features=['h2', 'h3', 'h4', 'h5', 'bold', 'italic', 'ol', 'ul', 'link'], required=False) 97 | buttons = blocks.ListBlock(ButtonBlock(required=False), default=[]) 98 | 99 | class Meta: 100 | """Provide additional meta information.""" 101 | 102 | icon = "pick" 103 | label = "Call to Action" 104 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/migrations/0009_auto_20181113_1721.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1 on 2018-11-14 00:21 2 | 3 | from django.db import migrations 4 | import pages.streamfields 5 | import wagtail.core.blocks 6 | import wagtail.core.fields 7 | import wagtail.documents.blocks 8 | import wagtail.images.blocks 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('pages', '0008_auto_20181113_1657'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name='flexpage', 20 | name='content', 21 | field=wagtail.core.fields.StreamField([('content', wagtail.core.blocks.StructBlock([('content', wagtail.core.blocks.StreamBlock([('text', pages.streamfields.RichTextBlock()), ('button', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.CharBlock(required=True)), ('page', wagtail.core.blocks.PageChooserBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False)), ('external_link', wagtail.core.blocks.CharBlock(required=False))])), ('image', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('caption', wagtail.core.blocks.CharBlock(required=False)), ('align', wagtail.core.blocks.ChoiceBlock(choices=[('left', 'Left'), ('right', 'Right'), ('full', 'Full')]))]))]))])), ('image_gallery', wagtail.core.blocks.StructBlock([('images', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(required=True))])))])), ('call_to_action', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(required=False)), ('text', wagtail.core.blocks.RichTextBlock(features=['h2', 'h3', 'h4', 'h5', 'bold', 'italic', 'ol', 'ul', 'link'], required=False)), ('buttons', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.CharBlock(required=True)), ('page', wagtail.core.blocks.PageChooserBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False)), ('external_link', wagtail.core.blocks.CharBlock(required=False))], required=False), default=[]))]))], blank=True, null=True), 22 | ), 23 | migrations.AlterField( 24 | model_name='homepage', 25 | name='content', 26 | field=wagtail.core.fields.StreamField([('content', wagtail.core.blocks.StructBlock([('content', wagtail.core.blocks.StreamBlock([('text', pages.streamfields.RichTextBlock()), ('button', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.CharBlock(required=True)), ('page', wagtail.core.blocks.PageChooserBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False)), ('external_link', wagtail.core.blocks.CharBlock(required=False))])), ('image', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('caption', wagtail.core.blocks.CharBlock(required=False)), ('align', wagtail.core.blocks.ChoiceBlock(choices=[('left', 'Left'), ('right', 'Right'), ('full', 'Full')]))]))]))])), ('image_gallery', wagtail.core.blocks.StructBlock([('images', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(required=True))])))])), ('call_to_action', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(required=False)), ('text', wagtail.core.blocks.RichTextBlock(features=['h2', 'h3', 'h4', 'h5', 'bold', 'italic', 'ol', 'ul', 'link'], required=False)), ('buttons', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.CharBlock(required=True)), ('page', wagtail.core.blocks.PageChooserBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False)), ('external_link', wagtail.core.blocks.CharBlock(required=False))], required=False), default=[]))]))], blank=True, null=True), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /vue/src/views/WagtailPageHandler.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 108 | 109 | 117 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/apps/pages/migrations/0010_auto_20181114_0847.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1 on 2018-11-14 15:47 2 | 3 | from django.db import migrations 4 | import pages.streamfields 5 | import wagtail.core.blocks 6 | import wagtail.core.fields 7 | import wagtail.documents.blocks 8 | import wagtail.images.blocks 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('pages', '0009_auto_20181113_1721'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name='flexpage', 20 | name='content', 21 | field=wagtail.core.fields.StreamField([('ContentBlock', wagtail.core.blocks.StructBlock([('content', wagtail.core.blocks.StreamBlock([('RichTextBlock', pages.streamfields.RichTextBlock()), ('ButtonBlock', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.CharBlock(required=True)), ('page', wagtail.core.blocks.PageChooserBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False)), ('external_link', wagtail.core.blocks.CharBlock(required=False))])), ('ImageBlock', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('caption', wagtail.core.blocks.CharBlock(required=False)), ('align', wagtail.core.blocks.ChoiceBlock(choices=[('left', 'Left'), ('right', 'Right'), ('full', 'Full')]))]))]))])), ('ImageGalleryBlock', wagtail.core.blocks.StructBlock([('images', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('ImageBlock', wagtail.images.blocks.ImageChooserBlock(required=True))])))])), ('CallToActionBlock', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(required=False)), ('text', wagtail.core.blocks.RichTextBlock(features=['h2', 'h3', 'h4', 'h5', 'bold', 'italic', 'ol', 'ul', 'link'], required=False)), ('buttons', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.CharBlock(required=True)), ('page', wagtail.core.blocks.PageChooserBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False)), ('external_link', wagtail.core.blocks.CharBlock(required=False))], required=False), default=[]))]))], blank=True, null=True), 22 | ), 23 | migrations.AlterField( 24 | model_name='homepage', 25 | name='content', 26 | field=wagtail.core.fields.StreamField([('ContentBlock', wagtail.core.blocks.StructBlock([('content', wagtail.core.blocks.StreamBlock([('RichTextBlock', pages.streamfields.RichTextBlock()), ('ButtonBlock', wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.CharBlock(required=True)), ('page', wagtail.core.blocks.PageChooserBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False)), ('external_link', wagtail.core.blocks.CharBlock(required=False))])), ('ImageBlock', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(required=True)), ('caption', wagtail.core.blocks.CharBlock(required=False)), ('align', wagtail.core.blocks.ChoiceBlock(choices=[('left', 'Left'), ('right', 'Right'), ('full', 'Full')]))]))]))])), ('ImageGalleryBlock', wagtail.core.blocks.StructBlock([('images', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('ImageBlock', wagtail.images.blocks.ImageChooserBlock(required=True))])))])), ('CallToActionBlock', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(required=False)), ('text', wagtail.core.blocks.RichTextBlock(features=['h2', 'h3', 'h4', 'h5', 'bold', 'italic', 'ol', 'ul', 'link'], required=False)), ('buttons', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('text', wagtail.core.blocks.CharBlock(required=True)), ('page', wagtail.core.blocks.PageChooserBlock(required=False)), ('document', wagtail.documents.blocks.DocumentChooserBlock(required=False)), ('external_link', wagtail.core.blocks.CharBlock(required=False))], required=False), default=[]))]))], blank=True, null=True), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /django/website/wagtail_vue/wagtail_vue/settings/settings_base.py: -------------------------------------------------------------------------------- 1 | """Common settings and globals.""" 2 | 3 | import os 4 | from os.path import abspath, basename, dirname, join, normpath 5 | from sys import path 6 | 7 | from django.core.exceptions import ImproperlyConfigured 8 | 9 | # ======== PATH CONFIGURATION 10 | # Absolute filesystem path to the Django project directory: 11 | DJANGO_ROOT = dirname(dirname(abspath(__file__))) 12 | 13 | # Absolute filesystem path to the top-level project folder: 14 | SITE_ROOT = dirname(DJANGO_ROOT) 15 | 16 | # Site name: 17 | SITE_NAME = basename(DJANGO_ROOT) 18 | 19 | # Add our project to our pythonpath, this way we don't need to type our project 20 | # name in our dotted import paths: 21 | path.append(DJANGO_ROOT) 22 | # ======== END PATH CONFIGURATION 23 | 24 | ALLOWED_HOSTS = [ 25 | '.wagtail-vue-definately-a-real-wesbite-we-promise.com', 26 | ] 27 | 28 | # ======== DEBUG CONFIGURATION 29 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#debug 30 | DEBUG = False 31 | 32 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug 33 | TEMPLATE_DEBUG = DEBUG 34 | # ======== END DEBUG CONFIGURATION 35 | 36 | 37 | # ======== MANAGER CONFIGURATION 38 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#admins 39 | ADMINS = ( 40 | ('backend', 'admin@yourwebsite.com'), 41 | ) 42 | 43 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#managers 44 | MANAGERS = ADMINS 45 | # ======== END MANAGER CONFIGURATION 46 | 47 | 48 | # ======== GENERAL CONFIGURATION 49 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#time-zone 50 | TIME_ZONE = 'Canada/Mountain' 51 | 52 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code 53 | LANGUAGE_CODE = 'en-us' 54 | 55 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id 56 | SITE_ID = 1 57 | 58 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n 59 | USE_I18N = True 60 | 61 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n 62 | USE_L10N = True 63 | 64 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#use-tz 65 | USE_TZ = True 66 | # ======== END GENERAL CONFIGURATION 67 | 68 | 69 | # ======== MEDIA CONFIGURATION 70 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root 71 | MEDIA_ROOT = normpath(join(SITE_ROOT, 'media')) 72 | 73 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url 74 | MEDIA_URL = '/media/' 75 | # ======== END MEDIA CONFIGURATION 76 | 77 | 78 | # ======== STATIC FILE CONFIGURATION 79 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root 80 | STATIC_ROOT = normpath(join(SITE_ROOT, 'static_collected')) 81 | 82 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url 83 | STATIC_URL = '/static/' 84 | 85 | # See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS # noqa: E501 86 | STATICFILES_DIRS = ( 87 | normpath(join(SITE_ROOT, 'static')), 88 | ) 89 | 90 | STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' 91 | 92 | # See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders # noqa: E501 93 | STATICFILES_FINDERS = ( 94 | 'django.contrib.staticfiles.finders.FileSystemFinder', 95 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 96 | ) 97 | # ======== END STATIC FILE CONFIGURATION 98 | 99 | 100 | # ======== SECRET CONFIGURATION 101 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key 102 | # Note: This key only used for development and testing. 103 | SECRET_KEY = r'lbo=9bzcg-hkmubso1n!6%at5@8u2+=&g%2um8wk7jjpnkczqg' 104 | # ======== END SECRET CONFIGURATION 105 | 106 | 107 | # ======== FIXTURE CONFIGURATION 108 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS # noqa: E501 109 | FIXTURE_DIRS = ( 110 | normpath(join(SITE_ROOT, 'fixtures')), 111 | ) 112 | # ======== END FIXTURE CONFIGURATION 113 | 114 | 115 | # ======== TEMPLATE CONFIGURATION 116 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors # noqa: E501 117 | TEMPLATES = [ 118 | { 119 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 120 | 'APP_DIRS': True, 121 | 'DIRS': [ 122 | normpath(join(SITE_ROOT, 'templates')), 123 | ], 124 | 'OPTIONS': { 125 | 'context_processors': [ 126 | 'django.contrib.auth.context_processors.auth', 127 | 'django.template.context_processors.debug', 128 | 'django.template.context_processors.i18n', 129 | 'django.template.context_processors.media', 130 | 'django.template.context_processors.static', 131 | 'django.template.context_processors.tz', 132 | 'django.contrib.messages.context_processors.messages', 133 | 'django.template.context_processors.request', 134 | ], 135 | 'debug': DEBUG, 136 | }, 137 | }, 138 | ] 139 | # ======== END TEMPLATE CONFIGURATION 140 | 141 | 142 | # ======== MIDDLEWARE CONFIGURATION 143 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#middleware-classes 144 | MIDDLEWARE = ( 145 | 'corsheaders.middleware.CorsMiddleware', 146 | # Default Django middleware. 147 | 'django.middleware.common.CommonMiddleware', 148 | 'django.contrib.sessions.middleware.SessionMiddleware', 149 | 'django.middleware.csrf.CsrfViewMiddleware', 150 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 151 | 'django.contrib.messages.middleware.MessageMiddleware', 152 | 'wagtail.core.middleware.SiteMiddleware', 153 | 'wagtail.contrib.redirects.middleware.RedirectMiddleware', 154 | ) 155 | # ======== END MIDDLEWARE CONFIGURATION 156 | 157 | 158 | # ======== URL CONFIGURATION 159 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf 160 | ROOT_URLCONF = '%s.urls' % SITE_NAME 161 | # ======== END URL CONFIGURATION 162 | 163 | 164 | # ======== APP CONFIGURATION 165 | INSTALLED_APPS = ( 166 | 'django.contrib.auth', 167 | 'django.contrib.contenttypes', 168 | 'django.contrib.sessions', 169 | 'django.contrib.sites', 170 | 'django.contrib.messages', 171 | 'django.contrib.staticfiles', 172 | 'django.contrib.humanize', 173 | 'django.contrib.admin', 174 | 175 | 'adminactions', 176 | 'django_extensions', 177 | 178 | 'wagtail.contrib.forms', 179 | 'wagtail.contrib.redirects', 180 | 'wagtail.embeds', 181 | 'wagtail.sites', 182 | 'wagtail.users', 183 | 'wagtail.snippets', 184 | 'wagtail.documents', 185 | 'wagtail.images', 186 | 'wagtail.search', 187 | 'wagtail.admin', 188 | 'wagtail.core', 189 | 'wagtail.api.v2', 190 | 191 | 'rest_framework', 192 | 'modelcluster', 193 | 'taggit', 194 | 'corsheaders', 195 | 196 | 'pages', 197 | ) 198 | # ======== END APP CONFIGURATION 199 | 200 | 201 | # ======== LOGGING CONFIGURATION 202 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#logging 203 | # A sample logging configuration. The only tangible logging 204 | # performed by this configuration is to send an email to 205 | # the site admins on every HTTP 500 error when DEBUG=False. 206 | # See http://docs.djangoproject.com/en/dev/topics/logging for 207 | # more details on how to customize your logging configuration. 208 | LOGGING = { 209 | 'version': 1, 210 | 'disable_existing_loggers': False, 211 | 'filters': { 212 | 'require_debug_false': { 213 | '()': 'django.utils.log.RequireDebugFalse' 214 | } 215 | }, 216 | 'handlers': { 217 | 'mail_admins': { 218 | 'level': 'ERROR', 219 | 'filters': ['require_debug_false'], 220 | 'class': 'django.utils.log.AdminEmailHandler' 221 | }, 222 | }, 223 | } 224 | # ======== END LOGGING CONFIGURATION 225 | 226 | 227 | # ======== WSGI CONFIGURATION 228 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application 229 | WSGI_APPLICATION = 'wsgi.application' 230 | # ======== END WSGI CONFIGURATION 231 | 232 | 233 | # ======== WAGTAIL SETTINGS CONFIGURATION 234 | WAGTAIL_SITE_NAME = "Wagtail/Vue Website" 235 | # ======== END WAGTAIL SETTINGS CONFIGURATION 236 | 237 | 238 | # ======== ERROR MESSAGE FOR MISSING ENVIRONMENT VARIABLES 239 | def get_env_variable(var_name): 240 | """Get the environment variable or return exception.""" 241 | try: 242 | return os.environ[var_name] 243 | except KeyError: 244 | error_msg = 'Set the %s environment variable' % var_name 245 | raise ImproperlyConfigured(error_msg) 246 | # ======== END ERROR MESSAGE FOR MISSING ENVIRONMENT VARIABLES 247 | -------------------------------------------------------------------------------- /vue/SLIDES.md: -------------------------------------------------------------------------------- 1 | # Wagtail + Vue.js 2 | 3 | __ __ _ _ _ __ __ _ 4 | \ \ / /_ _ __ _| |_ __ _(_) | _ \ \ / / _ ___ (_)___ 5 | \ \ /\ / / _` |/ _` | __/ _` | | | _| |_ \ \ / / | | |/ _ \ | / __| 6 | \ V V / (_| | (_| | || (_| | | | |_ _| \ V /| |_| | __/_ | \__ \ 7 | \_/\_/ \__,_|\__, |\__\__,_|_|_| |_| \_/ \__,_|\___(_)/ |___/ 8 | |___/ |__/ 9 | 10 | Using Wagtail as a headless CMS. 11 | 12 | - Routing 13 | - Rendering page content 14 | - Simple page navigation 15 | 16 | # Routing for Wagtail 17 | 18 | _____ _ _ __ 19 | | _ \ ___ _ _| |_(_)_ __ __ _ / _| ___ _ __ 20 | | |_) / _ \| | | | __| | '_ \ / _` | | |_ / _ \| '__| 21 | | _ < (_) | |_| | |_| | | | | (_| | | _| (_) | | 22 | |_| \_\___/ \__,_|\__|_|_| |_|\__, | |_| \___/|_| 23 | |___/ 24 | __ __ _ _ _ 25 | \ \ / /_ _ __ _| |_ __ _(_) | 26 | \ \ /\ / / _` |/ _` | __/ _` | | | 27 | \ V V / (_| | (_| | || (_| | | | 28 | \_/\_/ \__,_|\__, |\__\__,_|_|_| 29 | |___/ 30 | 31 | ## 32 | 33 | In Wagtail each type of page will have it's own model since they may have different types of content. This makes it easy for us to render a different component for each page type. 34 | 35 | ``` 36 | class HomePage(Page): 37 | """A home page class.""" 38 | ...fields for page content... 39 | ``` 40 | 41 | ## 42 | 43 | When a user navigates to a page we make a request to the Wagtail API's "find" method and pass in the URL the user requested. 44 | 45 | If Wagtail can resolve this path to an existing page than it will return a `302` redirect response to the page's detail view which in turn returns the JSON data. 46 | 47 | ``` 48 | User requests "http://localhost:8080/" 49 | 50 | GET /api/v2/pages/find/?html_path=/ => 302 51 | GET /api/v2/pages/3/ => 200 JSON data 52 | ``` 53 | 54 | TODO: Show example of page response: http://localhost:8000/api/v2/pages/3/?format=json 55 | 56 | Right in the returned JSON data we'll find the `meta.type` key which we use to determine which component to render. 57 | 58 | ## 59 | 60 | If Wagtail cannot resolve the URL it will return a `404` not found response and we can inform the user. 61 | 62 | ``` 63 | GET /api/v2/pages/find/?html_path=/wp-login.php => 404 Not Found message 64 | ``` 65 | 66 | # Rendering Wagtail page content 67 | 68 | ____ _ _ 69 | | _ \ ___ _ __ __| | ___ _ __(_)_ __ __ _ 70 | | |_) / _ \ '_ \ / _` |/ _ \ '__| | '_ \ / _` | 71 | | _ < __/ | | | (_| | __/ | | | | | | (_| | 72 | |_| \_\___|_| |_|\__,_|\___|_| |_|_| |_|\__, | 73 | |___/ 74 | __ __ _ _ _ 75 | \ \ / /_ _ __ _| |_ __ _(_) | _ __ __ _ __ _ ___ 76 | \ \ /\ / / _` |/ _` | __/ _` | | | | '_ \ / _` |/ _` |/ _ \ 77 | \ V V / (_| | (_| | || (_| | | | | |_) | (_| | (_| | __/ 78 | \_/\_/ \__,_|\__, |\__\__,_|_|_| | .__/ \__,_|\__, |\___| 79 | |___/ |_| |___/ 80 | _ _ 81 | ___ ___ _ __ | |_ ___ _ __ | |_ 82 | / __/ _ \| '_ \| __/ _ \ '_ \| __| 83 | | (_| (_) | | | | || __/ | | | |_ 84 | \___\___/|_| |_|\__\___|_| |_|\__| 85 | 86 | ## 87 | 88 | Along with the typical methods of managing page content, Wagtail has a unique concept called "StreamField" which allows the user to intersperse different types of content, usually referred to as "blocks", which can be repeated and arranged in any order. 89 | 90 | TODO: Show example of Wagtail StreamField content: http://localhost:8000/api/v2/pages/3/?format=json 91 | 92 | ## 93 | 94 | These StreamField blocks can at times be tricky to work with through the API. Since they are so flexible they are commonly used in many different ways and this can cause the same block to end up outputting data differently. At times we need to add extra logic to our components to work around this. 95 | 96 | ``` 97 | # ButtonBlock used inside of a "StreamBlock" 98 | { 99 | "id": "23960469-af52-41dd-a9fe-3f9bbb9593e5", 100 | "value": { 101 | "document": null, 102 | "text": "External link", 103 | "external_link": "https://www.duckduckgo.com/", 104 | "page": null 105 | }, 106 | "type": "ButtonBlock" 107 | } 108 | 109 | # The same ButtonBlock used inside of a "ListBlock" 110 | { 111 | "document": null, 112 | "text": "Page link", 113 | "external_link": "", 114 | "page": 4 115 | } 116 | ``` 117 | 118 | ## 119 | 120 | Inside of StreamField blocks Wagtail usually only stores the IDs for images, documents, or other pages. It's best to make a dedicated component for each of these to abstract the API logic of fetching the full details. 121 | 122 | ``` 123 | # Wagtail Endpoints 124 | Pages: /api/v2/pages// 125 | Images: /api/v2/images// 126 | Documents: /api/v2/documents// 127 | 128 | # WagtailPage component 129 | - Fetch a Wagtail page with the ID passed into our component. 130 | - Get the page's path from the "meta.html_url" property and anything else we want from the response. 131 | - Render a link with the path. 132 | ``` 133 | 134 | ## 135 | 136 | Which StreamField blocks are available can differ between page types and they can be nested inside of one another so we need a reliable way to pass data down the component tree. 137 | 138 | Again, we chose to abstract this out into a separate component which accepts any type of StreamField block and renders the specific component we've made for that block type. This allows us to loop through the StreamField content on any page or nested StreamField content inside of a StreamField block. 139 | 140 | ``` 141 | # HomePage component 142 | - Loop through blocks in content field. 143 | - For each block pass block data to StreamField component. 144 | 145 | # StreamField component 146 | - Register all available StreamField block components. 147 | - Render StreamField block component matching the current block's "type" property. 148 | 149 | # StreamField block component 150 | - This is where all your code for a specific StreamField block will go. Templating, styles, extra scripting, etc. 151 | - If you're using nested StreamFields you can re-use that component here and even re-use other StreamField block components entirely. 152 | ``` 153 | 154 | # Simple page navigation 155 | 156 | ____ _ _ 157 | / ___|(_)_ __ ___ _ __ | | ___ _ __ __ _ __ _ ___ 158 | \___ \| | '_ ` _ \| '_ \| |/ _ \ | '_ \ / _` |/ _` |/ _ \ 159 | ___) | | | | | | | |_) | | __/ | |_) | (_| | (_| | __/ 160 | |____/|_|_| |_| |_| .__/|_|\___| | .__/ \__,_|\__, |\___| 161 | |_| |_| |___/ 162 | _ _ _ 163 | _ __ __ ___ _(_) __ _ __ _| |_(_) ___ _ __ 164 | | '_ \ / _` \ \ / / |/ _` |/ _` | __| |/ _ \| '_ \ 165 | | | | | (_| |\ V /| | (_| | (_| | |_| | (_) | | | | 166 | |_| |_|\__,_| \_/ |_|\__, |\__,_|\__|_|\___/|_| |_| 167 | |___/ 168 | 169 | ...or: How I Learned to Stop Worrying and Love the API 170 | 171 | ## Specifying fields 172 | 173 | The first thing I'll show you is how to restrict which fields the Wagtail API returns when making a request. If we're just building a menu we don't need the page content bloating our requests. 174 | 175 | ``` 176 | GET /api/v2/pages/?fields=_,html_url,title 177 | ``` 178 | 179 | This request will return a list of all pages on the site with only the `html_url` and `title` for each. The `_` here is a special character and tells the Wagtail API to hide all fields by default, this gives us a clean slate to start with. 180 | 181 | ## Limiting 182 | 183 | If we got a thousand pages on the site we don't want all of those showing up in the menu. You can quickly limit the number of returned pages by tacking on the `limit` query parameter. 184 | 185 | ``` 186 | GET /api/v2/pages/?fields=_,html_url,title&limit=5 187 | ``` 188 | 189 | ## Filtering 190 | 191 | We're getting close now, but what if we want more control over what pages are in the menu? By default Wagtail pages have a boolean field called `show_in_menus`. We might as well use that and the Wagtail API makes this easy for us, simple add the field as a query parameter and assign the value we need. 192 | 193 | ``` 194 | GET /api/v2/pages/?fields=_,html_url,title&limit=5?show_in_menus=true 195 | ``` 196 | 197 | ## I want more... 198 | 199 | This is all we need for our purposes right now but with the Wagtail API you can do a lot more out of the box which can also be useful, just to name a few: 200 | 201 | - Pagination 202 | - Ordering 203 | - Filtering (field, page type, tree position, etc.) 204 | - Search 205 | 206 | For details on these, and more, check out the docs: https://docs.wagtail.io/en/latest/advanced_topics/api/v2/usage.html 207 | --------------------------------------------------------------------------------