├── .gitignore ├── LICENSE ├── README.md ├── peregrine ├── __init__.py ├── apps.py ├── fixtures │ └── initial_site.json ├── management │ └── commands │ │ └── peregrine_initial_site.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20171216_1408.py │ ├── 0003_auto_20171217_1706.py │ ├── 0004_auto_20180105_1315.py │ ├── 0005_auto_20180418_1637.py │ ├── 0006_auto_20180418_1652.py │ ├── 0007_auto_20180523_1908.py │ ├── 0008_peregrinesettings_revisions_to_keep.py │ ├── 0009_auto_20211105_1634.py │ └── __init__.py ├── models.py ├── static │ └── peregrine │ │ ├── peregrine-style.css │ │ ├── peregrine-style.min.css │ │ └── peregrine-style.min.css.gz ├── templates │ ├── peregrine │ │ ├── __init__.py │ │ ├── base.html │ │ ├── site_page.html │ │ ├── site_post.html │ │ ├── site_post_list.html │ │ └── tags │ │ │ ├── top_menu.html │ │ │ ├── top_menu_children.html │ │ │ └── top_menu_posts.html │ ├── wagtailcontentstream │ │ └── blocks │ │ │ ├── captioned_image.html │ │ │ └── heading.html │ └── wagtailembeds │ │ └── embed_frontend.html ├── templatetags │ ├── __init__.py │ └── peregrine.py ├── urls.py ├── views.py └── wagtail_hooks.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # manage.py, always default to dev settings 2 | manage.py 3 | 4 | # Django media files 5 | media/* 6 | 7 | # Python bytecode: 8 | *.py[co] 9 | 10 | # Packaging files: 11 | *.egg* 12 | dist/* 13 | 14 | # Sphinx docs: 15 | build 16 | 17 | # SQLite3 database files: 18 | *.db 19 | 20 | # Logs: 21 | *.log 22 | 23 | # Eclipse 24 | .project 25 | 26 | # Linux Editors 27 | *~ 28 | \#*\# 29 | /.emacs.desktop 30 | /.emacs.desktop.lock 31 | .elc 32 | auto-save-list 33 | tramp 34 | .\#* 35 | *.swp 36 | *.swo 37 | 38 | # Mac 39 | .DS_Store 40 | ._* 41 | 42 | # Windows 43 | Thumbs.db 44 | Desktop.ini 45 | 46 | # Dev tools 47 | .idea 48 | .vagrant 49 | 50 | # Ignore local configurations 51 | wrds/settings/local.py 52 | db.sqlite3 53 | 54 | # Node modules 55 | node_modules/ 56 | 57 | # Bower components 58 | bower_components/ 59 | 60 | # Coverage 61 | htmlcov/ 62 | .coverage 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Timothy Allen and individual contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of Django nor the names of its contributors may be used 15 | to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Peregrine 2 | 3 | Peregrine is an opinionated blogging platform which uses [the Wagtail CMS](https://wagtail.io) on the [Django web framework](https://www.djangoproject.com) and is geared for coding blogs. It uses Wagtail's fantastic [StreamField feature](http://docs.wagtail.io/en/v1.13/topics/streamfield.html) to provide fully structured content body element blocks, completely separating content from the presentation layer (CSS, JS, and HTML). 4 | 5 | It uses [Wagtail Code Block](https://github.com/FlipperPA/wagtailcodeblock) with [PrismJS](https://prismjs.com/) syntax highlighting under the hood for beautiful code snippets. You can see this in action at [PyPhilly.org](https://pyphilly.org/open-source-projects/getting-started-five-minute-install/). 6 | 7 | **Are you looking for a more robust system, for example, creating a marketing site? The maintainer of Peregrine also contributes to [CodeRedCMS](https://github.com/coderedcorp/coderedcms), which offers a lot more bells and whistles. Peregrine will remain a basic weblog solutions focused on features to display code.** 8 | 9 | ## Getting Started: the Five Minute Install 10 | 11 | These instructions will be fleshed out, but if you want to give it a try, here are the basics. 12 | 13 | ### System 14 | 15 | ```shell 16 | mkvirtualenv my_blog 17 | pip install peregrine 18 | django-admin startproject my_blog 19 | cd my_blog 20 | ``` 21 | 22 | ### Settings 23 | 24 | Your settings file will be located in `my_blog/settings.py` if you're using the default Django project layout created by the `startproject` command above. You'll need to add the sections beneath `INSTALLED_APPS` and `MIDDLEWARE`, and add `'wagtail.contrib.settings.context_processors.settings',` to your `TEMPLATES` context processors in your settings to look like this. 25 | 26 | ```python 27 | INSTALLED_APPS = [ 28 | ... 29 | ] 30 | 31 | PEREGRINE_APPS = [ 32 | "peregrine", 33 | "bootstrap4", 34 | "wagtailcodeblock", 35 | "wagtailcontentstream", 36 | 37 | "wagtail.contrib.forms", 38 | "wagtail.contrib.redirects", 39 | "wagtail.embeds", 40 | "wagtail.sites", 41 | "wagtail.users", 42 | "wagtail.snippets", 43 | "wagtail.documents", 44 | "wagtail.images", 45 | "wagtail.search", 46 | "wagtail.admin", 47 | "wagtail.core", 48 | "taggit", 49 | "modelcluster", 50 | 51 | "wagtail.contrib.settings", 52 | "wagtail.contrib.modeladmin", 53 | "wagtail.contrib.table_block", 54 | ] 55 | 56 | 57 | INSTALLED_APPS += PEREGRINE_APPS 58 | 59 | MIDDLEWARE = [ 60 | ... 61 | ] 62 | 63 | PEREGRINE_MIDDLEWARE = [ 64 | "wagtail.contrib.redirects.middleware.RedirectMiddleware", 65 | ] 66 | 67 | MIDDLEWARE += PEREGRINE_MIDDLEWARE 68 | 69 | # WAGTAIL_SITE_NAME is used by Wagtail; others are used by OpenGraph. 70 | WAGTAIL_SITE_NAME = "PyPhilly: Home of FlipperPA" 71 | WAGTAIL_SITE_DESCRIPTION = "PyPhilly is the website of Tim Allen, a web developer living in Philadelphia. Tim has a wide range of interests, but mostly writes about Python, Django, and virtual reality." 72 | WAGTAIL_SITE_URL = "https://PyPhilly.org/" 73 | 74 | TEMPLATES = [ 75 | { 76 | ... 77 | 78 | 'OPTIONS': { 79 | 'context_processors': [ 80 | ... 81 | 82 | 'wagtail.contrib.settings.context_processors.settings', 83 | ] 84 | } 85 | } 86 | ] 87 | 88 | ``` 89 | 90 | ### URLs 91 | 92 | ```python 93 | from django.contrib import admin 94 | from django.urls import include, path, re_path 95 | 96 | from wagtail.admin import urls as wagtailadmin_urls 97 | from wagtail.documents import urls as wagtaildocs_urls 98 | 99 | urlpatterns = [ 100 | path('admin/', admin.site.urls), 101 | 102 | # Wagtail / Peregrine URLs 103 | path('documents/', include(wagtaildocs_urls)), 104 | path('cms/', include(wagtailadmin_urls)), 105 | re_path(r'^', include('peregrine.urls')), 106 | ] 107 | ``` 108 | 109 | ### Fire it up! 110 | 111 | After you've set up your settings, we need to create your database and a superuser. Issue the following commands from your project root directory. 112 | 113 | *Only run the command `peregrine_initial_site` if you are running on a new project, as it loads database fixtures!* 114 | 115 | 116 | ```shell 117 | python manage.py migrate 118 | # ** Be sure to see the note above before running this next command. It isn't necessary if you don't want to. ** 119 | python manage.py peregrine_initial_site 120 | python manage.py createsuperuser 121 | python manage.py runserver 0:8000 122 | ``` 123 | 124 | You should then be able to navigate to http://localhost:8000/cms/ and log in, and start creating! 125 | 126 | ## Maintainer 127 | 128 | * Timothy Allen (https://github.com/FlipperPA/) 129 | 130 | ## Contributors 131 | 132 | * Jon Banafato (https://github.com/jonafato/) 133 | * Dave Bauer (https://github.com/tdxdave) 134 | * Rana Fayez (https://github.com/Tagine/) 135 | * Jeff Triplett (https://github.com/jefftriplett/) 136 | -------------------------------------------------------------------------------- /peregrine/__init__.py: -------------------------------------------------------------------------------- 1 | APPS = [ 2 | "wagtail.contrib.settings", 3 | ] 4 | -------------------------------------------------------------------------------- /peregrine/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PeregrineConfig(AppConfig): 5 | name = "peregrine" 6 | default_auto_field = "django.db.models.BigAutoField" 7 | -------------------------------------------------------------------------------- /peregrine/fixtures/initial_site.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "model": "wagtailcore.site", 4 | "pk": 1, 5 | "fields": { 6 | "hostname": "localhost", 7 | "port": 80, 8 | "site_name": null, 9 | "root_page": 3, 10 | "is_default_site": true 11 | } 12 | }, 13 | { 14 | "model": "wagtailcore.page", 15 | "pk": 1, 16 | "fields": { 17 | "path": "0001", 18 | "depth": 1, 19 | "numchild": 1, 20 | "title": "Root", 21 | "draft_title": "Root", 22 | "slug": "root", 23 | "content_type": 1, 24 | "live": true, 25 | "has_unpublished_changes": false, 26 | "url_path": "/", 27 | "owner": null, 28 | "seo_title": "", 29 | "show_in_menus": false, 30 | "search_description": "", 31 | "go_live_at": null, 32 | "expire_at": null, 33 | "expired": false, 34 | "locked": false, 35 | "first_published_at": null, 36 | "last_published_at": null, 37 | "latest_revision_created_at": null, 38 | "live_revision": null 39 | } 40 | }, 41 | { 42 | "model": "wagtailcore.page", 43 | "pk": 3, 44 | "fields": { 45 | "path": "00010002", 46 | "depth": 2, 47 | "numchild": 1, 48 | "title": "Peregrine: a blogging platform.", 49 | "draft_title": "Peregrine: a blogging platform.", 50 | "slug": "peregrine-blogging-platform", 51 | "content_type": 25, 52 | "live": true, 53 | "has_unpublished_changes": false, 54 | "url_path": "/peregrine-blogging-platform/", 55 | "owner": 1, 56 | "seo_title": "", 57 | "show_in_menus": true, 58 | "search_description": "", 59 | "go_live_at": null, 60 | "expire_at": null, 61 | "expired": false, 62 | "locked": false, 63 | "first_published_at": "2017-10-21T23:38:40.187Z", 64 | "last_published_at": "2017-10-21T23:38:40.187Z", 65 | "latest_revision_created_at": "2017-10-21T23:38:40.108Z", 66 | "live_revision": 1 67 | } 68 | }, 69 | { 70 | "model": "wagtailcore.page", 71 | "pk": 4, 72 | "fields": { 73 | "path": "000100020001", 74 | "depth": 3, 75 | "numchild": 0, 76 | "title": "Getting Started", 77 | "draft_title": "Getting Started", 78 | "slug": "getting-started-five-minute-install", 79 | "content_type": 25, 80 | "live": true, 81 | "has_unpublished_changes": false, 82 | "url_path": "/peregrine-blogging-platform/getting-started-five-minute-install/", 83 | "owner": 1, 84 | "seo_title": "", 85 | "show_in_menus": true, 86 | "search_description": "", 87 | "go_live_at": null, 88 | "expire_at": null, 89 | "expired": false, 90 | "locked": false, 91 | "first_published_at": "2017-10-22T15:52:20.625Z", 92 | "last_published_at": "2017-10-22T15:57:26.004Z", 93 | "latest_revision_created_at": "2017-10-22T15:57:25.915Z", 94 | "live_revision": 3 95 | } 96 | }, 97 | { 98 | "model": "wagtailcore.pagerevision", 99 | "pk": 1, 100 | "fields": { 101 | "page": 3, 102 | "submitted_for_moderation": false, 103 | "created_at": "2017-10-21T23:38:40.108Z", 104 | "user": 1, 105 | "content_json": "{\"pk\": 3, \"path\": \"00010002\", \"owner\": 1, \"expired\": false, \"slug\": \"peregrine-blogging-platform\", \"first_published_at\": null, \"locked\": false, \"search_description\": \"\", \"excerpt\": \"
Peregrine is an opinionated blogging platform which uses the Wagtail CMS on the Django web framework. It uses Wagtail's fantastic StreamField feature to provide fully structured content body element blocks, completely separating content from the presentation layer (CSS, JS, and HTML).
\", \"last_published_at\": null, \"body\": \"[{\\\"value\\\": \\\"Built on Wagtail.\\\", \\\"id\\\": \\\"27fc57ed-ee72-43d8-8c28-198b66d2406f\\\", \\\"type\\\": \\\"heading\\\"}, {\\\"value\\\": \\\"Wagtail provides a beautiful CMS experience.
\\\", \\\"id\\\": \\\"ae315cb3-5929-479f-a252-2e3c874b060c\\\", \\\"type\\\": \\\"paragraph\\\"}]\", \"show_in_menus\": true, \"url_path\": \"/peregrine-blogging-platform/\", \"live\": true, \"latest_revision_created_at\": null, \"draft_title\": \"Peregrine: a blogging platform.\", \"expire_at\": null, \"seo_title\": \"\", \"title\": \"Peregrine: a blogging platform.\", \"header_image\": null, \"live_revision\": null, \"go_live_at\": null, \"content_type\": 25, \"has_unpublished_changes\": false, \"depth\": 2, \"numchild\": 0}", 106 | "approved_go_live_at": null 107 | } 108 | }, 109 | { 110 | "model": "wagtailcore.pagerevision", 111 | "pk": 2, 112 | "fields": { 113 | "page": 4, 114 | "submitted_for_moderation": false, 115 | "created_at": "2017-10-22T15:52:20.547Z", 116 | "user": 1, 117 | "content_json": "{\"depth\": 3, \"owner\": 1, \"locked\": false, \"live_revision\": null, \"latest_revision_created_at\": null, \"search_description\": \"\", \"header_image\": null, \"path\": \"000100020001\", \"title\": \"Getting Started: the Five Minute Install\", \"content_type\": 25, \"expire_at\": null, \"last_published_at\": null, \"go_live_at\": null, \"body\": \"[{\\\"id\\\": \\\"01682ba7-3481-476e-b578-7a8a7192072c\\\", \\\"value\\\": \\\"Create a Fresh Django Project\\\", \\\"type\\\": \\\"heading\\\"}, {\\\"id\\\": \\\"f22be326-089f-4169-a353-7ce18388bf5e\\\", \\\"value\\\": {\\\"code\\\": \\\"mkvirtualenv my_blog\\\\r\\\\npip install peregrine\\\\r\\\\ndjango-admin startproject my_blog\\\\r\\\\ncd my_blog\\\", \\\"language\\\": \\\"bash\\\"}, \\\"type\\\": \\\"code\\\"}, {\\\"id\\\": \\\"c24769ae-e029-4ccc-b920-da3c844b3e0e\\\", \\\"value\\\": \\\"Modify Your Django Settings\\\", \\\"type\\\": \\\"heading\\\"}, {\\\"id\\\": \\\"31ebd29a-7723-49b7-8b06-29668aa43650\\\", \\\"value\\\": \\\"Your settings file will be located in\\\\u00a0my_blog/settings.py\\\\u00a0if you're using the default Django project layout created by the\\\\u00a0startproject\\\\u00a0command above. You can add these settings at the very bottom of the file.
\\\", \\\"type\\\": \\\"paragraph\\\"}, {\\\"id\\\": \\\"7e4c79d0-4948-4a4c-ae48-f293289b9682\\\", \\\"value\\\": {\\\"code\\\": \\\"PEREGRINE_APPS = [\\\\r\\\\n 'wagtail.wagtailcore',\\\\r\\\\n 'wagtail.wagtailadmin',\\\\r\\\\n 'wagtail.wagtaildocs',\\\\r\\\\n 'wagtail.wagtailsnippets',\\\\r\\\\n 'wagtail.wagtailusers',\\\\r\\\\n 'wagtail.wagtailimages',\\\\r\\\\n 'wagtail.wagtailembeds',\\\\r\\\\n 'wagtail.wagtailsearch',\\\\r\\\\n 'wagtail.wagtailsites',\\\\r\\\\n 'wagtail.wagtailredirects',\\\\r\\\\n 'wagtail.wagtailforms',\\\\r\\\\n 'wagtail.contrib.table_block',\\\\r\\\\n\\\\r\\\\n 'bootstrap4',\\\\r\\\\n 'wagtailcodeblock',\\\\r\\\\n 'wagtailcontentstream',\\\\r\\\\n 'peregrine',\\\\r\\\\n 'taggit',\\\\r\\\\n 'modelcluster',\\\\r\\\\n]\\\\r\\\\n\\\\r\\\\nINSTALLED_APPS += PEREGRINE_APPS\\\\r\\\\n\\\\r\\\\nPEREGRINE_MIDDLEWARE = [\\\\r\\\\n 'wagtail.wagtailcore.middleware.SiteMiddleware',\\\\r\\\\n 'wagtail.wagtailredirects.middleware.RedirectMiddleware',\\\\r\\\\n]\\\\r\\\\n\\\\r\\\\nMIDDLEWARE += PEREGRINE_MIDDLEWARE\\\\r\\\\n\\\\r\\\\nWAGTAIL_SITE_NAME = 'My Blog'\\\", \\\"language\\\": \\\"python\\\"}, \\\"type\\\": \\\"code\\\"}, {\\\"id\\\": \\\"1751a6eb-cd82-46b0-bcf0-0efad12ee3b0\\\", \\\"value\\\": \\\"Setting Up URLs\\\", \\\"type\\\": \\\"heading\\\"}, {\\\"id\\\": \\\"2fb81ed9-dfd4-41b0-9ed3-ad64181e07b7\\\", \\\"value\\\": \\\"Your URL route file will be located in\\\\u00a0my_blog/urls.py\\\\u00a0if you're using the default Django project layout created by the\\\\u00a0startproject\\\\u00a0command above. You can replace the entire file with this:
\\\", \\\"type\\\": \\\"paragraph\\\"}, {\\\"id\\\": \\\"5d4374e1-2848-4e5d-9285-372b04f3fff4\\\", \\\"value\\\": {\\\"code\\\": \\\"from django.conf.urls import url, include\\\\r\\\\nfrom django.contrib import admin\\\\r\\\\n\\\\r\\\\nfrom wagtail.wagtailcore import urls as wagtail_urls\\\\r\\\\nfrom wagtail.wagtailadmin import urls as wagtailadmin_urls\\\\r\\\\nfrom wagtail.wagtaildocs import urls as wagtaildocs_urls\\\\r\\\\n\\\\r\\\\nurlpatterns = [\\\\r\\\\n url(r'^admin/', admin.site.urls),\\\\r\\\\n\\\\r\\\\n # Peregrine URLs for Wagtail\\\\r\\\\n url(r'^documents/', include(wagtaildocs_urls)),\\\\r\\\\n url(r'^cms/', include(wagtailadmin_urls)),\\\\r\\\\n url(r'', include(wagtail_urls)),\\\\r\\\\n]\\\", \\\"language\\\": \\\"python\\\"}, \\\"type\\\": \\\"code\\\"}, {\\\"id\\\": \\\"c819c01a-a8c8-4536-bfd6-999d575e6a8c\\\", \\\"value\\\": \\\"Fire it up!\\\", \\\"type\\\": \\\"heading\\\"}, {\\\"id\\\": \\\"6a136620-52de-4704-9f24-e2d35e07ce21\\\", \\\"value\\\": \\\"After you've set up your settings, we need to create your database and a superuser. Issue the following commands from your project root directory.
Only run the command\\\\u00a0peregrine_initial_site\\\\u00a0if you are running on a new project, as it loads database fixtures!
\\\", \\\"type\\\": \\\"paragraph\\\"}, {\\\"id\\\": \\\"7c141684-0f1c-4b37-82dc-8a4967c5e5f8\\\", \\\"value\\\": {\\\"code\\\": \\\"python manage.py migrate\\\\r\\\\npython manage.py peregrine_initial_site\\\\r\\\\npython manage.py createsuperuser\\\\r\\\\npython manage.py runserver 0:8000\\\", \\\"language\\\": \\\"bash\\\"}, \\\"type\\\": \\\"code\\\"}, {\\\"id\\\": \\\"34e44c1d-078b-4224-98ab-b0a2e240d8d7\\\", \\\"value\\\": \\\"You should then be able to navigate to http://localhost:8000/cms/ and log in, and start creating!
If you know a little bit about Django, it should be relatively straightforward to get started with Peregrine. If you are new to Django, check out the excellent tutorial.
\"}", 118 | "approved_go_live_at": null 119 | } 120 | }, 121 | { 122 | "model": "wagtailcore.pagerevision", 123 | "pk": 3, 124 | "fields": { 125 | "page": 4, 126 | "submitted_for_moderation": false, 127 | "created_at": "2017-10-22T15:57:25.915Z", 128 | "user": 1, 129 | "content_json": "{\"has_unpublished_changes\": false, \"search_description\": \"\", \"latest_revision_created_at\": \"2017-10-22T15:52:20.547Z\", \"first_published_at\": \"2017-10-22T15:52:20.625Z\", \"seo_title\": \"\", \"slug\": \"getting-started-five-minute-install\", \"expire_at\": null, \"url_path\": \"/peregrine-blogging-platform/getting-started-five-minute-install/\", \"body\": \"[{\\\"type\\\": \\\"heading\\\", \\\"id\\\": \\\"01682ba7-3481-476e-b578-7a8a7192072c\\\", \\\"value\\\": \\\"Create a Fresh Django Project\\\"}, {\\\"type\\\": \\\"code\\\", \\\"id\\\": \\\"f22be326-089f-4169-a353-7ce18388bf5e\\\", \\\"value\\\": {\\\"language\\\": \\\"bash\\\", \\\"code\\\": \\\"mkvirtualenv my_blog\\\\r\\\\npip install peregrine\\\\r\\\\ndjango-admin startproject my_blog\\\\r\\\\ncd my_blog\\\"}}, {\\\"type\\\": \\\"heading\\\", \\\"id\\\": \\\"c24769ae-e029-4ccc-b920-da3c844b3e0e\\\", \\\"value\\\": \\\"Modify Your Django Settings\\\"}, {\\\"type\\\": \\\"paragraph\\\", \\\"id\\\": \\\"31ebd29a-7723-49b7-8b06-29668aa43650\\\", \\\"value\\\": \\\"Your settings file will be located in\\\\u00a0my_blog/settings.py\\\\u00a0if you're using the default Django project layout created by the\\\\u00a0startproject\\\\u00a0command above. You can add these settings at the very bottom of the file.
\\\"}, {\\\"type\\\": \\\"code\\\", \\\"id\\\": \\\"7e4c79d0-4948-4a4c-ae48-f293289b9682\\\", \\\"value\\\": {\\\"language\\\": \\\"python\\\", \\\"code\\\": \\\"PEREGRINE_APPS = [\\\\r\\\\n 'wagtail.wagtailcore',\\\\r\\\\n 'wagtail.wagtailadmin',\\\\r\\\\n 'wagtail.wagtaildocs',\\\\r\\\\n 'wagtail.wagtailsnippets',\\\\r\\\\n 'wagtail.wagtailusers',\\\\r\\\\n 'wagtail.wagtailimages',\\\\r\\\\n 'wagtail.wagtailembeds',\\\\r\\\\n 'wagtail.wagtailsearch',\\\\r\\\\n 'wagtail.wagtailsites',\\\\r\\\\n 'wagtail.wagtailredirects',\\\\r\\\\n 'wagtail.wagtailforms',\\\\r\\\\n 'wagtail.contrib.table_block',\\\\r\\\\n\\\\r\\\\n 'bootstrap4',\\\\r\\\\n 'wagtailcodeblock',\\\\r\\\\n 'wagtailcontentstream',\\\\r\\\\n 'peregrine',\\\\r\\\\n 'taggit',\\\\r\\\\n 'modelcluster',\\\\r\\\\n]\\\\r\\\\n\\\\r\\\\nINSTALLED_APPS += PEREGRINE_APPS\\\\r\\\\n\\\\r\\\\nPEREGRINE_MIDDLEWARE = [\\\\r\\\\n 'wagtail.wagtailcore.middleware.SiteMiddleware',\\\\r\\\\n 'wagtail.wagtailredirects.middleware.RedirectMiddleware',\\\\r\\\\n]\\\\r\\\\n\\\\r\\\\nMIDDLEWARE += PEREGRINE_MIDDLEWARE\\\\r\\\\n\\\\r\\\\nWAGTAIL_SITE_NAME = 'My Blog'\\\"}}, {\\\"type\\\": \\\"heading\\\", \\\"id\\\": \\\"1751a6eb-cd82-46b0-bcf0-0efad12ee3b0\\\", \\\"value\\\": \\\"Setting Up URLs\\\"}, {\\\"type\\\": \\\"paragraph\\\", \\\"id\\\": \\\"2fb81ed9-dfd4-41b0-9ed3-ad64181e07b7\\\", \\\"value\\\": \\\"Your URL route file will be located in\\\\u00a0my_blog/urls.py\\\\u00a0if you're using the default Django project layout created by the\\\\u00a0startproject\\\\u00a0command above. You can replace the entire file with this:
\\\"}, {\\\"type\\\": \\\"code\\\", \\\"id\\\": \\\"5d4374e1-2848-4e5d-9285-372b04f3fff4\\\", \\\"value\\\": {\\\"language\\\": \\\"python\\\", \\\"code\\\": \\\"from django.conf.urls import url, include\\\\r\\\\nfrom django.contrib import admin\\\\r\\\\n\\\\r\\\\nfrom wagtail.wagtailcore import urls as wagtail_urls\\\\r\\\\nfrom wagtail.wagtailadmin import urls as wagtailadmin_urls\\\\r\\\\nfrom wagtail.wagtaildocs import urls as wagtaildocs_urls\\\\r\\\\n\\\\r\\\\nurlpatterns = [\\\\r\\\\n url(r'^admin/', admin.site.urls),\\\\r\\\\n\\\\r\\\\n # Peregrine URLs for Wagtail\\\\r\\\\n url(r'^documents/', include(wagtaildocs_urls)),\\\\r\\\\n url(r'^cms/', include(wagtailadmin_urls)),\\\\r\\\\n url(r'', include(wagtail_urls)),\\\\r\\\\n]\\\"}}, {\\\"type\\\": \\\"heading\\\", \\\"id\\\": \\\"c819c01a-a8c8-4536-bfd6-999d575e6a8c\\\", \\\"value\\\": \\\"Fire it up!\\\"}, {\\\"type\\\": \\\"paragraph\\\", \\\"id\\\": \\\"6a136620-52de-4704-9f24-e2d35e07ce21\\\", \\\"value\\\": \\\"After you've set up your settings, we need to create your database and a superuser. Issue the following commands from your project root directory.
Only run the command\\\\u00a0peregrine_initial_site\\\\u00a0if you are running on a new project, as it loads database fixtures!
\\\"}, {\\\"type\\\": \\\"code\\\", \\\"id\\\": \\\"7c141684-0f1c-4b37-82dc-8a4967c5e5f8\\\", \\\"value\\\": {\\\"language\\\": \\\"bash\\\", \\\"code\\\": \\\"python manage.py migrate\\\\r\\\\npython manage.py peregrine_initial_site\\\\r\\\\npython manage.py createsuperuser\\\\r\\\\npython manage.py runserver 0:8000\\\"}}, {\\\"type\\\": \\\"paragraph\\\", \\\"id\\\": \\\"34e44c1d-078b-4224-98ab-b0a2e240d8d7\\\", \\\"value\\\": \\\"You should then be able to navigate to http://localhost:8000/cms/ and log in, and start creating!
If you know a little bit about Django, it should be relatively straightforward to get started with Peregrine with this five-minute install. If you are new to Django, check out the excellent tutorial.
\", \"owner\": 1, \"expired\": false, \"live\": true, \"draft_title\": \"Getting Started: the Five Minute Install\", \"header_image\": null, \"go_live_at\": null, \"path\": \"000100020001\", \"numchild\": 0}", 130 | "approved_go_live_at": null 131 | } 132 | }, 133 | { 134 | "model": "wagtailcore.grouppagepermission", 135 | "pk": 1, 136 | "fields": { 137 | "group": 1, 138 | "page": 1, 139 | "permission_type": "add" 140 | } 141 | }, 142 | { 143 | "model": "wagtailcore.grouppagepermission", 144 | "pk": 2, 145 | "fields": { 146 | "group": 1, 147 | "page": 1, 148 | "permission_type": "edit" 149 | } 150 | }, 151 | { 152 | "model": "wagtailcore.grouppagepermission", 153 | "pk": 3, 154 | "fields": { 155 | "group": 1, 156 | "page": 1, 157 | "permission_type": "publish" 158 | } 159 | }, 160 | { 161 | "model": "wagtailcore.grouppagepermission", 162 | "pk": 4, 163 | "fields": { 164 | "group": 2, 165 | "page": 1, 166 | "permission_type": "add" 167 | } 168 | }, 169 | { 170 | "model": "wagtailcore.grouppagepermission", 171 | "pk": 5, 172 | "fields": { 173 | "group": 2, 174 | "page": 1, 175 | "permission_type": "edit" 176 | } 177 | }, 178 | { 179 | "model": "wagtailcore.grouppagepermission", 180 | "pk": 6, 181 | "fields": { 182 | "group": 1, 183 | "page": 1, 184 | "permission_type": "lock" 185 | } 186 | }, 187 | { 188 | "model": "wagtailcore.collection", 189 | "pk": 1, 190 | "fields": { 191 | "path": "0001", 192 | "depth": 1, 193 | "numchild": 0, 194 | "name": "Root" 195 | } 196 | }, 197 | { 198 | "model": "wagtailcore.groupcollectionpermission", 199 | "pk": 1, 200 | "fields": { 201 | "group": 2, 202 | "collection": 1, 203 | "permission": 1 204 | } 205 | }, 206 | { 207 | "model": "wagtailcore.groupcollectionpermission", 208 | "pk": 2, 209 | "fields": { 210 | "group": 1, 211 | "collection": 1, 212 | "permission": 1 213 | } 214 | }, 215 | { 216 | "model": "wagtailcore.groupcollectionpermission", 217 | "pk": 3, 218 | "fields": { 219 | "group": 2, 220 | "collection": 1, 221 | "permission": 2 222 | } 223 | }, 224 | { 225 | "model": "wagtailcore.groupcollectionpermission", 226 | "pk": 4, 227 | "fields": { 228 | "group": 1, 229 | "collection": 1, 230 | "permission": 2 231 | } 232 | }, 233 | { 234 | "model": "wagtailcore.groupcollectionpermission", 235 | "pk": 5, 236 | "fields": { 237 | "group": 2, 238 | "collection": 1, 239 | "permission": 5 240 | } 241 | }, 242 | { 243 | "model": "wagtailcore.groupcollectionpermission", 244 | "pk": 6, 245 | "fields": { 246 | "group": 1, 247 | "collection": 1, 248 | "permission": 5 249 | } 250 | }, 251 | { 252 | "model": "wagtailcore.groupcollectionpermission", 253 | "pk": 7, 254 | "fields": { 255 | "group": 2, 256 | "collection": 1, 257 | "permission": 6 258 | } 259 | }, 260 | { 261 | "model": "wagtailcore.groupcollectionpermission", 262 | "pk": 8, 263 | "fields": { 264 | "group": 1, 265 | "collection": 1, 266 | "permission": 6 267 | } 268 | }, 269 | { 270 | "model": "peregrine.sitepage", 271 | "pk": 3, 272 | "fields": { 273 | "body": "[{\"type\": \"heading\", \"value\": \"Built on Wagtail.\", \"id\": \"27fc57ed-ee72-43d8-8c28-198b66d2406f\"}, {\"type\": \"paragraph\", \"value\": \"Wagtail provides a beautiful CMS experience.
\", \"id\": \"ae315cb3-5929-479f-a252-2e3c874b060c\"}]", 274 | "header_image": null, 275 | "excerpt": "Peregrine is an opinionated blogging platform which uses the Wagtail CMS on the Django web framework. It uses Wagtail's fantastic StreamField feature to provide fully structured content body element blocks, completely separating content from the presentation layer (CSS, JS, and HTML).
" 276 | } 277 | }, 278 | { 279 | "model": "peregrine.sitepage", 280 | "pk": 4, 281 | "fields": { 282 | "body": "[{\"type\": \"heading\", \"value\": \"Create a Fresh Django Project\", \"id\": \"01682ba7-3481-476e-b578-7a8a7192072c\"}, {\"type\": \"code\", \"value\": {\"code\": \"mkvirtualenv my_blog\\r\\npip install peregrine\\r\\ndjango-admin startproject my_blog\\r\\ncd my_blog\", \"language\": \"bash\"}, \"id\": \"f22be326-089f-4169-a353-7ce18388bf5e\"}, {\"type\": \"heading\", \"value\": \"Modify Your Django Settings\", \"id\": \"c24769ae-e029-4ccc-b920-da3c844b3e0e\"}, {\"type\": \"paragraph\", \"value\": \"Your settings file will be located in\\u00a0my_blog/settings.py\\u00a0if you're using the default Django project layout created by the\\u00a0startproject\\u00a0command above. You can add these settings at the very bottom of the file.
\", \"id\": \"31ebd29a-7723-49b7-8b06-29668aa43650\"}, {\"type\": \"code\", \"value\": {\"code\": \"PEREGRINE_APPS = [\\r\\n 'wagtail.wagtailcore',\\r\\n 'wagtail.wagtailadmin',\\r\\n 'wagtail.wagtaildocs',\\r\\n 'wagtail.wagtailsnippets',\\r\\n 'wagtail.wagtailusers',\\r\\n 'wagtail.wagtailimages',\\r\\n 'wagtail.wagtailembeds',\\r\\n 'wagtail.wagtailsearch',\\r\\n 'wagtail.wagtailsites',\\r\\n 'wagtail.wagtailredirects',\\r\\n 'wagtail.wagtailforms',\\r\\n 'wagtail.contrib.table_block',\\r\\n\\r\\n 'bootstrap4',\\r\\n 'wagtailcodeblock',\\r\\n 'wagtailcontentstream',\\r\\n 'peregrine',\\r\\n 'taggit',\\r\\n 'modelcluster',\\r\\n]\\r\\n\\r\\nINSTALLED_APPS += PEREGRINE_APPS\\r\\n\\r\\nPEREGRINE_MIDDLEWARE = [\\r\\n 'wagtail.wagtailcore.middleware.SiteMiddleware',\\r\\n 'wagtail.wagtailredirects.middleware.RedirectMiddleware',\\r\\n]\\r\\n\\r\\nMIDDLEWARE += PEREGRINE_MIDDLEWARE\\r\\n\\r\\nWAGTAIL_SITE_NAME = 'My Blog'\", \"language\": \"python\"}, \"id\": \"7e4c79d0-4948-4a4c-ae48-f293289b9682\"}, {\"type\": \"heading\", \"value\": \"Setting Up URLs\", \"id\": \"1751a6eb-cd82-46b0-bcf0-0efad12ee3b0\"}, {\"type\": \"paragraph\", \"value\": \"Your URL route file will be located in\\u00a0my_blog/urls.py\\u00a0if you're using the default Django project layout created by the\\u00a0startproject\\u00a0command above. You can replace the entire file with this:
\", \"id\": \"2fb81ed9-dfd4-41b0-9ed3-ad64181e07b7\"}, {\"type\": \"code\", \"value\": {\"code\": \"from django.conf.urls import url, include\\r\\nfrom django.contrib import admin\\r\\n\\r\\nfrom wagtail.wagtailcore import urls as wagtail_urls\\r\\nfrom wagtail.wagtailadmin import urls as wagtailadmin_urls\\r\\nfrom wagtail.wagtaildocs import urls as wagtaildocs_urls\\r\\n\\r\\nurlpatterns = [\\r\\n url(r'^admin/', admin.site.urls),\\r\\n\\r\\n # Peregrine URLs for Wagtail\\r\\n url(r'^documents/', include(wagtaildocs_urls)),\\r\\n url(r'^cms/', include(wagtailadmin_urls)),\\r\\n url(r'', include(wagtail_urls)),\\r\\n]\", \"language\": \"python\"}, \"id\": \"5d4374e1-2848-4e5d-9285-372b04f3fff4\"}, {\"type\": \"heading\", \"value\": \"Fire it up!\", \"id\": \"c819c01a-a8c8-4536-bfd6-999d575e6a8c\"}, {\"type\": \"paragraph\", \"value\": \"After you've set up your settings, we need to create your database and a superuser. Issue the following commands from your project root directory.
Only run the command\\u00a0peregrine_initial_site\\u00a0if you are running on a new project, as it loads database fixtures!
\", \"id\": \"6a136620-52de-4704-9f24-e2d35e07ce21\"}, {\"type\": \"code\", \"value\": {\"code\": \"python manage.py migrate\\r\\npython manage.py peregrine_initial_site\\r\\npython manage.py createsuperuser\\r\\npython manage.py runserver 0:8000\", \"language\": \"bash\"}, \"id\": \"7c141684-0f1c-4b37-82dc-8a4967c5e5f8\"}, {\"type\": \"paragraph\", \"value\": \"You should then be able to navigate to http://localhost:8000/cms/ and log in, and start creating!
If you know a little bit about Django, it should be relatively straightforward to get started with Peregrine with this five-minute install. If you are new to Django, check out the excellent tutorial.
" 285 | } 286 | } 287 | ] 288 | -------------------------------------------------------------------------------- /peregrine/management/commands/peregrine_initial_site.py: -------------------------------------------------------------------------------- 1 | from django.core.management import call_command 2 | from django.core.management.base import BaseCommand 3 | from django.db import connections, DEFAULT_DB_ALIAS 4 | 5 | 6 | INITIAL_FIXTURES = [ 7 | "initial_site", 8 | ] 9 | 10 | 11 | class Command(BaseCommand): 12 | """ 13 | This loads various fixtures of data to populate an initial Peregrine 14 | site including help on getting started. 15 | """ 16 | 17 | def add_arguments(self, parser): 18 | parser.add_argument( 19 | "--database", 20 | action="store", 21 | dest="database", 22 | default=DEFAULT_DB_ALIAS, 23 | help='The database to use. Default to the "default" database.', 24 | ) 25 | 26 | def handle(self, *args, **options): 27 | self.verbosity = int(options.get("verbosity")) 28 | 29 | # Get the database we're working with 30 | db = options.get("database") 31 | connection = connections[db] 32 | 33 | for fixture in INITIAL_FIXTURES: 34 | print("Installing {}".format(fixture)) 35 | call_command( 36 | "loaddata", 37 | fixture, 38 | verbosity=self.verbosity, 39 | database=connection.alias, 40 | skip_validation=True, 41 | app_label="peregrine", 42 | hide_empty=True, 43 | ) 44 | -------------------------------------------------------------------------------- /peregrine/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.8.dev20171216134037 on 2017-12-16 14:06 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import django.utils.timezone 9 | import modelcluster.fields 10 | import wagtail.contrib.table_block.blocks 11 | import wagtail.blocks 12 | import wagtail.fields 13 | import wagtail.documents.blocks 14 | import wagtail.embeds.blocks 15 | import wagtail.images.blocks 16 | 17 | 18 | class Migration(migrations.Migration): 19 | 20 | initial = True 21 | 22 | dependencies = [ 23 | ("wagtailcore", "0040_page_draft_title"), 24 | ("wagtailimages", "0019_delete_filter"), 25 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 26 | ] 27 | 28 | operations = [ 29 | migrations.CreateModel( 30 | name="Category", 31 | fields=[ 32 | ( 33 | "id", 34 | models.AutoField( 35 | auto_created=True, 36 | primary_key=True, 37 | serialize=False, 38 | verbose_name="ID", 39 | ), 40 | ), 41 | ("name", models.CharField(max_length=255)), 42 | ], 43 | options={ 44 | "verbose_name_plural": "categories", 45 | }, 46 | ), 47 | migrations.CreateModel( 48 | name="SitePage", 49 | fields=[ 50 | ( 51 | "page_ptr", 52 | models.OneToOneField( 53 | auto_created=True, 54 | on_delete=django.db.models.deletion.CASCADE, 55 | parent_link=True, 56 | primary_key=True, 57 | serialize=False, 58 | to="wagtailcore.Page", 59 | ), 60 | ), 61 | ( 62 | "body", 63 | wagtail.fields.StreamField( 64 | ( 65 | ( 66 | "heading", 67 | wagtail.blocks.TextBlock( 68 | icon="title", 69 | template="wagtailcontentstream/blocks/heading.html", 70 | ), 71 | ), 72 | ( 73 | "paragraph", 74 | wagtail.blocks.RichTextBlock( 75 | features=["bold", "italic", "link", "ol", "ul"], 76 | icon="pilcrow", 77 | ), 78 | ), 79 | ( 80 | "image", 81 | wagtail.blocks.StructBlock( 82 | ( 83 | ( 84 | "image", 85 | wagtail.images.blocks.ImageChooserBlock(), 86 | ), 87 | ( 88 | "caption", 89 | wagtail.blocks.TextBlock( 90 | required=False 91 | ), 92 | ), 93 | ) 94 | ), 95 | ), 96 | ( 97 | "document", 98 | wagtail.documents.blocks.DocumentChooserBlock(), 99 | ), 100 | ("embed", wagtail.embeds.blocks.EmbedBlock(icon="media")), 101 | ( 102 | "table", 103 | wagtail.contrib.table_block.blocks.TableBlock( 104 | icon="table" 105 | ), 106 | ), 107 | ( 108 | "code", 109 | wagtail.blocks.StructBlock( 110 | ( 111 | ( 112 | "language", 113 | wagtail.blocks.ChoiceBlock( 114 | choices=[ 115 | ("bash", "Bash/Shell"), 116 | ("css", "CSS"), 117 | ("diff", "diff"), 118 | ("http", "HTML"), 119 | ("javascript", "Javascript"), 120 | ("json", "JSON"), 121 | ("python", "Python"), 122 | ("scss", "SCSS"), 123 | ("yaml", "YAML"), 124 | ], 125 | help_text="Coding language", 126 | label="Language", 127 | ), 128 | ), 129 | ( 130 | "code", 131 | wagtail.blocks.TextBlock(label="Code"), 132 | ), 133 | ), 134 | icon="code", 135 | ), 136 | ), 137 | ), 138 | blank=True, 139 | ), 140 | ), 141 | ( 142 | "excerpt", 143 | wagtail.fields.RichTextField( 144 | blank=True, 145 | help_text="An short excerpt or abstract about the content.", 146 | null=True, 147 | ), 148 | ), 149 | ], 150 | options={ 151 | "verbose_name": "Page", 152 | }, 153 | bases=("wagtailcore.page",), 154 | ), 155 | migrations.CreateModel( 156 | name="SitePost", 157 | fields=[ 158 | ( 159 | "sitepage_ptr", 160 | models.OneToOneField( 161 | auto_created=True, 162 | on_delete=django.db.models.deletion.CASCADE, 163 | parent_link=True, 164 | primary_key=True, 165 | serialize=False, 166 | to="peregrine.SitePage", 167 | ), 168 | ), 169 | ( 170 | "post_date", 171 | models.DateTimeField( 172 | default=django.utils.timezone.now, 173 | help_text="The date and time of the post.", 174 | ), 175 | ), 176 | ], 177 | options={ 178 | "verbose_name": "Post", 179 | }, 180 | bases=("peregrine.sitepage",), 181 | ), 182 | migrations.AddField( 183 | model_name="sitepage", 184 | name="categories", 185 | field=modelcluster.fields.ParentalManyToManyField( 186 | blank=True, 187 | help_text="The categories for the page or post.", 188 | to="peregrine.Category", 189 | ), 190 | ), 191 | migrations.AddField( 192 | model_name="sitepage", 193 | name="header_image", 194 | field=models.ForeignKey( 195 | blank=True, 196 | help_text="A featured image that will appear in the site theme header.", 197 | null=True, 198 | on_delete=django.db.models.deletion.SET_NULL, 199 | related_name="+", 200 | to="wagtailimages.Image", 201 | ), 202 | ), 203 | migrations.AddField( 204 | model_name="sitepost", 205 | name="authors", 206 | field=modelcluster.fields.ParentalManyToManyField( 207 | blank=True, 208 | help_text="The authors of the post.", 209 | to=settings.AUTH_USER_MODEL, 210 | ), 211 | ), 212 | ] 213 | -------------------------------------------------------------------------------- /peregrine/migrations/0002_auto_20171216_1408.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.8.dev20171216134037 on 2017-12-16 14:08 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ("wagtailcore", "0040_page_draft_title"), 13 | ("peregrine", "0001_initial"), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name="PeregrineSettings", 19 | fields=[ 20 | ( 21 | "id", 22 | models.AutoField( 23 | auto_created=True, 24 | primary_key=True, 25 | serialize=False, 26 | verbose_name="ID", 27 | ), 28 | ), 29 | ( 30 | "post_number", 31 | models.IntegerField( 32 | default=10, help_text="The number of posts to display." 33 | ), 34 | ), 35 | ( 36 | "post_number_nav", 37 | models.IntegerField( 38 | default=10, 39 | help_text="The number of posts to display in navigation.", 40 | ), 41 | ), 42 | ], 43 | options={ 44 | "abstract": False, 45 | }, 46 | ), 47 | migrations.CreateModel( 48 | name="Settings", 49 | fields=[ 50 | ( 51 | "id", 52 | models.AutoField( 53 | auto_created=True, 54 | primary_key=True, 55 | serialize=False, 56 | verbose_name="ID", 57 | ), 58 | ), 59 | ("key", models.CharField(max_length=255, unique=True)), 60 | ("value", models.CharField(max_length=255)), 61 | ], 62 | ), 63 | migrations.AddIndex( 64 | model_name="settings", 65 | index=models.Index(fields=["key"], name="settings_key_idx"), 66 | ), 67 | migrations.AddField( 68 | model_name="peregrinesettings", 69 | name="landing_page", 70 | field=models.ForeignKey( 71 | blank=True, 72 | help_text="The page to display at the root. If blank, displays latest posts.", 73 | null=True, 74 | on_delete=django.db.models.deletion.SET_NULL, 75 | to="wagtailcore.Page", 76 | ), 77 | ), 78 | migrations.AddField( 79 | model_name="peregrinesettings", 80 | name="site", 81 | field=models.OneToOneField( 82 | editable=False, 83 | on_delete=django.db.models.deletion.CASCADE, 84 | to="wagtailcore.Site", 85 | ), 86 | ), 87 | ] 88 | -------------------------------------------------------------------------------- /peregrine/migrations/0003_auto_20171217_1706.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2017-12-17 17:06 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 | ("peregrine", "0002_auto_20171216_1408"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="peregrinesettings", 16 | name="post_number_rss", 17 | field=models.IntegerField( 18 | default=100, help_text="The number of posts to include in the RSS feed." 19 | ), 20 | ), 21 | migrations.AlterField( 22 | model_name="peregrinesettings", 23 | name="landing_page", 24 | field=models.ForeignKey( 25 | blank=True, 26 | help_text="The page to display at the root. If blank, displays the latest posts.", 27 | null=True, 28 | on_delete=django.db.models.deletion.SET_NULL, 29 | to="wagtailcore.Page", 30 | ), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /peregrine/migrations/0004_auto_20180105_1315.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-01-05 13:15 2 | 3 | from django.db import migrations, models 4 | import wagtail.contrib.table_block.blocks 5 | import wagtail.blocks 6 | import wagtail.fields 7 | import wagtail.documents.blocks 8 | import wagtail.embeds.blocks 9 | import wagtail.images.blocks 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ("peregrine", "0003_auto_20171217_1706"), 16 | ] 17 | 18 | operations = [ 19 | migrations.AddField( 20 | model_name="peregrinesettings", 21 | name="post_title", 22 | field=models.CharField( 23 | default="Posts", 24 | help_text="The menu text label for latest posts.", 25 | max_length=30, 26 | ), 27 | ), 28 | migrations.AlterField( 29 | model_name="sitepage", 30 | name="body", 31 | field=wagtail.fields.StreamField( 32 | ( 33 | ( 34 | "heading", 35 | wagtail.blocks.TextBlock( 36 | icon="title", 37 | template="wagtailcontentstream/blocks/heading.html", 38 | ), 39 | ), 40 | ( 41 | "paragraph", 42 | wagtail.blocks.RichTextBlock( 43 | features=["bold", "italic", "link", "ol", "ul"], 44 | icon="pilcrow", 45 | ), 46 | ), 47 | ( 48 | "image", 49 | wagtail.blocks.StructBlock( 50 | ( 51 | ( 52 | "image", 53 | wagtail.images.blocks.ImageChooserBlock( 54 | help_text="The image to display." 55 | ), 56 | ), 57 | ( 58 | "caption", 59 | wagtail.blocks.TextBlock( 60 | help_text="The caption will appear under the image, if entered.", 61 | required=False, 62 | ), 63 | ), 64 | ( 65 | "credit", 66 | wagtail.blocks.TextBlock( 67 | help_text="The credit will appear under the image, if entered.", 68 | required=False, 69 | ), 70 | ), 71 | ( 72 | "align", 73 | wagtail.blocks.ChoiceBlock( 74 | choices=[ 75 | ("left", "Left"), 76 | ("right", "Right"), 77 | ("center", "Center"), 78 | ("full", "Full Width"), 79 | ], 80 | help_text="How to align the image in the body of the page.", 81 | ), 82 | ), 83 | ) 84 | ), 85 | ), 86 | ("document", wagtail.documents.blocks.DocumentChooserBlock()), 87 | ("embed", wagtail.embeds.blocks.EmbedBlock(icon="media")), 88 | ( 89 | "table", 90 | wagtail.contrib.table_block.blocks.TableBlock(icon="table"), 91 | ), 92 | ( 93 | "code", 94 | wagtail.blocks.StructBlock( 95 | ( 96 | ( 97 | "language", 98 | wagtail.blocks.ChoiceBlock( 99 | choices=[ 100 | ("bash", "Bash/Shell"), 101 | ("css", "CSS"), 102 | ("diff", "diff"), 103 | ("http", "HTML"), 104 | ("javascript", "Javascript"), 105 | ("json", "JSON"), 106 | ("python", "Python"), 107 | ("scss", "SCSS"), 108 | ("yaml", "YAML"), 109 | ], 110 | help_text="Coding language", 111 | label="Language", 112 | ), 113 | ), 114 | ("code", wagtail.blocks.TextBlock(label="Code")), 115 | ), 116 | icon="code", 117 | ), 118 | ), 119 | ), 120 | blank=True, 121 | ), 122 | ), 123 | ] 124 | -------------------------------------------------------------------------------- /peregrine/migrations/0005_auto_20180418_1637.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.4 on 2018-04-18 16:37 2 | 3 | from django.db import migrations 4 | import wagtail.contrib.table_block.blocks 5 | import wagtail.blocks 6 | import wagtail.fields 7 | import wagtail.documents.blocks 8 | import wagtail.embeds.blocks 9 | import wagtail.images.blocks 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ("peregrine", "0004_auto_20180105_1315"), 16 | ] 17 | 18 | operations = [ 19 | migrations.DeleteModel( 20 | name="Settings", 21 | ), 22 | migrations.AlterModelOptions( 23 | name="peregrinesettings", 24 | options={"verbose_name": "Peregrine Settings"}, 25 | ), 26 | migrations.AlterField( 27 | model_name="sitepage", 28 | name="body", 29 | field=wagtail.fields.StreamField( 30 | ( 31 | ( 32 | "heading", 33 | wagtail.blocks.TextBlock( 34 | icon="title", 35 | template="wagtailcontentstream/blocks/heading.html", 36 | ), 37 | ), 38 | ( 39 | "paragraph", 40 | wagtail.blocks.RichTextBlock( 41 | features=[ 42 | "bold", 43 | "italic", 44 | "link", 45 | "ol", 46 | "ul", 47 | "monospace", 48 | ], 49 | icon="pilcrow", 50 | ), 51 | ), 52 | ( 53 | "image", 54 | wagtail.blocks.StructBlock( 55 | ( 56 | ( 57 | "image", 58 | wagtail.images.blocks.ImageChooserBlock( 59 | help_text="The image to display." 60 | ), 61 | ), 62 | ( 63 | "caption", 64 | wagtail.blocks.TextBlock( 65 | help_text="The caption will appear under the image, if entered.", 66 | required=False, 67 | ), 68 | ), 69 | ( 70 | "credit", 71 | wagtail.blocks.TextBlock( 72 | help_text="The credit will appear under the image, if entered.", 73 | required=False, 74 | ), 75 | ), 76 | ( 77 | "align", 78 | wagtail.blocks.ChoiceBlock( 79 | choices=[ 80 | ("left", "Left"), 81 | ("right", "Right"), 82 | ("center", "Center"), 83 | ("full", "Full Width"), 84 | ], 85 | help_text="How to align the image in the body of the page.", 86 | ), 87 | ), 88 | ) 89 | ), 90 | ), 91 | ("document", wagtail.documents.blocks.DocumentChooserBlock()), 92 | ("embed", wagtail.embeds.blocks.EmbedBlock(icon="media")), 93 | ( 94 | "table", 95 | wagtail.contrib.table_block.blocks.TableBlock(icon="table"), 96 | ), 97 | ( 98 | "code", 99 | wagtail.blocks.StructBlock( 100 | ( 101 | ( 102 | "language", 103 | wagtail.blocks.ChoiceBlock( 104 | choices=[ 105 | ("bash", "Bash/Shell"), 106 | ("css", "CSS"), 107 | ("diff", "diff"), 108 | ("http", "HTML"), 109 | ("javascript", "Javascript"), 110 | ("json", "JSON"), 111 | ("python", "Python"), 112 | ("scss", "SCSS"), 113 | ("yaml", "YAML"), 114 | ], 115 | help_text="Coding language", 116 | label="Language", 117 | ), 118 | ), 119 | ("code", wagtail.blocks.TextBlock(label="Code")), 120 | ), 121 | icon="code", 122 | ), 123 | ), 124 | ), 125 | blank=True, 126 | ), 127 | ), 128 | ] 129 | -------------------------------------------------------------------------------- /peregrine/migrations/0006_auto_20180418_1652.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.4 on 2018-04-18 16:52 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 | ("peregrine", "0005_auto_20180418_1637"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="peregrinesettings", 16 | name="landing_page", 17 | field=models.ForeignKey( 18 | blank=True, 19 | help_text="The front page for Peregrine to display. If blank, displays the latest posts.", 20 | null=True, 21 | on_delete=django.db.models.deletion.SET_NULL, 22 | to="wagtailcore.Page", 23 | ), 24 | ), 25 | migrations.AlterField( 26 | model_name="peregrinesettings", 27 | name="post_number", 28 | field=models.IntegerField( 29 | default=10, 30 | help_text="The number of posts to display on the posts page.", 31 | ), 32 | ), 33 | migrations.AlterField( 34 | model_name="peregrinesettings", 35 | name="post_number_nav", 36 | field=models.IntegerField( 37 | default=10, 38 | help_text="The number of posts to display in top navigation dropdown.", 39 | ), 40 | ), 41 | migrations.AlterField( 42 | model_name="peregrinesettings", 43 | name="post_title", 44 | field=models.CharField( 45 | default="Posts", 46 | help_text="The top navigation menu dropdown text label for latest posts.", 47 | max_length=30, 48 | ), 49 | ), 50 | ] 51 | -------------------------------------------------------------------------------- /peregrine/migrations/0007_auto_20180523_1908.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-05-23 19:08 2 | 3 | from django.db import migrations 4 | import wagtail.contrib.table_block.blocks 5 | import wagtail.blocks 6 | import wagtail.fields 7 | import wagtail.documents.blocks 8 | import wagtail.embeds.blocks 9 | import wagtail.images.blocks 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ("peregrine", "0006_auto_20180418_1652"), 16 | ] 17 | 18 | operations = [ 19 | migrations.AlterField( 20 | model_name="sitepage", 21 | name="body", 22 | field=wagtail.fields.StreamField( 23 | [ 24 | ( 25 | "heading", 26 | wagtail.blocks.TextBlock( 27 | icon="title", 28 | template="wagtailcontentstream/blocks/heading.html", 29 | ), 30 | ), 31 | ( 32 | "paragraph", 33 | wagtail.blocks.RichTextBlock( 34 | features=[ 35 | "bold", 36 | "italic", 37 | "link", 38 | "ol", 39 | "ul", 40 | "monospace", 41 | ], 42 | icon="pilcrow", 43 | ), 44 | ), 45 | ( 46 | "image", 47 | wagtail.blocks.StructBlock( 48 | [ 49 | ( 50 | "image", 51 | wagtail.images.blocks.ImageChooserBlock( 52 | help_text="The image to display." 53 | ), 54 | ), 55 | ( 56 | "caption", 57 | wagtail.blocks.TextBlock( 58 | help_text="The caption will appear under the image, if entered.", 59 | required=False, 60 | ), 61 | ), 62 | ( 63 | "credit", 64 | wagtail.blocks.TextBlock( 65 | help_text="The credit will appear under the image, if entered.", 66 | required=False, 67 | ), 68 | ), 69 | ( 70 | "align", 71 | wagtail.blocks.ChoiceBlock( 72 | choices=[ 73 | ("left", "Left"), 74 | ("right", "Right"), 75 | ("center", "Center"), 76 | ("full", "Full Width"), 77 | ], 78 | help_text="How to align the image in the body of the page.", 79 | ), 80 | ), 81 | ] 82 | ), 83 | ), 84 | ("document", wagtail.documents.blocks.DocumentChooserBlock()), 85 | ("embed", wagtail.embeds.blocks.EmbedBlock(icon="media")), 86 | ( 87 | "table", 88 | wagtail.contrib.table_block.blocks.TableBlock(icon="table"), 89 | ), 90 | ( 91 | "code", 92 | wagtail.blocks.StructBlock( 93 | [ 94 | ( 95 | "language", 96 | wagtail.blocks.ChoiceBlock( 97 | choices=[ 98 | ("bash", "Bash/Shell"), 99 | ("css", "CSS"), 100 | ("diff", "diff"), 101 | ("html", "HTML"), 102 | ("javascript", "Javascript"), 103 | ("json", "JSON"), 104 | ("python", "Python"), 105 | ("scss", "SCSS"), 106 | ("yaml", "YAML"), 107 | ], 108 | help_text="Coding language", 109 | label="Language", 110 | ), 111 | ), 112 | ("code", wagtail.blocks.TextBlock(label="Code")), 113 | ], 114 | icon="code", 115 | ), 116 | ), 117 | ], 118 | blank=True, 119 | ), 120 | ), 121 | ] 122 | -------------------------------------------------------------------------------- /peregrine/migrations/0008_peregrinesettings_revisions_to_keep.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.5 on 2018-05-27 15:45 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("peregrine", "0007_auto_20180523_1908"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="peregrinesettings", 15 | name="revisions_to_keep", 16 | field=models.IntegerField( 17 | blank=True, 18 | default=None, 19 | help_text="The number of revisions to keep. If None, keeps all revisions.", 20 | null=True, 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /peregrine/migrations/0009_auto_20211105_1634.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.9 on 2021-11-05 16:34 2 | 3 | from django.db import migrations, models 4 | import wagtail.contrib.table_block.blocks 5 | import wagtail.blocks 6 | import wagtail.fields 7 | import wagtail.documents.blocks 8 | import wagtail.embeds.blocks 9 | import wagtail.images.blocks 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ('peregrine', '0008_peregrinesettings_revisions_to_keep'), 16 | ] 17 | 18 | operations = [ 19 | migrations.AlterField( 20 | model_name='category', 21 | name='id', 22 | field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 23 | ), 24 | migrations.AlterField( 25 | model_name='peregrinesettings', 26 | name='id', 27 | field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 28 | ), 29 | migrations.AlterField( 30 | model_name='sitepage', 31 | name='body', 32 | field=wagtail.fields.StreamField([('heading', wagtail.blocks.TextBlock(icon='title', template='wagtailcontentstream/blocks/heading.html')), ('paragraph', wagtail.blocks.RichTextBlock(features=['bold', 'italic', 'link', 'ol', 'ul', 'monospace'], icon='pilcrow')), ('image', wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(help_text='The image to display.')), ('caption', wagtail.blocks.TextBlock(help_text='The caption will appear under the image, if entered.', required=False)), ('credit', wagtail.blocks.TextBlock(help_text='The credit will appear under the image, if entered.', required=False)), ('align', wagtail.blocks.ChoiceBlock(choices=[('left', 'Left'), ('right', 'Right'), ('center', 'Center'), ('full', 'Full Width')], help_text='How to align the image in the body of the page.'))])), ('document', wagtail.documents.blocks.DocumentChooserBlock()), ('embed', wagtail.embeds.blocks.EmbedBlock(icon='media')), ('table', wagtail.contrib.table_block.blocks.TableBlock(icon='table')), ('code', wagtail.blocks.StructBlock([('language', wagtail.blocks.ChoiceBlock(choices=[('bash', 'Bash/Shell'), ('css', 'CSS'), ('diff', 'diff'), ('html', 'HTML'), ('javascript', 'Javascript'), ('json', 'JSON'), ('python', 'Python'), ('scss', 'SCSS'), ('yaml', 'YAML')], help_text='Coding language', identifier='language', label='Language')), ('code', wagtail.blocks.TextBlock(identifier='code', label='Code'))], icon='code'))], blank=True), 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /peregrine/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlipperPA/peregrine/59cf20bdffc1b4fabcefcd84041ae7e6f63ca221/peregrine/migrations/__init__.py -------------------------------------------------------------------------------- /peregrine/models.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import models 3 | from django.forms import CheckboxSelectMultiple 4 | from django.utils.timezone import now 5 | 6 | from modelcluster.fields import ParentalManyToManyField 7 | 8 | from wagtail.admin.panels import FieldPanel, StreamFieldPanel 9 | from wagtail.contrib.settings.models import BaseSetting, register_setting 10 | from wagtail.fields import RichTextField 11 | from wagtail.images.edit_handlers import ImageChooserPanel 12 | from wagtail.search import index 13 | 14 | from wagtailcontentstream.models import ContentStreamPage 15 | 16 | 17 | class Category(models.Model): 18 | """ 19 | Categories which a Page or Post item can belong to. A page can belong to 20 | many categories. 21 | """ 22 | 23 | name = models.CharField(max_length=255) 24 | 25 | class Meta: 26 | verbose_name_plural = "categories" 27 | 28 | def __str__(self): 29 | return self.name 30 | 31 | 32 | class SitePage(ContentStreamPage): 33 | header_image = models.ForeignKey( 34 | "wagtailimages.Image", 35 | null=True, 36 | blank=True, 37 | on_delete=models.SET_NULL, 38 | related_name="+", 39 | help_text="A featured image that will appear in the site theme header.", 40 | ) 41 | excerpt = RichTextField( 42 | features=["bold", "italic", "link", "ol", "ul", "monospace"], 43 | blank=True, 44 | null=True, 45 | help_text="An short excerpt or abstract about the content.", 46 | ) 47 | categories = ParentalManyToManyField( 48 | "Category", 49 | blank=True, 50 | help_text="The categories for the page or post.", 51 | ) 52 | show_in_menus_default = True 53 | 54 | search_fields = ContentStreamPage.search_fields + [ 55 | index.SearchField("body"), 56 | index.SearchField("excerpt"), 57 | ] 58 | 59 | content_panels = [ 60 | FieldPanel("title"), 61 | ImageChooserPanel("header_image"), 62 | FieldPanel("excerpt"), 63 | FieldPanel("categories", widget=CheckboxSelectMultiple), 64 | StreamFieldPanel("body"), 65 | ] 66 | 67 | class Meta: 68 | verbose_name = "Page" 69 | 70 | 71 | class SitePost(SitePage): 72 | post_date = models.DateTimeField( 73 | default=now, 74 | help_text="The date and time of the post.", 75 | ) 76 | authors = ParentalManyToManyField( 77 | settings.AUTH_USER_MODEL, 78 | blank=True, 79 | help_text="The authors of the post.", 80 | ) 81 | show_in_menus_default = True 82 | 83 | content_panels = [ 84 | FieldPanel("title"), 85 | FieldPanel("post_date"), 86 | FieldPanel("authors"), 87 | ImageChooserPanel("header_image"), 88 | FieldPanel("excerpt"), 89 | FieldPanel("categories", widget=CheckboxSelectMultiple), 90 | StreamFieldPanel("body"), 91 | ] 92 | 93 | class Meta: 94 | verbose_name = "Post" 95 | 96 | 97 | @register_setting 98 | class PeregrineSettings(BaseSetting): 99 | """ 100 | Settings for the user to customize their Peregrine blog. 101 | """ 102 | 103 | landing_page = models.ForeignKey( 104 | "wagtailcore.Page", 105 | null=True, 106 | blank=True, 107 | on_delete=models.SET_NULL, 108 | help_text="The front page for Peregrine to display. If blank, displays the latest posts.", 109 | ) 110 | post_title = models.CharField( 111 | max_length=30, 112 | default="Posts", 113 | help_text="The top navigation menu dropdown text label for latest posts.", 114 | ) 115 | post_number = models.IntegerField( 116 | default=10, 117 | help_text="The number of posts to display on the posts page.", 118 | ) 119 | post_number_nav = models.IntegerField( 120 | default=10, 121 | help_text="The number of posts to display in top navigation dropdown.", 122 | ) 123 | post_number_rss = models.IntegerField( 124 | default=100, 125 | help_text="The number of posts to include in the RSS feed.", 126 | ) 127 | revisions_to_keep = models.IntegerField( 128 | default=None, 129 | blank=True, 130 | null=True, 131 | help_text="The number of revisions to keep. If None, keeps all revisions.", 132 | ) 133 | 134 | class Meta: 135 | verbose_name = "Peregrine Settings" 136 | -------------------------------------------------------------------------------- /peregrine/static/peregrine/peregrine-style.css: -------------------------------------------------------------------------------- 1 | .fixed-nav { 2 | padding-top: 60px; 3 | } 4 | 5 | .heading { 6 | border-bottom: 1px dotted; 7 | padding-top: 1rem; 8 | padding-bottom: 0.25rem; 9 | } 10 | 11 | .figure { 12 | z-index: 9999; 13 | } 14 | 15 | header { 16 | text-align: center; 17 | padding: 100px 0 100px 0; 18 | background-attachment: scroll; 19 | background-position: center center; 20 | background-repeat: none; 21 | -webkit-background-size: cover; 22 | -moz-background-size: cover; 23 | background-size: cover; 24 | -o-background-size: cover; 25 | } 26 | 27 | header .intro-text { 28 | padding-top: 5px; 29 | padding-bottom: 5px; 30 | margin-bottom: 5px; 31 | text-shadow: -1px 0 #777, 0 1px #777, 1px 0 #777, 0 -1px #777; 32 | } 33 | 34 | header .intro-text .intro-heading { 35 | font-size: 30px; 36 | } 37 | 38 | header .intro-text .intro-lead-in { 39 | font-size: 18px; 40 | } 41 | -------------------------------------------------------------------------------- /peregrine/static/peregrine/peregrine-style.min.css: -------------------------------------------------------------------------------- 1 | .fixed-nav{padding-top:60px}.heading{border-bottom:1px dotted;padding-top:1rem;padding-bottom:.25rem} -------------------------------------------------------------------------------- /peregrine/static/peregrine/peregrine-style.min.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlipperPA/peregrine/59cf20bdffc1b4fabcefcd84041ae7e6f63ca221/peregrine/static/peregrine/peregrine-style.min.css.gz -------------------------------------------------------------------------------- /peregrine/templates/peregrine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlipperPA/peregrine/59cf20bdffc1b4fabcefcd84041ae7e6f63ca221/peregrine/templates/peregrine/__init__.py -------------------------------------------------------------------------------- /peregrine/templates/peregrine/base.html: -------------------------------------------------------------------------------- 1 | {% load static bootstrap4 peregrine wagtailsettings_tags %} 2 | 3 | 4 | 5 | 6 | 7 | {% spaceless %} 8 | 9 | 10 | 11 | 12 | 13 | {# #} 14 |{{ page.excerpt|safe }}
46 | {% block jumbotron_bottom %} 47 | {% endblock jumbotron_bottom %} 48 | {% for category in page.categories.all %} 49 | {{ category.name }} 50 | {% endfor %} 51 |6 | by 7 | {% for author in page.authors.all %} 8 | {% if not forloop.first %} 9 | , 10 | {% endif %} 11 | {{ author.username }} 12 | {% empty %} 13 | Nobody 14 | {% endfor %} 15 | on {{ page.post_date }} 16 |
17 | {% endblock jumbotron_bottom %} 18 | -------------------------------------------------------------------------------- /peregrine/templates/peregrine/site_post_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'peregrine/base.html' %} 2 | 3 | {% block content %} 4 | {% if author or category %} 5 |37 | {{ post.excerpt|safe }} 38 |
39 |There aren't any live posts yet!
73 | {% endif %} 74 |