├── .gitignore ├── README.md ├── analytics-config.json.skel ├── config.py.skel ├── deploy.sh ├── docs ├── admin-dashboard.gif ├── admin-dashboard.png ├── game-review.png ├── home-page.png ├── mobile-menu.png └── podcast-manager.png ├── event ├── __init__.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_event_type.py │ ├── 0003_auto_20200825_2001.py │ ├── 0004_event_end_date.py │ └── __init__.py ├── models.py ├── templates │ └── event │ │ ├── calendar.html │ │ └── event.html ├── urls.py ├── views.py └── wagtail_hooks.py ├── game ├── __init__.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20191201_0749.py │ ├── 0003_reviewcodes.py │ ├── 0004_reviewcodes_redeemed_by.py │ ├── 0005_auto_20191201_1839.py │ ├── 0006_auto_20191201_1917.py │ ├── 0007_auto_20191202_0041.py │ ├── 0008_game_designer.py │ ├── 0009_auto_20200502_1803.py │ ├── 0010_auto_20200502_1817.py │ ├── 0011_game_type.py │ ├── 0012_auto_20200506_0600.py │ ├── 0013_auto_20200506_0745.py │ ├── 0014_auto_20200506_1646.py │ ├── 0015_auto_20200506_1648.py │ └── __init__.py ├── models.py ├── site_summary.py ├── templates │ └── wagtailadmin │ │ └── home │ │ └── site_summary_game.html └── wagtail_hooks.py ├── gulpfile.js ├── home ├── __init__.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py └── templates │ └── home │ ├── hero_page_listing.html │ └── home_page.html ├── image ├── migrations │ ├── 0001_initial.py │ ├── 0002_customimage_image_credit.py │ ├── 0003_customimage_game.py │ └── __init__.py └── models.py ├── install.py ├── manage.py ├── package.json ├── page ├── __init__.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_blogpage_header_image.py │ ├── 0003_auto_20191201_0756.py │ ├── 0004_auto_20191201_0829.py │ ├── 0005_blogpage_author.py │ ├── 0006_auto_20200416_2250.py │ ├── 0007_auto_20200416_2254.py │ ├── 0008_auto_20200416_2307.py │ ├── 0009_blogfolder_folder_icon.py │ ├── 0010_auto_20200424_0451.py │ ├── 0011_auto_20200424_2329.py │ ├── 0012_auto_20200501_1841.py │ ├── 0013_auto_20200501_1856.py │ ├── 0014_blogpage_legacy_url.py │ ├── 0015_auto_20200501_2340.py │ ├── 0016_auto_20200502_0226.py │ ├── 0017_auto_20200502_0248.py │ ├── 0018_blogpage_header_video.py │ ├── 0019_auto_20200505_1848.py │ ├── 0020_auto_20200505_1928.py │ ├── 0021_auto_20200505_1952.py │ ├── 0022_blogpage_enable_comments.py │ ├── 0023_auto_20200506_0155.py │ ├── 0024_auto_20200510_1956.py │ └── __init__.py ├── models.py ├── templates │ ├── blocks │ │ ├── image_block.html │ │ └── review_block.html │ ├── blog │ │ ├── large_listing.html │ │ ├── medium_listing.html │ │ ├── meta_tags.html │ │ ├── sidebar_listing.html │ │ ├── small_listing.html │ │ └── tags.html │ └── page │ │ ├── base_page.html │ │ ├── basic_page.html │ │ ├── blog_folder.html │ │ ├── blog_page.html │ │ ├── tag_folder.html │ │ ├── tag_index.html │ │ ├── tag_page.html │ │ └── user_page.html ├── views.py └── wagtail_hooks.py ├── podcast ├── __init__.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_podcastsettings.py │ ├── 0003_podcastsettings_description.py │ ├── 0004_auto_20190908_0158.py │ ├── 0005_podcast_file.py │ ├── 0006_podcast_publish_date.py │ ├── 0007_podcast_related_page.py │ └── __init__.py ├── models.py ├── site_summary.py ├── templates │ ├── podcast │ │ ├── list.html │ │ ├── player.html │ │ ├── podcast.xml │ │ ├── results.html │ │ └── type_index.html │ └── wagtailadmin │ │ └── home │ │ └── site_summary_podcast.html ├── urls.py ├── views.py └── wagtail_hooks.py ├── requirements.txt ├── search ├── __init__.py ├── templates │ └── search │ │ ├── search.html │ │ └── search_form.html └── views.py ├── snippet ├── __init__.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_delete_game.py │ └── __init__.py ├── templates │ └── wagtailsnippets │ │ └── snippets │ │ ├── list.html │ │ ├── results.html │ │ └── type_index.html ├── urls.py └── views.py ├── spritesanddice ├── __init__.py ├── context_processors.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_metadatasettings.py │ ├── 0003_sidebarsettings.py │ ├── 0004_delete_sidebarsettings.py │ ├── 0005_auto_20200509_2228.py │ └── __init__.py ├── models.py ├── settings │ ├── __init__.py │ ├── base.py │ ├── dev.py │ └── production.py ├── static │ ├── css │ │ ├── admin.css │ │ ├── main.css │ │ └── reflex.css │ ├── favicon.ico │ ├── fonts │ │ ├── Roboto-Bold.ttf │ │ ├── Roboto-BoldItalic.ttf │ │ ├── Roboto-Italic.ttf │ │ ├── Roboto-Light.ttf │ │ ├── Roboto-LightItalic.ttf │ │ ├── Roboto-Medium.ttf │ │ ├── Roboto-MediumItalic.ttf │ │ ├── Roboto-Regular.ttf │ │ ├── Roboto-Thin.ttf │ │ ├── Roboto-ThinItalic.ttf │ │ ├── RobotoSlab-Black.ttf │ │ ├── RobotoSlab-Bold.ttf │ │ ├── RobotoSlab-ExtraBold.ttf │ │ ├── RobotoSlab-ExtraLight.ttf │ │ ├── RobotoSlab-Light.ttf │ │ ├── RobotoSlab-Medium.ttf │ │ ├── RobotoSlab-Regular.ttf │ │ ├── RobotoSlab-SemiBold.ttf │ │ ├── RobotoSlab-Thin.ttf │ │ ├── RobotoSlab-VariableFont_wght.ttf │ │ ├── jaapokki-regular.woff │ │ ├── opensans-regular-webfont.woff │ │ └── wagtail.woff │ ├── img │ │ ├── logo-black.png │ │ ├── logo-white.png │ │ ├── podcast.jpg │ │ ├── sd-logo-black.png │ │ ├── sd-logo-white.png │ │ └── snd_end_mark.png │ ├── js │ │ ├── admin.js │ │ ├── jquery.js │ │ ├── navigation.js │ │ └── spritesanddice.js │ ├── scss │ │ ├── admin │ │ │ ├── admin.scss │ │ │ ├── analytics.scss │ │ │ ├── home.scss │ │ │ ├── login.scss │ │ │ ├── podcast.scss │ │ │ ├── summary.scss │ │ │ └── typography.scss │ │ ├── colors.scss │ │ ├── fonts.scss │ │ ├── icons.scss │ │ ├── main │ │ │ ├── author.scss │ │ │ ├── blog-page.scss │ │ │ ├── calendar.scss │ │ │ ├── home-page.scss │ │ │ ├── main.scss │ │ │ ├── navigation │ │ │ │ ├── footer.scss │ │ │ │ ├── header.scss │ │ │ │ ├── navbar.scss │ │ │ │ └── sidebar.scss │ │ │ ├── password-required.scss │ │ │ ├── podcast.scss │ │ │ ├── search.scss │ │ │ └── typography.scss │ │ ├── userbar.scss │ │ └── variables.scss │ ├── svg │ │ └── logo-big.svg │ └── wagtailadmin │ │ └── css │ │ └── userbar.css ├── stream_blocks.py ├── templates │ ├── 404.html │ ├── 500.html │ ├── base.html │ ├── disqus.html │ ├── google_analytics.html │ ├── meta_tags.html │ ├── navigation │ │ ├── footer.html │ │ ├── header.html │ │ ├── mobile_menu.html │ │ ├── mobile_social_links.html │ │ ├── navbar.html │ │ ├── pagination.html │ │ ├── sidebar.html │ │ ├── sidebar_posts.html │ │ └── social-link-icons.html │ ├── page_meta.html │ ├── password_required.html │ ├── robots.txt │ ├── rss.xml │ └── wagtailadmin │ │ ├── admin_base.html │ │ ├── base.html │ │ ├── home.html │ │ ├── login.html │ │ ├── review_copies.html │ │ ├── skeleton.html │ │ └── userbar │ │ └── base.html ├── templatetags │ ├── admin_tags.py │ └── menu_tags.py ├── urls.py ├── wagtail_hooks.py ├── wsgi.py └── wsgi_dev.py └── users ├── __init__.py ├── forms.py ├── migrations ├── 0001_initial.py ├── 0002_auto_20200422_0253.py ├── 0003_auto_20200422_0311.py ├── 0004_auto_20200422_0311.py └── __init__.py ├── models.py ├── templates ├── users │ ├── author.html │ ├── author_bio.html │ ├── author_social_links.html │ ├── user_grid.html │ └── user_index.html ├── wagtailadmin │ └── account │ │ └── change_bio.html └── wagtailusers │ └── users │ └── edit.html ├── templatetags └── customuser_tags.py ├── urls.py ├── views.py └── wagtail_hooks.py /.gitignore: -------------------------------------------------------------------------------- 1 | # System Files 2 | *.DS_Store 3 | 4 | # Database 5 | db.sqlite3 6 | 7 | # Config Files 8 | .gitconfig 9 | config.py 10 | analytics-config.json 11 | 12 | # Logs 13 | *.log 14 | 15 | # Pycache Files 16 | *.pyc 17 | __pycache__/ 18 | *.py[cod] 19 | 20 | # NPM 21 | node_modules/ 22 | package-lock.json 23 | 24 | # Compiled Output and temp files 25 | build/ 26 | tmp/ 27 | bakery/ 28 | 29 | # UWSGI Socket Files 30 | *.sock 31 | 32 | # Media / Collected static files 33 | /media/ 34 | /static/ 35 | 36 | # Server Config 37 | .htaccess 38 | .htpasswd 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Sprites and Dice Logo](spritesanddice/static/img/logo-black.png) 2 | 3 | # Sprites and Dice 3.0 4 | 5 | A gaming blog and news site built with [Wagtail CMS](https://github.com/wagtail/wagtail/). 6 | 7 | ----- 8 | 9 | ## UI Highlights 10 | 11 | ### Home Page 12 | 13 | ![Sprites and Dice Home Page](docs/home-page.png) 14 | 15 | 16 | ### Mobile Menu 17 | 18 | ![Mobile Menu](docs/mobile-menu.png) 19 | 20 | 21 | ### Game Review 22 | 23 | ![Screenshot of a game review](docs/game-review.png) 24 | 25 | 26 | ----- 27 | 28 | ## Admin Features 29 | 30 | ![Admin dashboard with site summary countup animation](docs/admin-dashboard.gif) 31 | 32 | ### [Podcast Manager](podcast/) 33 | 34 | ![Admin UI for the podcast manager](docs/podcast-manager.png) 35 | 36 | In combination with [Wagtailmedia](https://github.com/torchbox/wagtailmedia/)'s ability to manage .mp3 files, admins can use this Wagtail snippet to create new podcast episodes and edit their `podcast.xml` metadata. Podcasts can be embedded on any page using a custom "Podcast" stream block. 37 | 38 | ### [Game Manager](game/) 39 | 40 | A Wagtail snippet that allows admins to store info like game box art, publishers, MSRP, etc. 41 | 42 | ### Built-in Analytics Viewer 43 | 44 | Powered by [Wagalytics](https://github.com/tomdyson/wagalytics). 45 | -------------------------------------------------------------------------------- /analytics-config.json.skel: -------------------------------------------------------------------------------- 1 | { 2 | "Instructions": "To create a JSON key, follow the instructions at this URL: https://ga-dev-tools.appspot.com/embed-api/server-side-authorization/" 3 | } 4 | -------------------------------------------------------------------------------- /config.py.skel: -------------------------------------------------------------------------------- 1 | # Google Analytics Config 2 | GA_KEY_FILEPATH = 'analytics-config.json' 3 | GA_VIEW_ID = 'ga:XXXXXXXXX' 4 | 5 | # SECURITY WARNING: keep the secret key used in production secret! 6 | SECRET_KEY = 'some-random-string-goes-here' 7 | 8 | DATABASE_VALUES = { 9 | 'ENGINE': 'django.db.backends.postgresql', 10 | 'NAME': '', 11 | 'USER': '', 12 | 'PASSWORD': '', 13 | 'HOST': 'localhost', 14 | 'PORT': '5432', 15 | } 16 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | echo "Enabling Virtualenv..." 2 | source /home/sprites/sprites/bin/activate 3 | 4 | echo "Downloading updates from Git..." 5 | git reset --hard 6 | git clean -df 7 | git checkout . 8 | git pull 9 | 10 | echo "Installing Python dependencies..." 11 | pip install -r requirements.txt --quiet 12 | 13 | echo "Gathering static files..." 14 | ./manage.py collectstatic --noinput 15 | 16 | echo "Fixing file permissions..." 17 | sudo chown -R sprites:sprites . 18 | 19 | echo "Restarting Gunicorn..." 20 | sudo systemctl restart gunicorn 21 | 22 | echo "Restarting nginx..." 23 | sudo nginx -t && sudo systemctl restart nginx 24 | 25 | echo "Done!" 26 | -------------------------------------------------------------------------------- /docs/admin-dashboard.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/docs/admin-dashboard.gif -------------------------------------------------------------------------------- /docs/admin-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/docs/admin-dashboard.png -------------------------------------------------------------------------------- /docs/game-review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/docs/game-review.png -------------------------------------------------------------------------------- /docs/home-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/docs/home-page.png -------------------------------------------------------------------------------- /docs/mobile-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/docs/mobile-menu.png -------------------------------------------------------------------------------- /docs/podcast-manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/docs/podcast-manager.png -------------------------------------------------------------------------------- /event/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/event/__init__.py -------------------------------------------------------------------------------- /event/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-08-25 19:28 2 | 3 | from django.db import migrations, models 4 | import wagtail.search.index 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Event', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('title', models.CharField(blank=True, max_length=255, null=True)), 20 | ('description', models.TextField(blank=True, null=True)), 21 | ('date', models.DateTimeField(blank=True, null=True)), 22 | ], 23 | options={ 24 | 'abstract': False, 25 | }, 26 | bases=(wagtail.search.index.Indexed, models.Model), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /event/migrations/0002_event_type.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-08-25 19:59 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('event', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='event', 15 | name='type', 16 | field=models.CharField(choices=[('stream', 'Twitch Stream'), ('online', 'Virtual Game Night'), ('meatspace', 'In-Person Event'), ('convention', 'Convention'), ('other', 'Other')], default='online', max_length=30), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /event/migrations/0003_auto_20200825_2001.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-08-25 20:01 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('event', '0002_event_type'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='event', 15 | old_name='date', 16 | new_name='start_date', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /event/migrations/0004_event_end_date.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-08-25 20:02 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('event', '0003_auto_20200825_2001'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='event', 15 | name='end_date', 16 | field=models.DateTimeField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /event/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/event/migrations/__init__.py -------------------------------------------------------------------------------- /event/models.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.conf import settings 3 | from django.core.exceptions import ValidationError 4 | from django.core.files.images import ImageFile 5 | from django.core.files.base import ContentFile 6 | from django.core.validators import FileExtensionValidator 7 | from django.db import models 8 | from django.db.models import Q 9 | from django.utils.html import format_html 10 | from django.utils.translation import ugettext_lazy as _ 11 | 12 | from modelcluster.contrib.taggit import ClusterTaggableManager 13 | from modelcluster.fields import ParentalKey 14 | from modelcluster.models import ClusterableModel 15 | 16 | from taggit.models import TaggedItemBase 17 | 18 | from wagtail.admin.edit_handlers import InlinePanel, FieldPanel, StreamFieldPanel, MultiFieldPanel, HelpPanel 19 | from wagtail.contrib.modeladmin.options import ModelAdmin, ModelAdminGroup, modeladmin_register 20 | from wagtail.core import blocks 21 | from wagtail.core.fields import StreamField 22 | from wagtail.core.models import Page, Orderable 23 | from wagtail.images.edit_handlers import ImageChooserPanel 24 | from wagtail.images.models import Image 25 | from wagtail.search import index 26 | from wagtail.snippets.models import register_snippet 27 | 28 | # ===== Snippet Models ===== 29 | 30 | @register_snippet 31 | class Event(index.Indexed, ClusterableModel): 32 | 33 | title = models.CharField(null=True, blank=True, max_length=255) 34 | description = models.TextField(null=True, blank=True) 35 | 36 | start_date = models.DateTimeField(null=True, blank=True) 37 | end_date = models.DateTimeField(null=True, blank=True) 38 | 39 | type = models.CharField(max_length=30, choices=( 40 | ('stream', 'Twitch Stream'), 41 | ('online', 'Virtual Game Night'), 42 | ('meatspace', 'In-Person Event'), 43 | ('convention', 'Convention'), 44 | ('other', 'Other'), 45 | ), default='online') 46 | 47 | panels = [ 48 | FieldPanel('title'), 49 | FieldPanel('description'), 50 | FieldPanel('type'), 51 | FieldPanel('start_date'), 52 | FieldPanel('end_date'), 53 | ] 54 | 55 | def __str__(self): 56 | return self.title 57 | 58 | 59 | # ===== ModelAdmin Models ===== 60 | 61 | class EventAdmin(ModelAdmin): 62 | model = Event 63 | list_display = ('title', 'type', 'start_date', 'end_date') 64 | search_fields = ['title', 'description'] 65 | list_display_add_buttons = 'title' 66 | menu_icon = 'fa-calendar' 67 | 68 | modeladmin_register(EventAdmin) 69 | -------------------------------------------------------------------------------- /event/templates/event/calendar.html: -------------------------------------------------------------------------------- 1 | {% extends "page/base_page.html" %} 2 | {% load static wagtailcore_tags %} 3 | 4 | {% block body_class %}calendar{% endblock %} 5 | 6 | {% block title %}Events Schedule{% endblock %} 7 | {% block og_title %}Events Schedule{% endblock %} 8 | 9 | {% block before_page_feed%} 10 |
11 |
12 |

Events Schedule

13 |
14 |
15 | {% endblock %} 16 | 17 | {% block page_feed %} 18 | {% for event in events %} 19 | {% include "event/event.html" %} 20 | {% endfor %} 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /event/templates/event/event.html: -------------------------------------------------------------------------------- 1 |
2 |

{{event.title}}

3 | {{event.start_date|date:"M d"}} - {{event.end_date|date:"M d"}} 4 |
5 | {{event.description}} 6 |
7 | -------------------------------------------------------------------------------- /event/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path, re_path 2 | 3 | from . import views 4 | 5 | app_name = 'event' 6 | 7 | urlpatterns = [ 8 | path('', views.events_calendar, name='calendar'), 9 | ] 10 | -------------------------------------------------------------------------------- /event/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | from .models import Event 4 | 5 | def events_calendar(request): 6 | return render(request, 'event/calendar.html', { 7 | 'events': Event.objects.all().order_by('start_date') 8 | }) 9 | -------------------------------------------------------------------------------- /event/wagtail_hooks.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/event/wagtail_hooks.py -------------------------------------------------------------------------------- /game/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/game/__init__.py -------------------------------------------------------------------------------- /game/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-12-01 07:38 2 | 3 | from django.db import migrations, models 4 | import wagtail.search.index 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Game', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('name', models.CharField(max_length=255)), 20 | ], 21 | options={ 22 | 'ordering': ['name'], 23 | }, 24 | bases=(wagtail.search.index.Indexed, models.Model), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /game/migrations/0002_auto_20191201_0749.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-12-01 07:49 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import modelcluster.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('game', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='OtherInfo', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('sort_order', models.IntegerField(blank=True, editable=False, null=True)), 20 | ('label', models.CharField(blank=True, max_length=255)), 21 | ('text', models.CharField(max_length=255)), 22 | ], 23 | options={ 24 | 'ordering': ['sort_order'], 25 | 'abstract': False, 26 | }, 27 | ), 28 | migrations.AddField( 29 | model_name='game', 30 | name='author', 31 | field=models.CharField(blank=True, max_length=255), 32 | ), 33 | migrations.AddField( 34 | model_name='game', 35 | name='developer', 36 | field=models.CharField(blank=True, max_length=255), 37 | ), 38 | migrations.AddField( 39 | model_name='game', 40 | name='format', 41 | field=models.CharField(blank=True, max_length=255), 42 | ), 43 | migrations.AddField( 44 | model_name='game', 45 | name='number_of_players', 46 | field=models.CharField(blank=True, max_length=255), 47 | ), 48 | migrations.AddField( 49 | model_name='game', 50 | name='platforms', 51 | field=models.CharField(blank=True, max_length=255), 52 | ), 53 | migrations.AddField( 54 | model_name='game', 55 | name='play_time', 56 | field=models.CharField(blank=True, max_length=255), 57 | ), 58 | migrations.AddField( 59 | model_name='game', 60 | name='price', 61 | field=models.CharField(blank=True, max_length=255), 62 | ), 63 | migrations.AddField( 64 | model_name='game', 65 | name='publisher', 66 | field=models.CharField(blank=True, max_length=255), 67 | ), 68 | migrations.AddField( 69 | model_name='game', 70 | name='release_date', 71 | field=models.DateTimeField(blank=True, null=True), 72 | ), 73 | migrations.AddField( 74 | model_name='otherinfo', 75 | name='game', 76 | field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='other_info', to='game.Game'), 77 | ), 78 | ] 79 | -------------------------------------------------------------------------------- /game/migrations/0003_reviewcodes.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-12-01 08:29 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import modelcluster.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('game', '0002_auto_20191201_0749'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='ReviewCodes', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('sort_order', models.IntegerField(blank=True, editable=False, null=True)), 20 | ('code', models.CharField(blank=True, max_length=255)), 21 | ('notes', models.TextField()), 22 | ('redeemed', models.BooleanField()), 23 | ('game', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='review_codes', to='game.Game')), 24 | ], 25 | options={ 26 | 'ordering': ['sort_order'], 27 | 'abstract': False, 28 | }, 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /game/migrations/0004_reviewcodes_redeemed_by.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-12-01 08:31 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('game', '0003_reviewcodes'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='reviewcodes', 15 | name='redeemed_by', 16 | field=models.CharField(blank=True, max_length=255), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /game/migrations/0005_auto_20191201_1839.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-12-01 18:39 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 | ('image', '0001_initial'), 11 | ('game', '0004_reviewcodes_redeemed_by'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='game', 17 | name='box_art', 18 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='image.CustomImage'), 19 | ), 20 | migrations.AlterField( 21 | model_name='reviewcodes', 22 | name='code', 23 | field=models.CharField(max_length=255, null=True), 24 | ), 25 | migrations.AlterField( 26 | model_name='reviewcodes', 27 | name='notes', 28 | field=models.TextField(blank=True), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /game/migrations/0006_auto_20191201_1917.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-12-01 19:17 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('game', '0005_auto_20191201_1839'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='game', 15 | name='release_date', 16 | field=models.DateField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /game/migrations/0007_auto_20191202_0041.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-12-02 00:41 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('game', '0006_auto_20191201_1917'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='reviewcodes', 15 | name='code', 16 | field=models.CharField(blank=True, max_length=255, null=True), 17 | ), 18 | migrations.AlterField( 19 | model_name='reviewcodes', 20 | name='redeemed', 21 | field=models.BooleanField(default=False), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /game/migrations/0008_game_designer.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-04-25 01:28 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('game', '0007_auto_20191202_0041'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='game', 15 | name='designer', 16 | field=models.CharField(blank=True, max_length=255), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /game/migrations/0009_auto_20200502_1803.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-02 18:03 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('game', '0008_game_designer'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='game', 15 | options={'ordering': ['title']}, 16 | ), 17 | migrations.RenameField( 18 | model_name='game', 19 | old_name='name', 20 | new_name='title', 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /game/migrations/0010_auto_20200502_1817.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-02 18:17 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import modelcluster.contrib.taggit 6 | import modelcluster.fields 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('taggit', '0003_taggeditem_add_unique_index'), 13 | ('game', '0009_auto_20200502_1803'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='GameTag', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('content_object', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='game_tags', to='game.Game')), 22 | ('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='game_gametag_items', to='taggit.Tag')), 23 | ], 24 | options={ 25 | 'abstract': False, 26 | }, 27 | ), 28 | migrations.AddField( 29 | model_name='game', 30 | name='tags', 31 | field=modelcluster.contrib.taggit.ClusterTaggableManager(blank=True, help_text='A comma-separated list of tags.', through='game.GameTag', to='taggit.Tag', verbose_name='Tags'), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /game/migrations/0011_game_type.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-02 18:36 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('game', '0010_auto_20200502_1817'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='game', 15 | name='type', 16 | field=models.CharField(choices=[('video-game', 'Video Game'), ('tabletop-game', 'Tabletop Game'), ('book', 'Book')], default='video-game', max_length=30), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /game/migrations/0012_auto_20200506_0600.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-06 06:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('game', '0011_game_type'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='game', 15 | name='type', 16 | field=models.CharField(choices=[('video-game', 'Video Game'), ('tabletop-game', 'Tabletop'), ('book', 'Book')], default='video-game', max_length=30), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /game/migrations/0013_auto_20200506_0745.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-06 07:45 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('game', '0012_auto_20200506_0600'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='game', 15 | name='type', 16 | field=models.CharField(choices=[('video-game', 'Video Game'), ('tabletop-game', 'Tabletop'), ('book', 'Book'), ('movie', 'Movie')], default='video-game', max_length=30), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /game/migrations/0014_auto_20200506_1646.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-06 16:46 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import modelcluster.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('game', '0013_auto_20200506_0745'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='ReviewCopy', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('sort_order', models.IntegerField(blank=True, editable=False, null=True)), 20 | ('code', models.CharField(blank=True, help_text='A download code, if it exists.', max_length=255, null=True)), 21 | ('notes', models.TextField(blank=True)), 22 | ('redeemed_by', models.CharField(blank=True, help_text='Who is reviewing this game?', max_length=255)), 23 | ], 24 | options={ 25 | 'ordering': ['sort_order'], 26 | 'abstract': False, 27 | }, 28 | ), 29 | migrations.AlterField( 30 | model_name='game', 31 | name='format', 32 | field=models.CharField(blank=True, help_text='Card game, Miniatures game, Board game, etc.', max_length=255), 33 | ), 34 | migrations.AlterField( 35 | model_name='game', 36 | name='platforms', 37 | field=models.CharField(blank=True, help_text='PC, PS4, Virtual Boy, Vectrex, etc.', max_length=255), 38 | ), 39 | migrations.AlterField( 40 | model_name='game', 41 | name='play_time', 42 | field=models.CharField(blank=True, help_text='Average play session duration for board games, total playtime for some video games.', max_length=255), 43 | ), 44 | migrations.DeleteModel( 45 | name='ReviewCodes', 46 | ), 47 | migrations.AddField( 48 | model_name='reviewcopy', 49 | name='game', 50 | field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='review_copies', to='game.Game'), 51 | ), 52 | ] 53 | -------------------------------------------------------------------------------- /game/migrations/0015_auto_20200506_1648.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-06 16:48 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('game', '0014_auto_20200506_1646'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='reviewcopy', 15 | name='notes', 16 | field=models.TextField(blank=True, help_text='What version of the game? Is this DLC? An expansion? Who gave it to us?'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /game/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/game/migrations/__init__.py -------------------------------------------------------------------------------- /game/site_summary.py: -------------------------------------------------------------------------------- 1 | from game.models import Game 2 | 3 | from wagtail.admin.site_summary import SummaryItem 4 | 5 | GAME_ADMIN_LINK = '/admin/game/game/' 6 | 7 | class GameSummaryItem(SummaryItem): 8 | template = 'wagtailadmin/home/site_summary_game.html' 9 | 10 | def get_context(self): 11 | count = Game.objects.all().count() 12 | return { 13 | 'link': GAME_ADMIN_LINK, 14 | 'count': count 15 | } 16 | -------------------------------------------------------------------------------- /game/templates/wagtailadmin/home/site_summary_game.html: -------------------------------------------------------------------------------- 1 | {% load i18n wagtailadmin_tags %} 2 | 3 |
  • 4 | 5 | {% blocktrans count counter=count with count|intcomma as total %} 6 | {{ total }} Game 7 | {% plural %} 8 | {{ total }} Games 9 | {% endblocktrans %} 10 | 11 |
  • 12 | -------------------------------------------------------------------------------- /game/wagtail_hooks.py: -------------------------------------------------------------------------------- 1 | from game.site_summary import GameSummaryItem, GAME_ADMIN_LINK 2 | 3 | from wagtail.admin.menu import MenuItem 4 | from wagtail.core import hooks 5 | 6 | @hooks.register('construct_homepage_summary_items') 7 | def add_game_summary_item(request, items): 8 | items.append(GameSummaryItem(request)) 9 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var clean = require('gulp-clean'); 5 | var sass = require('gulp-sass'); 6 | var csso = require('gulp-csso'); 7 | 8 | 9 | gulp.task('sass:main', function () { 10 | return gulp.src('./spritesanddice/static/scss/main/main.scss') 11 | .pipe(sass().on('error', sass.logError)) 12 | .pipe(csso()) // Minify CSS 13 | .pipe(gulp.dest('./spritesanddice/static/css')); 14 | }); 15 | 16 | gulp.task('sass:admin', function () { 17 | return gulp.src('./spritesanddice/static/scss/admin/admin.scss') 18 | .pipe(sass().on('error', sass.logError)) 19 | .pipe(csso()) // Minify CSS 20 | .pipe(gulp.dest('./spritesanddice/static/css')); 21 | }); 22 | 23 | gulp.task('sass:userbar', function () { 24 | return gulp.src('./spritesanddice/static/scss/userbar.scss') 25 | .pipe(sass().on('error', sass.logError)) 26 | .pipe(csso()) // Minify CSS 27 | .pipe(gulp.dest('./spritesanddice/static/wagtailadmin/css/')); 28 | }); 29 | 30 | 31 | gulp.task('watch', function () { 32 | gulp.watch('./spritesanddice/static/scss/userbar.scss', gulp.series('sass:userbar')); 33 | gulp.watch('./spritesanddice/static/scss/**/*.scss', gulp.series('sass:main')); 34 | gulp.watch('./spritesanddice/static/scss/**/*.scss', gulp.series('sass:admin')); 35 | }); 36 | -------------------------------------------------------------------------------- /home/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/home/__init__.py -------------------------------------------------------------------------------- /home/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-08-23 01:15 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ('wagtailcore', '0041_group_collection_permissions_verbose_name_plural'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='HomePage', 18 | fields=[ 19 | ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), 20 | ], 21 | options={ 22 | 'abstract': False, 23 | }, 24 | bases=('wagtailcore.page',), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /home/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/home/migrations/__init__.py -------------------------------------------------------------------------------- /home/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from page.models import BlogPage 4 | 5 | from spritesanddice.models import SiteSettings 6 | 7 | from wagtail.core.models import Page 8 | 9 | 10 | class HomePage(Page): 11 | 12 | parent_page_types = [] 13 | 14 | def header_image(self): 15 | first_post = BlogPage.objects.live().public().order_by('-go_live_at').first() 16 | if first_post.header_image: 17 | return first_post.header_image 18 | else: 19 | return SiteSettings.objects.first().default_social_thumb 20 | 21 | def get_context(self, request): 22 | context = super(HomePage, self).get_context(request) 23 | context['blog_posts'] = BlogPage.objects.live().public().order_by('-go_live_at') 24 | return context 25 | -------------------------------------------------------------------------------- /home/templates/home/hero_page_listing.html: -------------------------------------------------------------------------------- 1 | {% load static wagtailimages_tags customuser_tags menu_tags %} 2 | 3 |
    4 |
    5 | 6 |
    7 | 8 | {% image page.header_image fill-1400x600 format-webp as webp_thumb %} 9 | 10 | {% image page.header_image fill-1400x600 format-jpeg %} 11 | 12 | {% if page.category.title != "Blog Posts" %} 13 | 16 | {% endif %} 17 |
    18 |
    19 |

    {{page.title}}

    20 | {% if page.subtitle%} 21 |

    {{page.subtitle}}

    22 | {% endif %} 23 | {% include "blog/meta_tags.html" %} 24 |

    25 | {{page.get_content_preview}} 26 |

    27 | Read More 28 |

    29 |
    30 |
    31 |
    32 | -------------------------------------------------------------------------------- /home/templates/home/home_page.html: -------------------------------------------------------------------------------- 1 | {% extends "page/base_page.html" %} 2 | {% load static wagtailimages_tags customuser_tags menu_tags %} 3 | 4 | {% block body_class %}home-page{% endblock %} 5 | 6 | {% block page_feed %} 7 | {% blog_posts as pages %} 8 | {% for page in pages %} 9 | {% if forloop.first %} 10 | {% if pages.number == 1 %} 11 | {% include "home/hero_page_listing.html" %} 12 | {% else %} 13 | {% include "navigation/pagination.html" %} 14 |
    15 | {% include "blog/medium_listing.html" %} 16 | {% endif %} 17 | {% else %} 18 | {% include "blog/medium_listing.html" %} 19 | {% endif %} 20 | {% endfor %} 21 |
    22 | {% include "navigation/pagination.html" %} 23 | 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /image/migrations/0002_customimage_image_credit.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-05 17:08 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('image', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='customimage', 15 | name='image_credit', 16 | field=models.CharField(blank=True, max_length=250), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /image/migrations/0003_customimage_game.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-05 17:10 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 | ('game', '0011_game_type'), 11 | ('image', '0002_customimage_image_credit'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='customimage', 17 | name='game', 18 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='game.Game'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /image/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/image/migrations/__init__.py -------------------------------------------------------------------------------- /install.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/install.py -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "spritesanddice.settings.dev") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sprites-and-dice", 3 | "license": "MIT", 4 | "version": "1.0.0", 5 | "description": "Dev dependencies for compiling CSS with Gulp.", 6 | "main": "index.js", 7 | "repository": "https://github.com/juan0tron/sprites-and-dice-django", 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "devDependencies": { 12 | "gulp": "^4.0.0", 13 | "gulp-autoprefixer": "~4.0.0", 14 | "gulp-clean": "^0.4.0", 15 | "gulp-cssnano": "^2.1.2", 16 | "gulp-csso": "^4.0.1", 17 | "gulp-rename": "^1.2.2", 18 | "gulp-sass": "^4.0.1", 19 | "gulp-size": "^2.1.0", 20 | "gulp-sourcemaps": "~2.6.1", 21 | "gulp-util": "~3.0.8" 22 | }, 23 | "dependencies": { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /page/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/page/__init__.py -------------------------------------------------------------------------------- /page/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-08-23 01:15 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import modelcluster.contrib.taggit 6 | import modelcluster.fields 7 | import wagtail.core.blocks 8 | import wagtail.core.fields 9 | import wagtail.images.blocks 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | initial = True 15 | 16 | dependencies = [ 17 | ('wagtailcore', '0041_group_collection_permissions_verbose_name_plural'), 18 | ('taggit', '0002_auto_20150616_2121'), 19 | ] 20 | 21 | operations = [ 22 | migrations.CreateModel( 23 | name='BlogPage', 24 | fields=[ 25 | ('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')), 26 | ('subtitle', models.CharField(blank=True, max_length=250)), 27 | ('content', wagtail.core.fields.StreamField([('heading', wagtail.core.blocks.CharBlock(classname='full title')), ('paragraph', wagtail.core.blocks.RichTextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock())], blank=True)), 28 | ], 29 | options={ 30 | 'abstract': False, 31 | }, 32 | bases=('wagtailcore.page',), 33 | ), 34 | migrations.CreateModel( 35 | name='PageTag', 36 | fields=[ 37 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 38 | ('content_object', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='page_tags', to='page.BlogPage')), 39 | ('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='page_pagetag_items', to='taggit.Tag')), 40 | ], 41 | options={ 42 | 'abstract': False, 43 | }, 44 | ), 45 | migrations.AddField( 46 | model_name='blogpage', 47 | name='tags', 48 | field=modelcluster.contrib.taggit.ClusterTaggableManager(blank=True, help_text='A comma-separated list of tags.', through='page.PageTag', to='taggit.Tag', verbose_name='Tags'), 49 | ), 50 | ] 51 | -------------------------------------------------------------------------------- /page/migrations/0002_blogpage_header_image.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-10-20 20:30 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 | ('image', '__first__'), 11 | ('page', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='blogpage', 17 | name='header_image', 18 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='image.CustomImage'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /page/migrations/0003_auto_20191201_0756.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-12-01 07:56 2 | 3 | from django.db import migrations 4 | import wagtail.core.blocks 5 | import wagtail.core.fields 6 | import wagtail.images.blocks 7 | import wagtail.snippets.blocks 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('page', '0002_blogpage_header_image'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='blogpage', 19 | name='content', 20 | field=wagtail.core.fields.StreamField([('image', wagtail.images.blocks.ImageChooserBlock()), ('Rich_Text', wagtail.core.blocks.RichTextBlock()), ('Podcast', wagtail.core.blocks.StructBlock([('podcast', wagtail.snippets.blocks.SnippetChooserBlock('podcast.Podcast'))], icon='fa-headphones')), ('Review_Info', wagtail.core.blocks.StructBlock([], icon='fa-pencil'))], blank=True), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /page/migrations/0004_auto_20191201_0829.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-12-01 08:29 2 | 3 | from django.db import migrations 4 | import wagtail.core.blocks 5 | import wagtail.core.fields 6 | import wagtail.images.blocks 7 | import wagtail.snippets.blocks 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('page', '0003_auto_20191201_0756'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='blogpage', 19 | name='content', 20 | field=wagtail.core.fields.StreamField([('image', wagtail.images.blocks.ImageChooserBlock()), ('Rich_Text', wagtail.core.blocks.RichTextBlock()), ('Podcast', wagtail.core.blocks.StructBlock([('podcast', wagtail.snippets.blocks.SnippetChooserBlock('podcast.Podcast'))], icon='fa-headphones')), ('Review_Info', wagtail.core.blocks.StructBlock([('game', wagtail.snippets.blocks.SnippetChooserBlock('game.Game'))], icon='fa-pencil'))], blank=True), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /page/migrations/0005_blogpage_author.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0 on 2020-04-16 22:08 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('page', '0004_auto_20191201_0829'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='blogpage', 18 | name='author', 19 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /page/migrations/0006_auto_20200416_2250.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0 on 2020-04-16 22:50 2 | 3 | from django.db import migrations 4 | import django.forms.widgets 5 | import wagtail.core.blocks 6 | import wagtail.core.fields 7 | import wagtail.images.blocks 8 | import wagtail.snippets.blocks 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('page', '0005_blogpage_author'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name='blogpage', 20 | name='content', 21 | field=wagtail.core.fields.StreamField([('image', wagtail.images.blocks.ImageChooserBlock()), ('Rich_Text', wagtail.core.blocks.RichTextBlock()), ('Podcast', wagtail.core.blocks.StructBlock([('podcast', wagtail.snippets.blocks.SnippetChooserBlock('podcast.Podcast'))], icon='fa-headphones')), ('Review_Info', wagtail.core.blocks.StructBlock([('game', wagtail.snippets.blocks.SnippetChooserBlock('game.Game'))], icon='fa-pencil')), ('Author_Bio', wagtail.core.blocks.StructBlock([('user', wagtail.core.blocks.ChooserBlock(target_model='users.User', widget=django.forms.widgets.Select))], icon='fa-user'))], blank=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /page/migrations/0007_auto_20200416_2254.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0 on 2020-04-16 22:54 2 | 3 | from django.db import migrations 4 | import spritesanddice.stream_blocks 5 | import wagtail.core.blocks 6 | import wagtail.core.fields 7 | import wagtail.images.blocks 8 | import wagtail.snippets.blocks 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('page', '0006_auto_20200416_2250'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name='blogpage', 20 | name='content', 21 | field=wagtail.core.fields.StreamField([('image', wagtail.images.blocks.ImageChooserBlock()), ('Rich_Text', wagtail.core.blocks.RichTextBlock()), ('Podcast', wagtail.core.blocks.StructBlock([('podcast', wagtail.snippets.blocks.SnippetChooserBlock('podcast.Podcast'))], icon='fa-headphones')), ('Review_Info', wagtail.core.blocks.StructBlock([('game', wagtail.snippets.blocks.SnippetChooserBlock('game.Game'))], icon='fa-pencil')), ('Author_Bio', wagtail.core.blocks.StructBlock([('user', spritesanddice.stream_blocks.UserChooserBlock())], icon='fa-user'))], blank=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /page/migrations/0008_auto_20200416_2307.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0 on 2020-04-16 23:07 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import spritesanddice.stream_blocks 6 | import wagtail.core.blocks 7 | import wagtail.core.fields 8 | import wagtail.images.blocks 9 | import wagtail.snippets.blocks 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | dependencies = [ 15 | ('wagtailcore', '0045_assign_unlock_grouppagepermission'), 16 | ('page', '0007_auto_20200416_2254'), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='BasicPage', 22 | fields=[ 23 | ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), 24 | ('content', wagtail.core.fields.StreamField([('image', wagtail.images.blocks.ImageChooserBlock()), ('Rich_Text', wagtail.core.blocks.RichTextBlock()), ('Author_Bio', wagtail.core.blocks.StructBlock([('user', spritesanddice.stream_blocks.UserChooserBlock())], icon='fa-user'))], blank=True)), 25 | ], 26 | options={ 27 | 'abstract': False, 28 | }, 29 | bases=('wagtailcore.page',), 30 | ), 31 | migrations.CreateModel( 32 | name='BlogFolder', 33 | fields=[ 34 | ('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')), 35 | ], 36 | options={ 37 | 'abstract': False, 38 | }, 39 | bases=('wagtailcore.page',), 40 | ), 41 | migrations.AlterField( 42 | model_name='blogpage', 43 | name='content', 44 | field=wagtail.core.fields.StreamField([('image', wagtail.images.blocks.ImageChooserBlock()), ('Rich_Text', wagtail.core.blocks.RichTextBlock()), ('Podcast', wagtail.core.blocks.StructBlock([('podcast', wagtail.snippets.blocks.SnippetChooserBlock('podcast.Podcast'))], icon='fa-headphones')), ('Review_Info', wagtail.core.blocks.StructBlock([('game', wagtail.snippets.blocks.SnippetChooserBlock('game.Game'))], icon='fa-pencil'))], blank=True), 45 | ), 46 | ] 47 | -------------------------------------------------------------------------------- /page/migrations/0009_blogfolder_folder_icon.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-04-24 04:47 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 | ('image', '0001_initial'), 11 | ('page', '0008_auto_20200416_2307'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='blogfolder', 17 | name='folder_icon', 18 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='image.CustomImage'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /page/migrations/0010_auto_20200424_0451.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-04-24 04:51 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('page', '0009_blogfolder_folder_icon'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='blogfolder', 15 | old_name='folder_icon', 16 | new_name='icon', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /page/migrations/0011_auto_20200424_2329.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-04-24 23:29 2 | 3 | from django.db import migrations 4 | import spritesanddice.stream_blocks 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 | ('page', '0010_auto_20200424_0451'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='basicpage', 19 | name='content', 20 | field=wagtail.core.fields.StreamField([('image', wagtail.images.blocks.ImageChooserBlock()), ('Rich_Text', wagtail.core.blocks.RichTextBlock()), ('Author_Bio', wagtail.core.blocks.StructBlock([('user', spritesanddice.stream_blocks.UserChooserBlock())], icon='fa-user')), ('User_Grid', wagtail.core.blocks.StructBlock([('users', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('user', spritesanddice.stream_blocks.UserChooserBlock())])))], icon='fa-users'))], blank=True), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /page/migrations/0012_auto_20200501_1841.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-01 18:41 2 | 3 | from django.db import migrations 4 | import spritesanddice.stream_blocks 5 | import wagtail.core.blocks 6 | import wagtail.core.fields 7 | import wagtail.images.blocks 8 | import wagtail.snippets.blocks 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('page', '0011_auto_20200424_2329'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name='basicpage', 20 | name='content', 21 | field=wagtail.core.fields.StreamField([('Image', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('caption', wagtail.core.blocks.RichTextBlock())])), ('Rich_Text', wagtail.core.blocks.RichTextBlock()), ('Author_Bio', wagtail.core.blocks.StructBlock([('user', spritesanddice.stream_blocks.UserChooserBlock())], icon='fa-user')), ('User_Grid', wagtail.core.blocks.StructBlock([('users', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('user', spritesanddice.stream_blocks.UserChooserBlock())])))], icon='fa-users'))], blank=True), 22 | ), 23 | migrations.AlterField( 24 | model_name='blogpage', 25 | name='content', 26 | field=wagtail.core.fields.StreamField([('Image', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('caption', wagtail.core.blocks.RichTextBlock())])), ('Rich_Text', wagtail.core.blocks.RichTextBlock()), ('Podcast', wagtail.core.blocks.StructBlock([('podcast', wagtail.snippets.blocks.SnippetChooserBlock('podcast.Podcast'))], icon='fa-headphones')), ('Review_Info', wagtail.core.blocks.StructBlock([('game', wagtail.snippets.blocks.SnippetChooserBlock('game.Game'))], icon='fa-pencil'))], blank=True), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /page/migrations/0013_auto_20200501_1856.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-01 18:56 2 | 3 | from django.db import migrations 4 | import spritesanddice.stream_blocks 5 | import wagtail.core.blocks 6 | import wagtail.core.fields 7 | import wagtail.images.blocks 8 | import wagtail.snippets.blocks 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('page', '0012_auto_20200501_1841'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name='basicpage', 20 | name='content', 21 | field=wagtail.core.fields.StreamField([('Image', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('caption', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'link']))])), ('Rich_Text', wagtail.core.blocks.RichTextBlock()), ('Author_Bio', wagtail.core.blocks.StructBlock([('user', spritesanddice.stream_blocks.UserChooserBlock())], icon='fa-user')), ('User_Grid', wagtail.core.blocks.StructBlock([('users', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('user', spritesanddice.stream_blocks.UserChooserBlock())])))], icon='fa-users'))], blank=True), 22 | ), 23 | migrations.AlterField( 24 | model_name='blogpage', 25 | name='content', 26 | field=wagtail.core.fields.StreamField([('Image', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('caption', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'link']))])), ('Rich_Text', wagtail.core.blocks.RichTextBlock()), ('Podcast', wagtail.core.blocks.StructBlock([('podcast', wagtail.snippets.blocks.SnippetChooserBlock('podcast.Podcast'))], icon='fa-headphones')), ('Review_Info', wagtail.core.blocks.StructBlock([('game', wagtail.snippets.blocks.SnippetChooserBlock('game.Game'))], icon='fa-pencil'))], blank=True), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /page/migrations/0014_blogpage_legacy_url.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-01 19:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('page', '0013_auto_20200501_1856'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='blogpage', 15 | name='legacy_url', 16 | field=models.CharField(blank=True, max_length=255), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /page/migrations/0015_auto_20200501_2340.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-01 23:40 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import modelcluster.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('page', '0014_blogpage_legacy_url'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='blogpage', 17 | name='legacy_url', 18 | ), 19 | migrations.CreateModel( 20 | name='LegacyUrl', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('sort_order', models.IntegerField(blank=True, editable=False, null=True)), 24 | ('path', models.CharField(max_length=255)), 25 | ('blogpage', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='legacy_urls', to='page.BlogPage')), 26 | ], 27 | options={ 28 | 'ordering': ['sort_order'], 29 | 'abstract': False, 30 | }, 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /page/migrations/0016_auto_20200502_0226.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-02 02:26 2 | 3 | from django.db import migrations 4 | import spritesanddice.stream_blocks 5 | import wagtail.core.blocks 6 | import wagtail.core.fields 7 | import wagtail.images.blocks 8 | import wagtail.snippets.blocks 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('page', '0015_auto_20200501_2340'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name='basicpage', 20 | name='content', 21 | field=wagtail.core.fields.StreamField([('Image', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('caption', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'link'], required=False))])), ('Rich_Text', wagtail.core.blocks.RichTextBlock()), ('Author_Bio', wagtail.core.blocks.StructBlock([('user', spritesanddice.stream_blocks.UserChooserBlock())], icon='fa-user')), ('User_Grid', wagtail.core.blocks.StructBlock([('users', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('user', spritesanddice.stream_blocks.UserChooserBlock())])))], icon='fa-users'))], blank=True), 22 | ), 23 | migrations.AlterField( 24 | model_name='blogpage', 25 | name='content', 26 | field=wagtail.core.fields.StreamField([('Image', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('caption', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'link'], required=False))])), ('Rich_Text', wagtail.core.blocks.RichTextBlock()), ('Podcast', wagtail.core.blocks.StructBlock([('podcast', wagtail.snippets.blocks.SnippetChooserBlock('podcast.Podcast'))], icon='fa-headphones')), ('Review_Info', wagtail.core.blocks.StructBlock([('game', wagtail.snippets.blocks.SnippetChooserBlock('game.Game'))], icon='fa-pencil'))], blank=True), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /page/migrations/0017_auto_20200502_0248.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-02 02:48 2 | 3 | from django.db import migrations 4 | import wagtail.core.blocks 5 | import wagtail.core.fields 6 | import wagtail.images.blocks 7 | import wagtail.snippets.blocks 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('page', '0016_auto_20200502_0226'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='blogpage', 19 | name='content', 20 | field=wagtail.core.fields.StreamField([('Image', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('caption', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'link'], required=False))])), ('Rich_Text', wagtail.core.blocks.RichTextBlock()), ('Podcast', wagtail.core.blocks.StructBlock([('podcast', wagtail.snippets.blocks.SnippetChooserBlock('podcast.Podcast'))], icon='fa-headphones')), ('Game', wagtail.core.blocks.StructBlock([('game', wagtail.snippets.blocks.SnippetChooserBlock('game.Game'))], icon='fa-pencil'))], blank=True), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /page/migrations/0018_blogpage_header_video.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-02 18:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('page', '0017_auto_20200502_0248'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='blogpage', 15 | name='header_video', 16 | field=models.URLField(blank=True, max_length=250), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /page/migrations/0019_auto_20200505_1848.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-05 18:48 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 | ('page', '0018_blogpage_header_video'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='TagFolder', 16 | fields=[ 17 | ('blogfolder_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='page.BlogFolder')), 18 | ], 19 | options={ 20 | 'abstract': False, 21 | }, 22 | bases=('page.blogfolder',), 23 | ), 24 | migrations.AddField( 25 | model_name='blogpage', 26 | name='legacy_id', 27 | field=models.IntegerField(blank=True, null=True), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /page/migrations/0020_auto_20200505_1928.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-05 19:28 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 | ('taggit', '0003_taggeditem_add_unique_index'), 11 | ('page', '0019_auto_20200505_1848'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='blogfolder', 17 | name='show_in_sidebar', 18 | field=models.BooleanField(default=False), 19 | ), 20 | migrations.AddField( 21 | model_name='tagfolder', 22 | name='tag', 23 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='taggit.Tag'), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /page/migrations/0021_auto_20200505_1952.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-05 19:52 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('page', '0020_auto_20200505_1928'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='basicpage', 15 | name='show_in_sidebar', 16 | field=models.BooleanField(default=False), 17 | ), 18 | migrations.AddField( 19 | model_name='blogpage', 20 | name='show_in_sidebar', 21 | field=models.BooleanField(default=False), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /page/migrations/0022_blogpage_enable_comments.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-05 23:57 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('page', '0021_auto_20200505_1952'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='blogpage', 15 | name='enable_comments', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /page/migrations/0023_auto_20200506_0155.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-06 01:55 2 | 3 | from django.db import migrations 4 | import spritesanddice.stream_blocks 5 | import wagtail.core.blocks 6 | import wagtail.core.fields 7 | import wagtail.images.blocks 8 | import wagtail.snippets.blocks 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('page', '0022_blogpage_enable_comments'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name='basicpage', 20 | name='content', 21 | field=wagtail.core.fields.StreamField([('Image', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(required=False)), ('caption', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'link'], required=False))])), ('Rich_Text', wagtail.core.blocks.RichTextBlock()), ('Author_Bio', wagtail.core.blocks.StructBlock([('user', spritesanddice.stream_blocks.UserChooserBlock())], icon='fa-user')), ('User_Grid', wagtail.core.blocks.StructBlock([('users', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('user', spritesanddice.stream_blocks.UserChooserBlock())])))], icon='fa-users'))], blank=True), 22 | ), 23 | migrations.AlterField( 24 | model_name='blogpage', 25 | name='content', 26 | field=wagtail.core.fields.StreamField([('Image', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(required=False)), ('caption', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'link'], required=False))])), ('Rich_Text', wagtail.core.blocks.RichTextBlock()), ('Podcast', wagtail.core.blocks.StructBlock([('podcast', wagtail.snippets.blocks.SnippetChooserBlock('podcast.Podcast'))], icon='fa-headphones')), ('Game', wagtail.core.blocks.StructBlock([('game', wagtail.snippets.blocks.SnippetChooserBlock('game.Game'))], icon='fa-pencil'))], blank=True), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /page/migrations/0024_auto_20200510_1956.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-10 19:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('page', '0023_auto_20200506_0155'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='blogpage', 15 | name='enable_comments', 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /page/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/page/migrations/__init__.py -------------------------------------------------------------------------------- /page/templates/blocks/image_block.html: -------------------------------------------------------------------------------- 1 | {% load wagtailimages_tags %} 2 |
    3 | {% image value.image width-1080 %} 4 | {{value.caption}} 5 |
    6 | -------------------------------------------------------------------------------- /page/templates/blocks/review_block.html: -------------------------------------------------------------------------------- 1 | {% load wagtailimages_tags %} 2 |
    3 | 4 |

    5 | {% if game.box_art %}{% image game.box_art height-100 class="box-art" %}{% endif %} 6 | {{game.title}} 7 |

    8 | 9 | {% if game.author %}Author: {{game.author}}
    {% endif %} 10 | {% if game.developer %}Developer: {{game.developer}}
    {% endif %} 11 | {% if game.designer %}Designer: {{game.designer}}
    {% endif %} 12 | {% if game.publisher %}Publisher: {{game.publisher}}
    {% endif %} 13 | {% if game.platforms %}Platforms: {{game.platforms}}
    {% endif %} 14 | {% if game.format %}Format: {{game.format}}
    {% endif %} 15 | {% if game.number_of_players %}Number of Players: {{game.number_of_players}}
    {% endif %} 16 | {% if game.play_time %}Play Time: {{game.play_time}}
    {% endif %} 17 | {% if game.price %}Price: {{game.price}}
    {% endif %} 18 | {% if game.release_date %}Release Date: {{game.release_date}}
    {% endif %} 19 | 20 | {% for x in game.other_info.all %} 21 | {% if x.label %} 22 | {{x.label}}: {{x.text}}
    23 | {% else %} 24 | {{x.text}}
    25 | {% endif%} 26 | {% endfor %} 27 |
    28 | -------------------------------------------------------------------------------- /page/templates/blog/large_listing.html: -------------------------------------------------------------------------------- 1 | {% load static wagtailimages_tags customuser_tags menu_tags %} 2 | 3 |
    4 |
    5 |
    6 |

    {{page.title}}

    7 |

    {{page.subtitle}}

    8 | {% include "blog/meta_tags.html" %} 9 |
    10 |
    11 | 12 | {% if page.header_image %} 13 | {% image page.header_image fill-800x1100 %} 14 | {% else %} 15 |
    16 | {% endif %} 17 | {% if page.category.title != "Blog Posts" %} 18 | 21 | {% endif %} 22 |
    23 |
    24 |
    25 |
    26 |

    {{page.get_content_preview}}

    27 |

    28 | Read More 29 |

    30 |
    31 |
    32 |
    33 |
    34 | -------------------------------------------------------------------------------- /page/templates/blog/medium_listing.html: -------------------------------------------------------------------------------- 1 | {% load static wagtailimages_tags customuser_tags menu_tags %} 2 | 3 |
    4 |
    5 | 6 | {% if page.header_image %} 7 | 8 | {% image page.header_image fill-1200x800 format-webp as webp_thumb %} 9 | 10 | {% image page.header_image fill-1200x800 format-jpeg %} 11 | 12 | {% else %} 13 |
    14 | {% endif %} 15 | {% if page.category.title != "Blog Posts" %} 16 | 19 | {% endif %} 20 |
    21 |
    22 |
    23 |

    24 | {{page.title}} 25 |

    26 | {% if page.subtitle %} 27 |
    28 | {{page.subtitle}} 29 |
    30 | {% endif %} 31 | {% include "blog/meta_tags.html" %} 32 |
    33 |
    34 | -------------------------------------------------------------------------------- /page/templates/blog/meta_tags.html: -------------------------------------------------------------------------------- 1 | {% load customuser_tags menu_tags %} 2 | 3 |
    4 | {% if page.author %} 5 | By {% author page.author %} 6 | {% endif %} 7 | {{page.post_date|date:"M d, Y"}} 8 |
    9 | -------------------------------------------------------------------------------- /page/templates/blog/sidebar_listing.html: -------------------------------------------------------------------------------- 1 | {% load wagtailimages_tags menu_tags customuser_tags %} 2 | 3 |
    4 |
    5 | 38 |
    39 |
    40 | -------------------------------------------------------------------------------- /page/templates/blog/small_listing.html: -------------------------------------------------------------------------------- 1 | {% load static wagtailimages_tags customuser_tags menu_tags %} 2 | 3 |
    4 |
    5 | 6 | {% if page.header_image %} 7 | {% image page.header_image fill-1200x800 %} 8 | {% else %} 9 |
    10 | {% endif %} 11 | {% if page.category.title != "Blog Posts" %} 12 | 15 | {% endif %} 16 |
    17 |
    18 |
    19 |

    SMALL {{page.title}}

    20 |
    21 |
    22 | -------------------------------------------------------------------------------- /page/templates/blog/tags.html: -------------------------------------------------------------------------------- 1 |
    2 | {% if page.tags.all|length > 0 %} 3 | {% for tag in page.tags.all %} 4 | 5 | 6 | {{tag}} 7 | 8 | {% endfor %} 9 | {% endif %} 10 |
    11 | -------------------------------------------------------------------------------- /page/templates/page/base_page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static wagtailimages_tags menu_tags customuser_tags %} 3 | 4 | {% block body_class %}home-page{% endblock %} 5 | 6 | {% block og_description %}{{page.subtitle}}{% endblock %} 7 | {% block meta_description %}{{page.subtitle}}{% endblock %} 8 | 9 | {% block content %} 10 |
    11 |
    12 | {% block before_page_feed %}{% endblock %} 13 | {% block page_feed %}{% endblock %} 14 | {% block after_page_feed %}{% endblock %} 15 |
    16 | 17 | 20 | 21 |
    22 | {% endblock content %} 23 | -------------------------------------------------------------------------------- /page/templates/page/basic_page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load wagtailcore_tags wagtailimages_tags menu_tags customuser_tags %} 3 | 4 | {% block body_class %}blog-page{% endblock %} 5 | 6 | {% block content %} 7 |
    8 |
    9 |
    10 |

    {{page.title}}

    11 |
    12 |
    13 | {% for block in page.content %} 14 | {% include_block block %} 15 | {% endfor %} 16 |
    17 |
    18 |
    19 | {% include 'navigation/sidebar.html' %} 20 |
    21 |
    22 | {% endblock %} 23 | 24 | {% block extra_js %}{% endblock %} 25 | -------------------------------------------------------------------------------- /page/templates/page/blog_folder.html: -------------------------------------------------------------------------------- 1 | {% extends "page/base_page.html" %} 2 | {% load static wagtailimages_tags customuser_tags menu_tags %} 3 | 4 | {% block body_class %}blog-folder{% endblock %} 5 | 6 | {% block before_page_feed %} 7 |

    8 | {% image page.icon original alt="page.title" class="folder-icon" %} 9 | {{page.title}} 10 |

    11 |
    12 | {% endblock %} 13 | 14 | {% block page_feed %} 15 | {% blog_posts blog_folder=self as pages %} 16 | {% for page in pages %} 17 | {% include "blog/medium_listing.html" %} 18 | {% endfor %} 19 |
    20 | {% include "navigation/pagination.html" %} 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /page/templates/page/blog_page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load wagtailembeds_tags wagtailcore_tags wagtailimages_tags menu_tags customuser_tags %} 3 | 4 | {% block body_class %}blog-page{% endblock %} 5 | 6 | {% block content %} 7 |
    8 |
    9 |

    {{page.header_title}}

    10 | {% if page.subtitle %} 11 |

    {{page.subtitle}}

    12 | {% endif %} 13 | 14 | 23 | {{page.post_date|date:"M d, Y"}} 24 |
    25 | 26 |
    27 | {% if page.header_video %} 28 |
    29 | {% embed page.header_video %} 30 |
    31 | {% elif page.header_image %} 32 |
    33 | {% image page.header_image width-1080 %} 34 |
    35 | {% endif %} 36 | 37 |
    38 | {% for block in page.content %} 39 | {% include_block block %} 40 | {% endfor %} 41 |
    42 | 43 | {% include "blog/tags.html" %} 44 | 45 |
    46 | 47 | {% author_bio page.author %} 48 | 49 | 50 | 51 | {% if page.enable_comments and page.live %} 52 | {% include "disqus.html" %} 53 | {% endif %} 54 | 55 |
    56 |
    57 | {% include 'navigation/sidebar.html' %} 58 |
    59 |
    60 | {% endblock %} 61 | 62 | {% block extra_js %}{% endblock %} 63 | -------------------------------------------------------------------------------- /page/templates/page/tag_folder.html: -------------------------------------------------------------------------------- 1 | {% include "page/tag_page.html" %} 2 | -------------------------------------------------------------------------------- /page/templates/page/tag_index.html: -------------------------------------------------------------------------------- 1 | {% extends "page/base_page.html" %} 2 | {% load static wagtailimages_tags customuser_tags menu_tags %} 3 | 4 | {% block title %}{{title}}{% endblock %} 5 | {% block og_title %}{{title}}{% endblock %} 6 | 7 | {% block body_class %}tag-page{% endblock %} 8 | 9 | {% block before_page_feed %} 10 |

    {{title}}

    11 |
    12 | {% endblock %} 13 | 14 | {% block page_feed %} 15 |
    16 | 17 | {% for tag in tags %} 18 | {{tag.name}} 19 | {% endfor %} 20 |
    21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /page/templates/page/tag_page.html: -------------------------------------------------------------------------------- 1 | {% extends "page/base_page.html" %} 2 | {% load static wagtailimages_tags customuser_tags menu_tags %} 3 | 4 | {% block title %}{{title}}{% endblock %} 5 | {% block og_title %}{{title}}{% endblock %} 6 | 7 | {% block body_class %}blog-folder tag-page{% endblock %} 8 | 9 | {% block before_page_feed %} 10 |

    11 | {% image page.icon original alt="page.title" class="folder-icon" %} 12 | {{title}} 13 |

    14 |
    15 | {% endblock %} 16 | 17 | {% block page_feed %} 18 | {% blog_posts tag=tag_slug page_number=request.GET.page as pages %} 19 | {% for page in pages %} 20 | {% include "blog/medium_listing.html" %} 21 | {% endfor %} 22 |
    23 | {% include "navigation/pagination.html" %} 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /page/templates/page/user_page.html: -------------------------------------------------------------------------------- 1 | {% extends "page/base_page.html" %} 2 | {% load static wagtailimages_tags customuser_tags menu_tags %} 3 | 4 | {% block title %}{{user.get_full_name}}{% endblock %} 5 | {% block og_title %}{{user.get_full_name}}{% endblock %} 6 | 7 | {% block body_class %}user-page{% endblock %} 8 | 9 | {% block page_feed %} 10 | {% author_bio user %} 11 |
    12 | {% blog_posts user=user page_number=request.GET.page as pages %} 13 | {% if pages|length > 0 %} 14 |


    Posts by {{user.get_full_name}}

    15 | {% for page in pages %} 16 | {% include "blog/medium_listing.html" %} 17 | {% endfor %} 18 |
    19 | {% include "navigation/pagination.html" %} 20 | {% endif %} 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /page/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import redirect, render 2 | 3 | from page.models import BlogPage, PageTag, TagFolder 4 | 5 | from taggit.models import Tag 6 | 7 | def tag_index(request): 8 | return render(request, 'page/tag_index.html', { 9 | 'title': 'Tags', 10 | 'tags': Tag.objects.all().distinct().order_by('name') 11 | }) 12 | 13 | 14 | def tag_page(request, tag_slug=''): 15 | tag_name = tag_slug.replace('-', ' ') # Un-slugify 16 | title = 'Pages tagged "{}"'.format(tag_name) 17 | tag_folder = TagFolder.objects.filter(title__iexact=tag_name).first() 18 | 19 | if tag_folder: 20 | return render(request, 'page/tag_page.html', { 21 | 'title': tag_folder.title, 22 | 'tag_name': tag_name, 23 | 'tag_slug': tag_slug, 24 | 'page': tag_folder, 25 | }) 26 | else: 27 | return render(request, 'page/tag_page.html', { 28 | 'title': title, 29 | 'tag_name': tag_name, 30 | 'tag_slug': tag_slug, 31 | }) 32 | 33 | def get_rss_feed(request): 34 | return render(request, 'rss.xml', { 35 | 'pages': BlogPage.objects.live().public().order_by('-go_live_at') 36 | }, content_type='text/xml') 37 | -------------------------------------------------------------------------------- /page/wagtail_hooks.py: -------------------------------------------------------------------------------- 1 | from page.models import BlogPage, LegacyUrl 2 | 3 | from wagtail.contrib.redirects.models import Redirect 4 | from wagtail.core import hooks 5 | 6 | # When a page moves, create a new LegacyUrl 7 | @hooks.register('before_move_page') 8 | def create_redirect_on_page_move(request, page, destination_page): 9 | if type(page) == BlogPage: 10 | old_url = Redirect.normalise_path(page.url) 11 | new_url = Redirect.normalise_path(destination_page.url + page.slug) 12 | 13 | # URLs are different, make a new LegacyUrl 14 | if old_url != new_url: 15 | legacy_url, created = LegacyUrl.objects.get_or_create(path=old_url, blogpage=page) 16 | legacy_url.save() 17 | 18 | # Check for existing LegacyUrls at the new path - if they exist, delete them 19 | existing_legacy_url = LegacyUrl.objects.filter(path=new_url).first() 20 | if existing_legacy_url: 21 | existing_legacy_url.delete() 22 | -------------------------------------------------------------------------------- /podcast/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/podcast/__init__.py -------------------------------------------------------------------------------- /podcast/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-09-07 21:12 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Podcast', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('title', models.CharField(blank=True, max_length=255, null=True)), 19 | ], 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /podcast/migrations/0002_podcastsettings.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-09-08 01:23 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', '0041_group_collection_permissions_verbose_name_plural'), 11 | ('podcast', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='PodcastSettings', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('title', models.CharField(blank=True, max_length=255, null=True)), 20 | ('site', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='wagtailcore.Site')), 21 | ], 22 | options={ 23 | 'abstract': False, 24 | }, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /podcast/migrations/0003_podcastsettings_description.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-09-08 01:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('podcast', '0002_podcastsettings'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='podcastsettings', 15 | name='description', 16 | field=models.TextField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /podcast/migrations/0004_auto_20190908_0158.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-09-08 01:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('podcast', '0003_podcastsettings_description'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='podcast', 15 | name='description', 16 | field=models.TextField(blank=True, null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='podcast', 20 | name='episode_number', 21 | field=models.IntegerField(blank=True, null=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /podcast/migrations/0005_podcast_file.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-09-08 02:42 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 | ('wagtailmedia', '0003_copy_media_permissions_to_collections'), 11 | ('podcast', '0004_auto_20190908_0158'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='podcast', 17 | name='file', 18 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='podcast_file', to='wagtailmedia.Media'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /podcast/migrations/0006_podcast_publish_date.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-09-08 02:47 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('podcast', '0005_podcast_file'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='podcast', 15 | name='publish_date', 16 | field=models.DateTimeField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /podcast/migrations/0007_podcast_related_page.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-05 17:16 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 | ('page', '0018_blogpage_header_video'), 11 | ('podcast', '0006_podcast_publish_date'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='podcast', 17 | name='related_page', 18 | field=models.ForeignKey(blank=True, help_text='The associated announcement post for this episode.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='page.BlogPage'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /podcast/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/podcast/migrations/__init__.py -------------------------------------------------------------------------------- /podcast/models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from django.db import models 4 | from django.http import HttpResponseRedirect 5 | 6 | from modelcluster.models import ClusterableModel 7 | 8 | import time 9 | 10 | from wagtail.admin.edit_handlers import FieldPanel, MultiFieldPanel, PageChooserPanel 11 | from wagtail.contrib.settings.models import BaseSetting, register_setting 12 | from wagtail.documents.models import Document 13 | from wagtail.documents.edit_handlers import DocumentChooserPanel 14 | from wagtail.snippets.models import register_snippet 15 | from wagtail.search import index 16 | 17 | from wagtailmedia.edit_handlers import MediaChooserPanel 18 | 19 | 20 | @register_setting 21 | class PodcastSettings(BaseSetting): 22 | title = models.CharField(null=True, blank=True, max_length=255) 23 | description = models.TextField(null=True, blank=True) 24 | 25 | panels = [ 26 | MultiFieldPanel([ 27 | FieldPanel('title'), 28 | FieldPanel('description'), 29 | ], heading="Podcast Feed Settings") 30 | ] 31 | 32 | def save(self, *args, **kwargs): 33 | # TODO: IF CHANGES AFFECT PODCAST METADATA, UPDATE ALL EPISODES 34 | # episodes = Podcast.objects.all() 35 | # for e in episodes: 36 | # # update here 37 | # e.save() 38 | super(PodcastSettings, self).save(*args, **kwargs) 39 | 40 | @register_snippet 41 | class Podcast(index.Indexed, ClusterableModel): 42 | 43 | file = models.ForeignKey( 44 | 'wagtailmedia.Media', 45 | null=True, 46 | blank=True, 47 | on_delete=models.SET_NULL, 48 | related_name='podcast_file' 49 | ) 50 | 51 | episode_number = models.IntegerField(null=True, blank=True) 52 | title = models.CharField(null=True, blank=True, max_length=255) 53 | description = models.TextField(null=True, blank=True) 54 | publish_date = models.DateTimeField(null=True, blank=True) 55 | 56 | related_page = models.ForeignKey( 57 | 'page.BlogPage', 58 | null=True, 59 | blank=True, 60 | on_delete=models.SET_NULL, 61 | related_name='+', 62 | help_text="The associated announcement post for this episode." 63 | ) 64 | 65 | panels = [ 66 | MediaChooserPanel('file'), 67 | FieldPanel('episode_number'), 68 | FieldPanel('title'), 69 | FieldPanel('description'), 70 | PageChooserPanel('related_page'), 71 | FieldPanel('publish_date'), 72 | ] 73 | 74 | search_fields = [ 75 | index.SearchField('title', partial_match=True), 76 | index.SearchField('description', partial_match=True), 77 | ] 78 | 79 | def __str__(self): 80 | return self.title 81 | 82 | # Example XML date string: "Fri, 11 May 2018 10:12:46 -0400" 83 | def xml_pubdate(self): 84 | if self.publish_date: 85 | return datetime.strftime(self.publish_date, "%a, %d %b %Y %H:%M:%S %z") 86 | 87 | # Media file duration is in seconds. Convert to HH:MM 88 | def episode_length(self): 89 | seconds = 0 90 | if(self.file): 91 | seconds = self.file.duration 92 | return time.strftime('%H:%M:%S', time.gmtime(seconds)) 93 | 94 | def save(self, *args, **kwargs): 95 | # EDIT MP3 METADATA/FILENAME HERE 96 | # https://eyed3.readthedocs.io/en/latest/ 97 | super(Podcast, self).save(*args, **kwargs) 98 | -------------------------------------------------------------------------------- /podcast/site_summary.py: -------------------------------------------------------------------------------- 1 | from podcast.models import Podcast 2 | 3 | from wagtail.admin.site_summary import SummaryItem 4 | 5 | PODCAST_ADMIN_LINK = '/admin/snippets/podcast/podcast/' 6 | 7 | class PodcastSummaryItem(SummaryItem): 8 | template = 'wagtailadmin/home/site_summary_podcast.html' 9 | 10 | def get_context(self): 11 | count = Podcast.objects.all().count() 12 | return { 13 | 'link': PODCAST_ADMIN_LINK, 14 | 'count': count 15 | } 16 | -------------------------------------------------------------------------------- /podcast/templates/podcast/player.html: -------------------------------------------------------------------------------- 1 | {% load i18n wagtailadmin_tags %} 2 |
    3 | Sprites and Dice Podcast 4 |
    5 |
    6 |

    #{{podcast.episode_number}}: {{podcast.title}}

    7 | Download 8 |
    9 | 12 |
    13 |
    14 | -------------------------------------------------------------------------------- /podcast/templates/podcast/podcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% autoescape off %} 4 | 5 | 6 | {{settings.podcast.PodcastSettings.title}} 7 | {{settings.podcast.PodcastSettings.description}} 8 | https://www.spritesanddice.com/ 9 | 10 | 11 | 12 | en 13 | 14 | Games & Hobbies 15 | 16 | contact@spritesanddice.com 17 | 18 | wyatt@spritesanddice.com (Wyatt Krause) 19 | 20 | jon@spritesanddice.com (Jon Glover) 21 | 22 | {{latest_episode.xml_pubdate}} 23 | {{latest_episode.xml_pubdate}}{# TODO: Look for the last modified date of all podcasts and use that instead #} 24 | 25 | {{settings.podcast.PodcastSettings.description}} 26 | 27 | 28 | 29 | 30 | 31 | 32 | video games, board games, independent, nerd, sprites and dice, tabletop, indie games 33 | 34 | 35 | Sprites and Dice 36 | 37 | no 38 | no 39 | 40 | 41 | wyatt@spritesanddice.com 42 | Wyatt Krause 43 | 44 | 45 | {% for episode in episodes %} 46 | 47 | {{episode.title}} 48 | {% if episode.related_page %}https://www.spritesanddice.com{{episode.related_page.url}}{% endif %} 49 | {{episode.description}} 50 | 51 | https://www.spritesanddice.com{{episode.file.file.url}} 52 | {{episode.xml_pubdate}} 53 | {{settings.podcast.PodcastSettings.title}} 54 | {{episode.description}} 55 | {{episode.episode_length}} 56 | {{episode.episode_number}} 57 | 58 | {% endfor %} 59 | 60 | 61 | {% endautoescape %} 62 | 63 | -------------------------------------------------------------------------------- /podcast/templates/podcast/results.html: -------------------------------------------------------------------------------- 1 | {% extends "wagtailsnippets/snippets/results.html" %} 2 | {% load i18n wagtailadmin_tags %} 3 | 4 | {% block custom_content %} 5 | 6 | {% if is_searchable %} 7 | 15 | {% endif %} 16 | 17 |
    18 |
    19 | 20 |
    21 |
    22 |

    {{settings.podcast.PodcastSettings.title}}

    23 |

    Sprites and Dice

    24 |

    25 | {{settings.podcast.PodcastSettings.description}} 26 |  Edit Feed 27 |

    28 |

    {{items|length}} Episodes

    29 |
    30 |
    31 | 32 |
    33 | {% if can_delete_snippets %} 34 | {% blocktrans with snippet_type_name=model_opts.verbose_name_plural %}Delete {{ snippet_type_name }}{% endblocktrans %} 35 | {% endif %} 36 | {% if can_add_snippet %} 37 | {% blocktrans with snippet_type_name=model_opts.verbose_name %}Add {{ snippet_type_name }}{% endblocktrans %} 38 | {% endif %} 39 |
    40 | {% endblock %} 41 | 42 | {% block list_template %} 43 | {% include "podcast/list.html" %} 44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /podcast/templates/podcast/type_index.html: -------------------------------------------------------------------------------- 1 | {% extends "wagtailsnippets/snippets/type_index.html" %} 2 | {% load i18n wagtailadmin_tags %} 3 | 4 | {% block content %} 5 |
    6 |
    7 | {% block results_template %} 8 | {% include "podcast/results.html" %} 9 | {% endblock %} 10 |
    11 |
    12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /podcast/templates/wagtailadmin/home/site_summary_podcast.html: -------------------------------------------------------------------------------- 1 | {% load i18n wagtailadmin_tags %} 2 | 3 |
  • 4 | 5 | {% blocktrans count counter=count with count|intcomma as total %} 6 | {{ total }} Podcast 7 | {% plural %} 8 | {{ total }} Podcasts 9 | {% endblocktrans %} 10 | 11 |
  • 12 | -------------------------------------------------------------------------------- /podcast/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from . import views 4 | 5 | app_name = 'podcast' 6 | urlpatterns = [ 7 | url(r'^podcast/podcast/$', views.list, name='list'), 8 | url(r'^podcast/Podcast/$', views.list, name='list'), 9 | ] 10 | -------------------------------------------------------------------------------- /podcast/wagtail_hooks.py: -------------------------------------------------------------------------------- 1 | from podcast.site_summary import PodcastSummaryItem, PODCAST_ADMIN_LINK 2 | 3 | from wagtail.admin.menu import MenuItem 4 | from wagtail.core import hooks 5 | 6 | @hooks.register('construct_homepage_summary_items') 7 | def add_podcast_summary_item(request, items): 8 | items.append(PodcastSummaryItem(request)) 9 | 10 | @hooks.register('register_admin_menu_item') 11 | def register_color_menu_item(): 12 | return MenuItem('Podcast', PODCAST_ADMIN_LINK, classnames='icon icon-fa-headphones', order=400) 13 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==3.0.3 2 | wagtail==2.9 3 | wagtailfontawesome==1.1.4 4 | wagtailmedia>=0.5 5 | wagalytics==1.2 6 | psycopg2-binary>=2.8.5 7 | gunicorn>=20.0.4 8 | wand>=0.5.9 9 | -------------------------------------------------------------------------------- /search/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/search/__init__.py -------------------------------------------------------------------------------- /search/templates/search/search.html: -------------------------------------------------------------------------------- 1 | {% extends "page/base_page.html" %} 2 | {% load static wagtailcore_tags %} 3 | 4 | {% block body_class %}search-results-page{% endblock %} 5 | 6 | {% block title %}Search Results{% if request.GET.query %} - "{{request.GET.query}}"{% endif %}{% endblock %} 7 | {% block og_title %}Search Results{% if request.GET.query %} - "{{request.GET.query}}"{% endif %}{% endblock %} 8 | 9 | {% block before_page_feed%} 10 |
    11 |
    12 |

    Search Results

    13 |
    14 |
    15 | {% include "search/search_form.html" %} 16 |
    17 |
    18 | {% endblock %} 19 | 20 | {% block page_feed %} 21 | {% if pages %} 22 | {% include "navigation/pagination.html" %} 23 |
    24 | {% for page in pages %} 25 | {% include "blog/medium_listing.html" %} 26 | {% endfor %} 27 |
    28 | {% include "navigation/pagination.html" %} 29 | {% elif search_query %} 30 |
    31 |

    No results found.

    32 | {% endif %} 33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /search/templates/search/search_form.html: -------------------------------------------------------------------------------- 1 |
    2 | 8 | 11 |
    12 | -------------------------------------------------------------------------------- /search/views.py: -------------------------------------------------------------------------------- 1 | from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator 2 | from django.shortcuts import render 3 | 4 | from page.models import BlogPage 5 | 6 | from wagtail.core.models import Page 7 | from wagtail.search.models import Query 8 | 9 | 10 | def search(request): 11 | search_query = request.GET.get('query', None) 12 | page = request.GET.get('page', 1) 13 | 14 | # Search 15 | if search_query: 16 | search_results = BlogPage.objects.live().order_by('-go_live_at').search(search_query) 17 | query = Query.get(search_query) 18 | 19 | # Record hit 20 | query.add_hit() 21 | 22 | else: 23 | search_results = Page.objects.none() 24 | 25 | # Pagination 26 | paginator = Paginator(search_results, 25) 27 | 28 | try: 29 | search_results = paginator.page(page) 30 | except PageNotAnInteger: 31 | search_results = paginator.page(1) 32 | except EmptyPage: 33 | search_results = paginator.page(paginator.num_pages) 34 | 35 | return render(request, 'search/search.html', { 36 | 'search_query': search_query, 37 | 'pages': search_results, 38 | }) 39 | -------------------------------------------------------------------------------- /snippet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/snippet/__init__.py -------------------------------------------------------------------------------- /snippet/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.11 on 2019-08-23 01:23 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Game', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('title', models.CharField(blank=True, max_length=255, null=True)), 19 | ], 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /snippet/migrations/0002_delete_game.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-05 20:44 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('snippet', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.DeleteModel( 14 | name='Game', 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /snippet/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/snippet/migrations/__init__.py -------------------------------------------------------------------------------- /snippet/templates/wagtailsnippets/snippets/list.html: -------------------------------------------------------------------------------- 1 | {% load i18n wagtailadmin_tags %} 2 | 3 | {% block customcontent %}{% endblock %} 4 | 5 | {% block table %} 6 | 7 | {% if can_delete_snippets %}{% endif %} 8 | 9 | 10 | 11 | 12 | 13 | {% if can_delete_snippets %} 14 | 18 | {% endif %} 19 | 20 | 21 | 22 | 23 | {% for snippet in items %} 24 | 25 | {% if can_delete_snippets %} 26 | 30 | {% endif %} 31 | 48 | 49 | {% endfor %} 50 | 51 |
    15 | 16 | 17 | {% trans "Title" %}
    27 | 28 | 29 | 32 | {% if choosing %} 33 | 34 | {% else %} 35 | 36 |
      37 |
    • 38 | Edit 39 |
    • 40 | {% if can_delete_snippets %} 41 |
    • 42 | Delete 43 |
    • 44 | {% endif %} 45 |
    46 | {% endif %} 47 |
    52 | {% endblock %} 53 | -------------------------------------------------------------------------------- /snippet/templates/wagtailsnippets/snippets/results.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | {% block custom_content %}{% endblock %} 4 | 5 | {% if items %} 6 | {% if is_searching %} 7 |

    8 | {% blocktrans count counter=items.paginator.count %} 9 | There is {{ counter }} match 10 | {% plural %} 11 | There are {{ counter }} matches 12 | {% endblocktrans %} 13 |

    14 | {% endif %} 15 | 16 | 17 | {% block list_template %} 18 | {% include "wagtailsnippets/snippets/list.html" %} 19 | {% endblock %} 20 | 21 | {% url 'wagtailsnippets:list' model_opts.app_label model_opts.model_name as wagtailsnippets_list_url %} 22 | {% include "wagtailadmin/shared/pagination_nav.html" with items=items linkurl=wagtailsnippets_list_url %} 23 | {% else %} 24 | {% if is_searching %} 25 |

    {% blocktrans %}Sorry, no podcasts match "{{ query_string }}"{% endblocktrans %}

    26 | {% else %} 27 | {% url 'wagtailsnippets:add' model_opts.app_label model_opts.model_name as wagtailsnippets_create_url %} 28 |

    {% blocktrans with snippet_type_name_plural=model_opts.verbose_name_plural %}No {{ snippet_type_name_plural }} have been created. Why not add one?{% endblocktrans %}

    29 | {% endif %} 30 | {% endif %} 31 | -------------------------------------------------------------------------------- /snippet/templates/wagtailsnippets/snippets/type_index.html: -------------------------------------------------------------------------------- 1 | {% extends "wagtailadmin/base.html" %} 2 | {% load i18n static %} 3 | {% block titletag %}{% blocktrans with snippet_type_name_plural=model_opts.verbose_name_plural|capfirst %}Snippets {{ snippet_type_name_plural }}{% endblocktrans %}{% endblock %} 4 | 5 | {% block extra_js %} 6 | {{ block.super }} 7 | 14 | {% if can_delete_snippets %} 15 | 16 | {% endif %} 17 | {% endblock %} 18 | 19 | {% block content %} 20 | 47 | 48 |
    49 |
    50 | {% block results_template %} 51 | {% include "wagtailsnippets/snippets/results.html" %} 52 | {% endblock %} 53 |
    54 |
    55 | {% endblock %} 56 | -------------------------------------------------------------------------------- /snippet/urls.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/snippet/urls.py -------------------------------------------------------------------------------- /snippet/views.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/snippet/views.py -------------------------------------------------------------------------------- /spritesanddice/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/__init__.py -------------------------------------------------------------------------------- /spritesanddice/context_processors.py: -------------------------------------------------------------------------------- 1 | import os 2 | from django.conf import settings 3 | 4 | def global_vars(request): 5 | return { 6 | 'VERSION': settings.SPRITES_VERSION 7 | } 8 | -------------------------------------------------------------------------------- /spritesanddice/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-04-24 18:52 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', '0045_assign_unlock_grouppagepermission'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='HeaderSettings', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('update_schedule', models.CharField(blank=True, max_length=255, null=True)), 21 | ('site', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='wagtailcore.Site')), 22 | ], 23 | options={ 24 | 'abstract': False, 25 | }, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /spritesanddice/migrations/0002_metadatasettings.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-04-25 05:26 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 | ('image', '0001_initial'), 11 | ('wagtailcore', '0045_assign_unlock_grouppagepermission'), 12 | ('spritesanddice', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='MetaDataSettings', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='image.CustomImage')), 21 | ('site', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='wagtailcore.Site')), 22 | ], 23 | options={ 24 | 'abstract': False, 25 | }, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /spritesanddice/migrations/0003_sidebarsettings.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-03 02:56 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', '0045_assign_unlock_grouppagepermission'), 11 | ('spritesanddice', '0002_metadatasettings'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='SidebarSettings', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('site', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='wagtailcore.Site')), 20 | ], 21 | options={ 22 | 'abstract': False, 23 | }, 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /spritesanddice/migrations/0004_delete_sidebarsettings.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-03 23:21 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('spritesanddice', '0003_sidebarsettings'), 10 | ] 11 | 12 | operations = [ 13 | migrations.DeleteModel( 14 | name='SidebarSettings', 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /spritesanddice/migrations/0005_auto_20200509_2228.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-05-09 22:28 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', '0045_assign_unlock_grouppagepermission'), 11 | ('image', '0003_customimage_game'), 12 | ('spritesanddice', '0004_delete_sidebarsettings'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='SiteSettings', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('header_text', models.CharField(blank=True, help_text='Displayed below the site header in desktop mode.', max_length=255, null=True)), 21 | ('slogan', models.CharField(blank=True, max_length=255, null=True)), 22 | ('default_social_thumb', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='image.CustomImage')), 23 | ('site', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='wagtailcore.Site')), 24 | ], 25 | options={ 26 | 'abstract': False, 27 | }, 28 | ), 29 | migrations.RemoveField( 30 | model_name='metadatasettings', 31 | name='image', 32 | ), 33 | migrations.RemoveField( 34 | model_name='metadatasettings', 35 | name='site', 36 | ), 37 | migrations.DeleteModel( 38 | name='HeaderSettings', 39 | ), 40 | migrations.DeleteModel( 41 | name='MetaDataSettings', 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /spritesanddice/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/migrations/__init__.py -------------------------------------------------------------------------------- /spritesanddice/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from wagtail.admin.edit_handlers import FieldPanel, MultiFieldPanel, HelpPanel 4 | from wagtail.contrib.settings.models import BaseSetting, register_setting 5 | from wagtail.images.edit_handlers import ImageChooserPanel 6 | 7 | 8 | @register_setting 9 | class SiteSettings(BaseSetting): 10 | header_text = models.CharField(null=True, blank=True, max_length=255, help_text="Displayed below the site header in desktop mode.") 11 | slogan = models.CharField(null=True, blank=True, max_length=255) 12 | 13 | default_social_thumb = models.ForeignKey( 14 | 'image.CustomImage', 15 | null=True, 16 | blank=True, 17 | on_delete=models.SET_NULL, 18 | related_name='+' 19 | ) 20 | 21 | panels = [ 22 | FieldPanel('header_text'), 23 | FieldPanel('slogan'), 24 | MultiFieldPanel([ 25 | HelpPanel(content="The default image that will show in social media if another isn't available on the page."), 26 | ImageChooserPanel('default_social_thumb'), 27 | ], heading="Social Media Metadata") 28 | ] 29 | -------------------------------------------------------------------------------- /spritesanddice/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/settings/__init__.py -------------------------------------------------------------------------------- /spritesanddice/settings/dev.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | from config import * 3 | 4 | INSTALLED_APPS += [ 5 | 'wagtail.contrib.styleguide', 6 | ] 7 | 8 | # SECURITY WARNING: don't run with debug turned on in production! 9 | DEBUG = True 10 | 11 | # SECURITY WARNING: define the correct hosts in production! 12 | ALLOWED_HOSTS = ['*'] 13 | 14 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 15 | 16 | try: 17 | from .local import * 18 | except ImportError: 19 | pass 20 | -------------------------------------------------------------------------------- /spritesanddice/settings/production.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | from config import * 3 | 4 | INSTALLED_APPS += [ 5 | ] 6 | 7 | DEBUG = False 8 | 9 | ALLOWED_HOSTS = ['localhost', '64.227.25.145', 'spritesanddice.com', 'www.spritesanddice.com'] 10 | 11 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 12 | 13 | try: 14 | from .local import * 15 | except ImportError: 16 | pass 17 | -------------------------------------------------------------------------------- /spritesanddice/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/favicon.ico -------------------------------------------------------------------------------- /spritesanddice/static/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/Roboto-Light.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/Roboto-Thin.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/RobotoSlab-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/RobotoSlab-Black.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/RobotoSlab-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/RobotoSlab-Bold.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/RobotoSlab-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/RobotoSlab-ExtraBold.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/RobotoSlab-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/RobotoSlab-ExtraLight.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/RobotoSlab-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/RobotoSlab-Light.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/RobotoSlab-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/RobotoSlab-Medium.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/RobotoSlab-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/RobotoSlab-Regular.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/RobotoSlab-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/RobotoSlab-SemiBold.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/RobotoSlab-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/RobotoSlab-Thin.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/RobotoSlab-VariableFont_wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/RobotoSlab-VariableFont_wght.ttf -------------------------------------------------------------------------------- /spritesanddice/static/fonts/jaapokki-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/jaapokki-regular.woff -------------------------------------------------------------------------------- /spritesanddice/static/fonts/opensans-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/opensans-regular-webfont.woff -------------------------------------------------------------------------------- /spritesanddice/static/fonts/wagtail.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/fonts/wagtail.woff -------------------------------------------------------------------------------- /spritesanddice/static/img/logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/img/logo-black.png -------------------------------------------------------------------------------- /spritesanddice/static/img/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/img/logo-white.png -------------------------------------------------------------------------------- /spritesanddice/static/img/podcast.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/img/podcast.jpg -------------------------------------------------------------------------------- /spritesanddice/static/img/sd-logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/img/sd-logo-black.png -------------------------------------------------------------------------------- /spritesanddice/static/img/sd-logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/img/sd-logo-white.png -------------------------------------------------------------------------------- /spritesanddice/static/img/snd_end_mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/img/snd_end_mark.png -------------------------------------------------------------------------------- /spritesanddice/static/js/admin.js: -------------------------------------------------------------------------------- 1 | function siteSummaryCountUp(){ 2 | $('.stats a span:not(.visuallyhidden)').each(function(){ 3 | let span = $(this); 4 | $({ Counter: 0 }).animate({ Counter: $(span).text().replace(',','') }, { 5 | duration: 1000, 6 | easing: 'swing', 7 | step: function (){ 8 | $(span).text(Math.ceil(this.Counter)); 9 | } 10 | }); 11 | }); 12 | } 13 | 14 | // Append "Add Child" button to folder pages in page drawer 15 | function pageExplorerDrawerAddChildButton(){ 16 | $('.c-explorer__item').each(function(){ 17 | let is_folder = $(this).find('.icon-folder-inverse').length > 0; 18 | let has_add_child_button = $(this).find('.add-folder-child').length > 0; 19 | if(is_folder && !has_add_child_button){ 20 | let page_id = $(this).find('.c-explorer__item__link').attr('href').replace('/admin/pages/','').replace('/',''); 21 | let page_title = $(this).find('.c-explorer__item__title').html(); 22 | let last_button = $(this).find('.c-explorer__item__action').last(); 23 | $(` 24 | 25 | 26 | 27 | Add a child page to '${page_title}' 28 | 29 | 30 | `).insertBefore(last_button); 31 | } 32 | }); 33 | } 34 | 35 | $(document).bind('DOMSubtreeModified',function(){ 36 | pageExplorerDrawerAddChildButton(); 37 | }) 38 | 39 | $(document).ready(function(){ 40 | siteSummaryCountUp(); 41 | pageExplorerDrawerAddChildButton(); 42 | }); 43 | -------------------------------------------------------------------------------- /spritesanddice/static/js/navigation.js: -------------------------------------------------------------------------------- 1 | const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; 2 | 3 | var scrollSpeed; 4 | var transparentNav = false; 5 | 6 | function showNav() { 7 | $('nav.navbar').removeClass('hidden'); 8 | } 9 | 10 | function hideNav() { 11 | $('nav.navbar').addClass('hidden'); 12 | } 13 | 14 | function initFixedNavbar() { 15 | $(document).bind('mousewheel DOMMouseScroll', function(e){ 16 | scrollSpeed = e.originalEvent.wheelDelta || e.originalEvent.detail || 0; 17 | 18 | let down = scrollSpeed >= 1; 19 | let up = scrollSpeed <= -1; 20 | if(!isFirefox){ 21 | up = scrollSpeed >= 1; 22 | down = scrollSpeed <= -1; 23 | } 24 | 25 | if (down) { hideNav(); } 26 | else if(up) { showNav(); } 27 | }); 28 | } 29 | 30 | function initHamburgerButton(){ 31 | $('.mobile-menu-trigger').click(function(e){ 32 | e.preventDefault(); 33 | $('.mobile-drawer').addClass('active'); 34 | $('.mobile-overlay').addClass('active'); 35 | }); 36 | $('.mobile-overlay').click(function(e){ 37 | e.preventDefault(); 38 | $('.mobile-drawer').removeClass('active'); 39 | $('.mobile-overlay').removeClass('active'); 40 | }) 41 | } 42 | 43 | function placeUserBarInNav(){ 44 | if($('.wagtail-userbar').length){ 45 | $('.wagtail-userbar').detach().appendTo('nav .userbar-container ul'); 46 | } 47 | } 48 | 49 | $(document).ready(function(){ 50 | initHamburgerButton(); 51 | initFixedNavbar(); 52 | }); 53 | 54 | // Rip the .wagtail-userbar out of its default location and append it to the nav 55 | $(window).on('load', function(){ 56 | placeUserBarInNav(); 57 | }) 58 | -------------------------------------------------------------------------------- /spritesanddice/static/js/spritesanddice.js: -------------------------------------------------------------------------------- 1 | function addEndMarkIcon(){ 2 | let last_paragraph = $('.blog-page .blog-post-content > .rich-text').last().find('p').last(); 3 | let p_not_empty = $.trim(last_paragraph.html()).length; 4 | if (p_not_empty) { 5 | $(last_paragraph).append(" "); 6 | } 7 | } 8 | 9 | function makeExternalLinksTargetBlank(){ 10 | // Make all outgoing links open in a new tab 11 | $('a').each(function() { 12 | if( location.hostname === this.hostname || !this.hostname.length ) { 13 | return; 14 | } else { 15 | $(this).attr('target', '_blank'); 16 | } 17 | }); 18 | } 19 | 20 | // Make it easier to target rich text youtube embeds with CSS (kinda hacky) 21 | function addClassToYouTubeContainer(){ 22 | $('.blog-page .blog-post-content iframe').closest('div').addClass('embed') 23 | } 24 | 25 | function initFontAwesome(){ 26 | // Prevents FA from converting tags to and breaking CSS 27 | window.FontAwesomeConfig = { autoReplaceSvg: false }; 28 | } 29 | 30 | $(document).ready(function(){ 31 | initFontAwesome(); 32 | addClassToYouTubeContainer(); 33 | addEndMarkIcon(); 34 | makeExternalLinksTargetBlank(); 35 | }); 36 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/admin/analytics.scss: -------------------------------------------------------------------------------- 1 | /* Wagalytics Style Fixes and Customizations */ 2 | 3 | header .fields .field { 4 | overflow: hidden; 5 | 6 | .field-content { 7 | width: 100%; 8 | } 9 | } 10 | 11 | header form.left { 12 | min-width: 500px; 13 | max-width: 100%; 14 | } 15 | 16 | .wagalytics { 17 | 18 | td.title { 19 | word-break: keep-all; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/admin/home.scss: -------------------------------------------------------------------------------- 1 | header { 2 | .welcome { 3 | text-align: center; 4 | margin: 0; 5 | padding-left: 3em; 6 | 7 | img { 8 | margin: 1em auto; 9 | max-height: 3em; 10 | display: block; 11 | max-width: 100%; 12 | } 13 | } 14 | 15 | .quick-actions { 16 | margin-right: 0; 17 | float: right; 18 | 19 | .row { 20 | display: flex; 21 | flex-direction: row; 22 | 23 | .col { 24 | margin: 1em 0 1em .5em; 25 | max-width: 33%; 26 | display: flex; 27 | flex-direction: column; 28 | 29 | a { 30 | line-height: 3em; 31 | 32 | &:before { 33 | line-height: 2.5em; 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/admin/login.scss: -------------------------------------------------------------------------------- 1 | @import '../colors.scss'; 2 | 3 | body.login{ 4 | .wrapper{ 5 | width: 450px; 6 | max-width: 100%; 7 | margin: 0 auto; 8 | padding: 0; 9 | } 10 | 11 | .content-wrapper { 12 | padding: 1em; 13 | } 14 | 15 | img.login-img{ 16 | display: block; 17 | margin: 10px auto; 18 | max-width: 90%; 19 | } 20 | 21 | h1{ 22 | font-size: 3em; 23 | text-align: center; 24 | } 25 | 26 | .full .iconfield .input:before{ 27 | left: 1em; 28 | } 29 | 30 | .fields li.full{ 31 | position: relative; 32 | width: 500px; 33 | margin: auto; 34 | padding: 0; 35 | max-width: 100%; 36 | overflow: hidden; 37 | 38 | @media (max-width: $width-xs) { 39 | input{ 40 | padding: 1em; 41 | } 42 | } 43 | @media (min-width: $width-xs) { 44 | input{ 45 | padding-left: 4em; 46 | } 47 | } 48 | 49 | &:nth-child(1){ 50 | border-top-left-radius: $border-radius; 51 | border-top-right-radius: $border-radius; 52 | } 53 | &:nth-child(2){ 54 | border-bottom-left-radius: $border-radius; 55 | border-bottom-right-radius: $border-radius; 56 | } 57 | } 58 | 59 | .submit button{ 60 | width:100%; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/admin/podcast.scss: -------------------------------------------------------------------------------- 1 | .podcast-results-title{ 2 | position: absolute; 3 | top: 30px; 4 | right: 50px; 5 | } 6 | 7 | .podcast-actions{ 8 | float: right; 9 | margin-top: -70px; 10 | } 11 | 12 | #podcast-search.search-form { 13 | right: 50px; 14 | top: 30px; 15 | position: absolute; 16 | border: 1px solid #e6e6e6; 17 | border-radius: 500px; 18 | overflow: hidden; 19 | z-index: 1; 20 | label{ 21 | display: none; 22 | } 23 | li{ 24 | padding: 0; 25 | } 26 | .field-content{ 27 | width: 100%; 28 | } 29 | input{ 30 | border: transparent; 31 | } 32 | .error-message{ 33 | display:none; 34 | } 35 | .error input{ 36 | background:#fafafa; 37 | } 38 | } 39 | 40 | #podcast{ 41 | display: flex; 42 | flex-direction: row; 43 | justify-content: flex-start; 44 | align-items: flex-start; 45 | padding: 30px 0; 46 | position: relative; 47 | 48 | #album-art{ 49 | max-width: 200px; 50 | img{ 51 | border-radius: 3px; 52 | } 53 | } 54 | 55 | #metadata{ 56 | max-width: 60%; 57 | padding: 15px 30px; 58 | 59 | h1{ 60 | text-transform: capitalize; 61 | } 62 | 63 | .episode-count{ 64 | opacity: .8; 65 | } 66 | 67 | > *{ 68 | margin-top: 0; 69 | } 70 | 71 | p > a { 72 | white-space: nowrap; 73 | } 74 | } 75 | } 76 | 77 | table.listing#podcast-table{ 78 | table-layout: fixed; 79 | width: 100%; 80 | 81 | .title-wrapper{ font-weight: 500; } 82 | 83 | a:not(:hover){ background: transparent } 84 | 85 | a{ 86 | transition: 0.3s ease-in-out all; 87 | text-overflow: ellipsis; 88 | overflow: hidden; 89 | white-space: nowrap; 90 | display: block; 91 | } 92 | th, td{ 93 | padding: .6em .8em; 94 | text-overflow: ellipsis; 95 | overflow: hidden; 96 | white-space: nowrap; 97 | &.select{ width: 20px; } 98 | &.episode{ width: 15px; } 99 | &.title{ width: 130px; } 100 | &.description{ width: 100px; } 101 | &.length{ width: 60px; } 102 | &.date{ width: 60px; } 103 | &.actions-cell{ 104 | width: 100px; 105 | ul.actions{ 106 | display:table-cell; 107 | > li{ float: right; margin:0; } 108 | } 109 | } 110 | } 111 | tbody{ 112 | border: none; 113 | tr{ 114 | border: none; 115 | transition: 0.1s ease-in-out all; 116 | &:nth-child(odd):not(.selected):not(:hover){ 117 | background-color: #f8f8f8; 118 | } 119 | td{ 120 | &.actions-cell .actions{ 121 | visibility: visible; 122 | } 123 | } 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/admin/summary.scss: -------------------------------------------------------------------------------- 1 | section.summary { 2 | background-color: #444; 3 | color: #FFF; 4 | 5 | margin: 0; 6 | padding-top: 0; 7 | padding-bottom: 1em; 8 | 9 | ul.stats { 10 | display: flex; 11 | justify-content: flex-start; 12 | padding: 0; 13 | flex-flow: row nowrap; 14 | overflow-x: scroll; 15 | 16 | li { 17 | float: none; 18 | display: flex; 19 | padding: 1.5em; 20 | margin: 0; 21 | justify-content: flex-start; 22 | white-space: nowrap; 23 | flex-grow: 1; 24 | min-width: 160px; 25 | max-width: 25%; 26 | 27 | a { 28 | display: flex; 29 | flex-direction: column; 30 | width: auto; 31 | flex-grow: 1; 32 | color: #ccc; 33 | 34 | span { 35 | display: inline; 36 | font-size: 2em; 37 | } 38 | } 39 | 40 | &::before { 41 | opacity: 0.66; 42 | font-size: 3em; 43 | } 44 | } 45 | 46 | /* Overrides "display:table" */ 47 | &::before { 48 | content: unset; 49 | display: none; 50 | } 51 | 52 | /* Fill empty space in last row */ 53 | &::after { 54 | content: " "; 55 | flex: auto; 56 | display: flex; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/admin/typography.scss: -------------------------------------------------------------------------------- 1 | body, p{ 2 | font-family: 'Roboto','Open Sans',Helvetica,sans-serif; 3 | } 4 | 5 | h1, h2, h3, h4, h5, h6{ 6 | font-family: 'Jaapokki','Roboto Slab',Helvetica,sans-serif; 7 | } 8 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/colors.scss: -------------------------------------------------------------------------------- 1 | // Sprites and Dice Brand Palette 2 | $light-blue: #00ACDB; 3 | $mid-blue: #0098B7; 4 | $darker-blue: #008DA9; 5 | $darkest-blue: #00667F; 6 | $sprites-grey: #232323; 7 | 8 | $light-grey: #CCCCCC; 9 | $lighter-grey: #EEEEEE; 10 | 11 | $text-color: #2E2E2E; 12 | $dark-text: #272727; 13 | 14 | $primary-link-color: #008da8; 15 | $secondary-link-color: #619d5f; 16 | 17 | $white: #F9F9F9; 18 | 19 | // Brand Colors 20 | $facebook: #3A5795; 21 | $twitter: #1DA1F2; 22 | $patreon: #FE5900; 23 | $discord: #7289DA; 24 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/fonts.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Declare @font-face tags here. 3 | */ 4 | 5 | 6 | /* ========== Roboto ========== */ 7 | 8 | @font-face { 9 | font-family: 'Roboto'; 10 | src: local('Roboto Regular'), 11 | local('Roboto-Regular'), 12 | url('../fonts/Roboto-Regular.ttf'); 13 | font-weight: normal; 14 | font-style: normal; 15 | font-display: swap; 16 | } 17 | 18 | /* ========== Open Sans ========== */ 19 | 20 | @font-face { 21 | font-family: 'Open Sans'; 22 | src: local('Open Sans Regular'), 23 | local('Open-Sans-Regular'), 24 | url('../fonts/opensans-regular-webfont.woff') format('woff'); 25 | font-weight: normal; 26 | font-style: normal; 27 | font-display: fallback; // Fallback for Roboto 28 | } 29 | 30 | 31 | /* ========== Jaapokki ========== */ 32 | 33 | @font-face { 34 | font-family: 'Jaapokki'; 35 | src: local('Jaapokki Regular'), 36 | local('Jaapokki-Regular'), 37 | url('../fonts/jaapokki-regular.woff') format('woff'); 38 | font-weight: normal; 39 | font-style: normal; 40 | font-display: swap; 41 | } 42 | 43 | 44 | /* ========== Roboto Slab ========== */ 45 | 46 | @font-face { 47 | font-family: 'Roboto Slab'; 48 | src: local('Roboto Slab Regular'), 49 | local('Roboto-Slab-Regular'), 50 | url('../fonts/RobotoSlab-Regular.ttf'); 51 | font-weight: normal; 52 | font-style: normal; 53 | font-display: fallback; // Fallback for Jaapokki 54 | } 55 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/icons.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/spritesanddice/static/scss/icons.scss -------------------------------------------------------------------------------- /spritesanddice/static/scss/main/author.scss: -------------------------------------------------------------------------------- 1 | .author-bio { 2 | font-size: 16px; 3 | .user-title { 4 | font-size: 0.8em; 5 | } 6 | } 7 | 8 | .user-bio-small { 9 | .title { 10 | margin-bottom: 0; 11 | } 12 | .subtitle { 13 | margin: 0; 14 | } 15 | } 16 | 17 | div.grid.user-grid { 18 | max-width: 650px; 19 | margin: auto; 20 | } 21 | 22 | .avatar-column { 23 | display: flex; 24 | justify-content: center; 25 | align-content: center; 26 | flex-flow: column nowrap; 27 | padding-right: 0!important; 28 | 29 | img { width: 100%; } 30 | } 31 | 32 | .social-links { 33 | display: flex; 34 | flex-flow: row nowrap; 35 | font-size: 0.8em; 36 | 37 | a { 38 | padding: .5em; 39 | margin: 5px; 40 | background: #aaa; 41 | border-radius: $border-radius; 42 | display: block; 43 | line-height: 1; 44 | 45 | &:nth-child(1){ 46 | margin-left: 0; 47 | } 48 | } 49 | 50 | i { 51 | color: $white; 52 | } 53 | 54 | li.twitter a { background: $twitter; } 55 | li.email a { background: #dd4b39; } 56 | li.website a { background: #464646; } 57 | } 58 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/main/blog-page.scss: -------------------------------------------------------------------------------- 1 | @import '../colors.scss'; 2 | 3 | .blog-folder { 4 | .folder-icon { 5 | display: inline-block; 6 | max-height: 2em; 7 | width: auto; 8 | margin-right: .25em; 9 | } 10 | } 11 | 12 | .blog-post-meta { 13 | display: flex; 14 | flex-flow: row wrap; 15 | align-items: center; 16 | 17 | span { 18 | margin-right: 1em; 19 | white-space: nowrap; 20 | line-height: 2; 21 | 22 | .fa { 23 | margin-right: .25em; 24 | color: $light-blue; 25 | } 26 | } 27 | } 28 | 29 | twitter-widget { 30 | margin: auto; 31 | } 32 | 33 | .blog-page { 34 | .blog-post-content {} 35 | 36 | .blog-post-header { 37 | margin: 0; 38 | 39 | img { 40 | width: 100%; 41 | } 42 | 43 | // When the sidebar is gone, let images bleed to the edges 44 | @media (max-width: $width-sm){ 45 | margin: 0 -15px; 46 | } 47 | } 48 | 49 | .review-block { 50 | line-height: 1.8; 51 | padding: 10px 20px; 52 | margin: 1.5em 0; 53 | border-left: 5px solid $lighter-grey; 54 | overflow: hidden; 55 | 56 | .box-art { 57 | display: inline; 58 | margin-right: 1em; 59 | } 60 | } 61 | 62 | // Youtube Embeds 63 | div.embed { 64 | position: relative; 65 | padding-bottom: 56.25%; 66 | padding-top: 30px; 67 | height: 0; 68 | overflow: hidden; 69 | margin: 0; 70 | max-width: unset; 71 | 72 | iframe, 73 | object, 74 | embed { 75 | position: absolute; 76 | top: 0; 77 | left: 0; 78 | width: 100%; 79 | height: 100%; 80 | } 81 | } 82 | 83 | .rich-text { 84 | div.embed { 85 | margin: 15px 0; 86 | } 87 | } 88 | 89 | 90 | .social-media{ 91 | margin: 0; 92 | margin-bottom: 15px; 93 | display: block; 94 | overflow: hidden; 95 | 96 | a { 97 | color: #fff; 98 | } 99 | 100 | .fa { 101 | margin: 0 5px; 102 | background: transparent; 103 | } 104 | 105 | .fb-like, 106 | .twitter-follow, 107 | .patreon-back { 108 | float: left; 109 | display: inline-block; 110 | width: 33.33%; 111 | margin: 0 auto; 112 | text-align: center; 113 | font-size: 1.2rem; 114 | line-height:2.5; 115 | } 116 | 117 | .fb-like{ background: $facebook; } 118 | 119 | .twitter-follow{ background: $twitter; } 120 | 121 | .patreon-back{ background: $patreon; } 122 | } 123 | } 124 | 125 | .image-block { 126 | text-align: center; 127 | 128 | 129 | p { 130 | padding-left: 1rem; 131 | padding-right: 1rem; 132 | } 133 | 134 | img { 135 | max-height: 75vh; /* dont make em too tall */ 136 | width: auto; 137 | margin: auto; 138 | // When the sidebar is visible, give it room to breathe 139 | @media (min-width: $width-sm) { 140 | max-width: 95%; 141 | } 142 | } 143 | // When the sidebar is gone, let images bleed to the edges 144 | @media (max-width: $width-sm){ 145 | margin: 0 -15px; 146 | } 147 | 148 | .rich-text{ 149 | margin: 1em auto; 150 | max-width: 500px; 151 | font-size: 0.8em; 152 | } 153 | } 154 | 155 | .author-bio { 156 | 157 | } 158 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/main/calendar.scss: -------------------------------------------------------------------------------- 1 | body.calendar { 2 | .event { 3 | border-radius: $border-radius; 4 | padding: 1em; 5 | margin-bottom: 1em; 6 | 7 | &.stream { 8 | background-color: purple; 9 | } 10 | &.online { 11 | background-color: blue; 12 | } 13 | &.meatspace { 14 | background-color: yellow; 15 | } 16 | &.convention { 17 | background-color: red; 18 | } 19 | &.other { 20 | background-color: orange; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/main/home-page.scss: -------------------------------------------------------------------------------- 1 | @import '../colors.scss'; 2 | 3 | .home-page{} 4 | 5 | div.blog-post-listing.grid { 6 | margin: 0 auto; 7 | font-size: 16px; 8 | 9 | .blog-post-meta { 10 | max-width: 650px; 11 | display: flex; 12 | flex-direction: column; 13 | margin-top: .15em; 14 | margin-left: auto; 15 | margin-right: auto; 16 | justify-content: flex-start; 17 | align-items: flex-start; 18 | width: 100%; 19 | 20 | a { 21 | color: $secondary-link-color; 22 | } 23 | } 24 | 25 | .title, .subtitle { 26 | line-height: 1.15; 27 | margin: 0 auto; 28 | width: 100%; 29 | max-width: 650px; 30 | } 31 | 32 | > div { 33 | display: flex; 34 | flex-flow: column nowrap; 35 | justify-content: center; 36 | align-content: center; 37 | } 38 | 39 | &.hero { 40 | 41 | > .col-12 { 42 | padding: 0; 43 | } 44 | 45 | .btn{ 46 | display: block; 47 | max-width: max-content; 48 | font-size: .8em; 49 | text-align: center; 50 | margin-right: auto; 51 | margin-left: 0; 52 | } 53 | 54 | .hero-image { 55 | margin: -15px; 56 | background: $sprites-grey; 57 | position: relative; 58 | height: 0; 59 | padding-bottom: 44%; 60 | margin-bottom: 1.65em; 61 | 62 | img { 63 | box-shadow: inset 0px -10px 25px -5px rgba(39,39,39,1); 64 | width: 100%; 65 | } 66 | } 67 | 68 | .hero{ 69 | .title,.subtitle{ 70 | line-height: 1.15; 71 | } 72 | } 73 | 74 | h1, h2, h3, p { 75 | width: 100%; 76 | max-width: 650px; 77 | margin-left: auto; 78 | margin-right: auto; 79 | } 80 | } 81 | 82 | &.small{ 83 | max-width: 650px; 84 | padding: 1.25em 0; 85 | } 86 | 87 | &.medium { 88 | padding: 1.65em 0; 89 | 90 | .title { 91 | margin-bottom: .15em; 92 | } 93 | } 94 | 95 | &.large{ 96 | padding: 2.5em 0; 97 | max-width: 750px; 98 | 99 | div.title-col { 100 | justify-content: flex-end; 101 | align-items: flex-end; 102 | } 103 | } 104 | 105 | &.hero { 106 | .title, .subtitle { 107 | line-height: 1.65; 108 | } 109 | } 110 | 111 | .listing-image { 112 | height: auto; 113 | 114 | a { 115 | display: block; 116 | position: relative; 117 | } 118 | 119 | img { 120 | width: 100%; 121 | margin: auto; 122 | } 123 | 124 | .placeholder { 125 | background: $sprites-grey; 126 | height: 100%; 127 | width: 100%; 128 | min-height: 200px; 129 | } 130 | } 131 | 132 | // Show an ellipsis after 3 lines of text 133 | .listing-description{ 134 | max-height: calc(1.7em * 3); 135 | overflow: hidden; 136 | text-overflow: ellipsis; 137 | display: block; 138 | max-width: 100%; 139 | display: -webkit-box; 140 | -webkit-line-clamp: 3; 141 | -webkit-box-orient: vertical; 142 | max-width: 85%; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/main/navigation/footer.scss: -------------------------------------------------------------------------------- 1 | footer{ 2 | color: $white; 3 | background: $dark-text; 4 | a { 5 | color: $primary-link-color; 6 | } 7 | } 8 | 9 | .footer-social-icons { 10 | display: flex; 11 | justify-content: space-between; 12 | } 13 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/main/navigation/header.scss: -------------------------------------------------------------------------------- 1 | header { 2 | width: 100%; 3 | background: $dark-text; 4 | display: block; 5 | color: $white; 6 | padding: 15px 0; 7 | border-bottom: 2px solid #CCC; 8 | 9 | .header-logo{ 10 | .update-schedule{ 11 | margin-top: 1em; 12 | text-align: center; 13 | text-transform: uppercase; 14 | } 15 | } 16 | 17 | @media (min-width: $width-sm) { 18 | .header-search { 19 | margin-top: 1.6%; // Line up with clear space in site logo in desktop mode 20 | } 21 | } 22 | 23 | .header-search { 24 | .search-input-container { 25 | input { 26 | font-size: .75em; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/main/navigation/sidebar.scss: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | justify-content: space-between; 3 | display: flex!important; 4 | flex-direction: column; 5 | height: 100%; 6 | 7 | .text { 8 | padding-left: 15px; 9 | padding-right: 15px; 10 | } 11 | } 12 | 13 | 14 | img.sidebar-icon { 15 | display: inline-block; 16 | max-height: 2.25rem; 17 | width: auto; 18 | } 19 | 20 | @media (max-width: $width-lg) { 21 | .cta-prefix, 22 | img.sidebar-icon { 23 | display: none; 24 | } 25 | } 26 | 27 | .sidebar-posts { 28 | display: flex; 29 | flex-direction: column; 30 | margin: 1.65em auto; 31 | 32 | h2, h3, h4, h5 { 33 | a { 34 | color: $sprites-grey; 35 | span { color: $sprites-grey; } 36 | } 37 | } 38 | 39 | // Category title 40 | h4 { 41 | margin-bottom: 0; 42 | 43 | span { 44 | white-space: nowrap; 45 | } 46 | } 47 | 48 | h6 { 49 | margin: 0; 50 | line-height: 1.65; 51 | } 52 | 53 | .sidebar-blog-post { 54 | background: $lighter-grey; 55 | padding-bottom: 1.5em; 56 | margin-bottom: 3em; 57 | flex-direction: column; 58 | 59 | small { 60 | display: inline-block; 61 | } 62 | } 63 | 64 | .byline { 65 | margin: 0; 66 | } 67 | 68 | .post-date { 69 | float: right; 70 | } 71 | 72 | .thumb { 73 | height: 0; 74 | padding-bottom: 66%; 75 | background: $sprites-grey; 76 | position: relative; 77 | margin-bottom: 1.65em; 78 | 79 | img { 80 | width: 100%; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/main/password-required.scss: -------------------------------------------------------------------------------- 1 | .password-protected-page { 2 | #password-content { 3 | input { 4 | font-size: 1.5em; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/main/podcast.scss: -------------------------------------------------------------------------------- 1 | .podcast-player { 2 | display: flex; 3 | flex-direction: row; 4 | align-items: center; 5 | align-content: stretch; 6 | background: #191919; // Match the default background of Firefox's HTML5 audio tag 7 | margin: 15px 0; 8 | 9 | img, 10 | .audio-content { 11 | display: flex; 12 | flex-direction: column; 13 | } 14 | 15 | img { 16 | height: 125px; 17 | width: auto; 18 | } 19 | 20 | .audio-content { 21 | flex-grow: 1; 22 | padding: 0 1em; 23 | 24 | .audio-title { 25 | margin: 0.25em 1em; 26 | 27 | p { 28 | color: #FFF; 29 | margin: 0; 30 | float: left; 31 | } 32 | 33 | a { 34 | display: inline; 35 | float: right; 36 | 37 | svg { 38 | height: 1em; 39 | vertical-align: middle; 40 | 41 | .icon { 42 | stroke: #FFF; 43 | } 44 | } 45 | } 46 | } 47 | 48 | audio { 49 | width: 100%; 50 | display: block; 51 | padding: 0.25em 0; 52 | 53 | &::-webkit-media-controls-panel { 54 | background: #191919; 55 | } 56 | 57 | &::-webkit-media-controls-enclosure { 58 | border-radius: 0; 59 | box-shadow: none; 60 | } 61 | 62 | &::-webkit-media-controls-mute-button, 63 | &::-webkit-media-controls-play-button, 64 | &::-webkit-media-controls-seek-back-button, 65 | &::-webkit-media-controls-seek-forward-button, 66 | &::-webkit-media-controls-fullscreen-button, 67 | &::-webkit-media-controls-rewind-button, 68 | &::-webkit-media-controls-return-to-realtime-button, 69 | &::-webkit-media-controls-toggle-closed-captions-button { 70 | -webkit-filter: invert(100%); 71 | } 72 | 73 | &::-webkit-media-controls-volume-slider { 74 | -webkit-filter: invert(100%); 75 | box-shadow: none; 76 | } 77 | 78 | &::-internal-media-controls-overflow-button { 79 | -webkit-filter: invert(10); 80 | } 81 | 82 | &::-webkit-media-controls-timeline { 83 | -webkit-filter: invert(100%); 84 | } 85 | 86 | &::-webkit-media-controls-time-remaining-display, 87 | &::-webkit-media-controls-current-time-display { 88 | color: #FFF; 89 | text-shadow: none; 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/main/search.scss: -------------------------------------------------------------------------------- 1 | .search-input-container{ 2 | display: flex; 3 | flex-flow: row nowrap; 4 | align-items: stretch; 5 | width: 100%; 6 | overflow: hidden; 7 | font-size: 1rem; 8 | 9 | input, button { 10 | display: flex; 11 | flex-direction: column; 12 | } 13 | 14 | input { 15 | background: $lighter-grey; 16 | color: $sprites-grey; 17 | border: none; 18 | padding: .5em .75em; 19 | 20 | border-top-left-radius: $border-radius; 21 | border-bottom-left-radius: $border-radius; 22 | border-top-right-radius: 0px; 23 | border-bottom-right-radius: 0px; 24 | 25 | width: calc(100% - 3.5em); 26 | } 27 | 28 | button { 29 | border-top-left-radius: 0px; 30 | border-bottom-left-radius: 0px; 31 | border-top-right-radius: $border-radius; 32 | border-bottom-right-radius: $border-radius; 33 | 34 | padding: 0; 35 | margin: 0; 36 | border: none; 37 | 38 | align-items: center; // Vertical Align Icon 39 | font-size: 1em; 40 | padding: .5em; 41 | color: #FFF; 42 | } 43 | } 44 | 45 | // Search Results Page Only 46 | .search-results-page { 47 | 48 | .search-page-header { 49 | align-items: center; 50 | 51 | .search-form { 52 | .search-input-container { 53 | justify-content: flex-end; 54 | 55 | input { 56 | font-size: 1em; 57 | } 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/main/typography.scss: -------------------------------------------------------------------------------- 1 | body{ 2 | color: $text-color; 3 | font-family: 'Roboto',Helvetica,sans-serif; 4 | font-size: 18px; 5 | font-weight: 300; 6 | line-height: 1.65; 7 | 8 | @media (max-width: $width-sm) { font-size: 16px; } 9 | } 10 | 11 | /* keep content from being too wide to read comfortably */ 12 | .blog-post-content > .rich-text, 13 | .tags { 14 | margin: auto; 15 | max-width: 650px; 16 | } 17 | 18 | header{ 19 | .header-logo .update-schedule{ 20 | font-family: 'Jaapokki'; 21 | } 22 | } 23 | 24 | nav{ 25 | font-family: 'Jaapokki','Roboto',Helvetica,sans-serif; 26 | font-size: 1.15em; 27 | } 28 | 29 | h1, h2, h3, h4, h5, h6 { 30 | font-family: "Jaapokki", "Roboto Slab"; 31 | color: $sprites-grey; 32 | margin: 1.15rem 0; 33 | line-height: 1.15; 34 | a { 35 | color: $sprites-grey; 36 | } 37 | } 38 | 39 | .btn { 40 | font-size: 1.15em; 41 | font-family: 'Jaapokki'; 42 | } 43 | 44 | h1 { font-size: 3.052em; } 45 | h2 { font-size: 2.441em; } 46 | h3 { font-size: 1.953em; } 47 | h4 { font-size: 1.563em; } 48 | h5 { font-size: 1.25em; } 49 | h6 { font-size: 1em; } 50 | 51 | .subtitle{ 52 | font-family: 'Roboto'; 53 | color: $sprites-grey; 54 | opacity: 0.6; 55 | 56 | a { 57 | color: $sprites-grey; 58 | } 59 | } 60 | 61 | p { 62 | margin: 1.65rem auto; 63 | line-height: 1.65; 64 | max-width: 650px; 65 | 66 | &:empty { 67 | margin: 0; 68 | } 69 | } 70 | 71 | b { 72 | font-weight: bold; 73 | } 74 | 75 | .rich-text ul { 76 | list-style-type: initial; 77 | padding: initial; 78 | margin: 1.7rem auto; 79 | line-height: 2; 80 | padding: 0 1em; 81 | } 82 | 83 | blockquote { 84 | font-size: 1.25em; 85 | margin: 1.5em auto; 86 | padding: 1em 1.5em; 87 | border-left: 1px solid #aaa; 88 | opacity: .75; 89 | } 90 | 91 | small { 92 | font-size: 0.8em; 93 | opacity: 0.8; 94 | 95 | a { 96 | color: $secondary-link-color; 97 | } 98 | } 99 | 100 | .richtext-image { 101 | height: auto; 102 | 103 | &.full-width { 104 | width: 100%; 105 | } 106 | 107 | &.left { 108 | max-width: 50%; 109 | float: left; 110 | padding-right: 1.5em; 111 | } 112 | 113 | &.right{ 114 | max-width: 50%; 115 | float: right; 116 | padding-left: 1.5em; 117 | } 118 | } 119 | 120 | .wagtail-userbar-items { 121 | font-family: 'Roboto', sans-serif; 122 | } 123 | 124 | i.endmark { 125 | content: ""; 126 | margin-left: 5px; 127 | margin-bottom: -1px; 128 | height: 15px; 129 | width: 16px; 130 | background-size: 100%; 131 | background-image: url('/static/img/snd_end_mark.png'); 132 | display: inline; 133 | } 134 | 135 | .tag { 136 | font-size: .8em; 137 | } 138 | -------------------------------------------------------------------------------- /spritesanddice/static/scss/variables.scss: -------------------------------------------------------------------------------- 1 | // Keep a consistent border radius on all buttons / tags 2 | $border-radius: 2.5px; 3 | 4 | // Reflex Grid Breakpoints 5 | $width-xs: 576px; 6 | $width-sm: 768px; 7 | $width-md: 992px; 8 | $width-lg: 1200px; 9 | $width-xlg: 1600px; 10 | -------------------------------------------------------------------------------- /spritesanddice/static/svg/logo-big.svg: -------------------------------------------------------------------------------- 1 | logo -------------------------------------------------------------------------------- /spritesanddice/stream_blocks.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from users.models import User 4 | 5 | from wagtail.core import blocks 6 | from wagtail.images.blocks import ImageChooserBlock 7 | from wagtail.documents.blocks import DocumentChooserBlock 8 | from wagtail.snippets.blocks import SnippetChooserBlock 9 | 10 | # Generic User Chooser 11 | class UserChooserBlock(blocks.ChooserBlock): 12 | target_model = User 13 | widget = forms.Select 14 | 15 | # Return the key value for the select field 16 | def value_for_form(self, value): 17 | if isinstance(value, self.target_model): 18 | return value.pk 19 | else: 20 | return value 21 | 22 | class Meta: 23 | icon = "user" 24 | 25 | 26 | class ImageBlock(blocks.StructBlock): 27 | image = ImageChooserBlock(required=False) 28 | caption = blocks.RichTextBlock(required=False, features=['bold', 'italic', 'link']) 29 | 30 | class Meta: 31 | icon = "image" 32 | template = 'blocks/image_block.html' 33 | 34 | 35 | class PodcastBlock(blocks.StructBlock): 36 | podcast = SnippetChooserBlock('podcast.Podcast') 37 | 38 | def get_context(self, value, parent_context=None): 39 | context = super().get_context(value, parent_context=parent_context) 40 | context['podcast'] = value['podcast'] 41 | return context 42 | 43 | class Meta: 44 | template = 'podcast/player.html' 45 | 46 | 47 | class GameBlock(blocks.StructBlock): 48 | game = SnippetChooserBlock('game.Game') 49 | 50 | def get_context(self, value, parent_context=None): 51 | context = super().get_context(value, parent_context=parent_context) 52 | context['game'] = value['game'] 53 | return context 54 | 55 | class Meta: 56 | template = 'blocks/review_block.html' 57 | 58 | 59 | class AuthorBlock(blocks.StructBlock): 60 | user = UserChooserBlock() 61 | 62 | def get_context(self, value, parent_context=None): 63 | context = super().get_context(value, parent_context=parent_context) 64 | context['user'] = value['user'] 65 | return context 66 | 67 | class Meta: 68 | template = 'users/author_bio.html' 69 | 70 | 71 | class UserGrid(blocks.StructBlock): 72 | users = blocks.ListBlock(blocks.StructBlock([ 73 | ('user', UserChooserBlock()), 74 | ])) 75 | 76 | def get_context(self, value, parent_context=None): 77 | context = super().get_context(value, parent_context=parent_context) 78 | context['users'] = [x['user'] for x in value['users']] 79 | return context 80 | 81 | class Meta: 82 | template = 'users/user_grid.html' 83 | 84 | 85 | # Stream Blocks for all content types 86 | stream_blocks = [ 87 | # Default 88 | ('Image', ImageBlock()), 89 | ('Rich_Text', blocks.RichTextBlock()), 90 | ] 91 | 92 | # Basic Pages Only 93 | basic_blocks = stream_blocks + [ 94 | ('Author_Bio', AuthorBlock(icon='fa-user')), 95 | ('User_Grid', UserGrid(icon='fa-users')), 96 | ] 97 | 98 | # Blog Page Only 99 | blog_blocks = stream_blocks + [ 100 | ('Podcast', PodcastBlock(icon='fa-headphones')), 101 | ('Game', GameBlock(icon='fa-pencil')), 102 | ] 103 | -------------------------------------------------------------------------------- /spritesanddice/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body_class %}blog-page template-404{% endblock %} 4 | 5 | {% block content %} 6 |
    7 |
    8 |
    9 |

    Page not found

    10 |
    11 |
    12 |

    13 |
    14 |
    15 |
    16 | {% include 'navigation/sidebar.html' %} 17 |
    18 |
    19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /spritesanddice/templates/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Internal server error 6 | 7 | 8 | 9 |

    Internal server error

    10 | 11 |

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

    12 | 13 | 14 | -------------------------------------------------------------------------------- /spritesanddice/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static wagtailuserbar wagtailfontawesome wagtailimages_tags menu_tags %} 2 | 3 | 4 | {% with request.site.site_name as site_name %} 5 | 6 | 7 | 8 | 9 | 10 | {% block title %} 11 | {% if self.seo_title %}{{ self.seo_title }}{% else %}{{ self.title }}{% endif %} 12 | {% endblock %} 13 | {% if self.title != "Sprites and Dice" %} | Sprites and Dice{% else %}| {{settings.spritesanddice.SiteSettings.slogan}}{% endif %} 14 | 15 | 16 | {% if page %} 17 | {% include "page_meta.html"%} 18 | {% else %} 19 | {% include "meta_tags.html" %} 20 | {% endif %} 21 | 22 | {% include "google_analytics.html" %} 23 | 24 | 25 | 26 | {# Preload the most important fonts #} 27 | 28 | 29 | 30 | {# Global stylesheets #} 31 | 32 | 33 | 34 | 35 | {% block extra_css %} 36 | {# Override this in templates to add extra stylesheets #} 37 | {% endblock %} 38 | 39 | {% endwith %} 40 | 41 | 42 | {% wagtailuserbar %} 43 | 44 | {% block header %} 45 | {% include 'navigation/header.html' %} 46 | {% endblock %} 47 | 48 | {% include 'navigation/navbar.html' %} 49 | 50 |
    51 | {% block content %} 52 | {% endblock %} 53 |
    54 | 55 | {% include 'navigation/footer.html' %} 56 | 57 | {# Global javascript #} 58 | 59 | 60 | 61 | 62 | {% block extra_js %} 63 | {# Override this in templates to add extra javascript #} 64 | {% endblock %} 65 | 66 | 67 | -------------------------------------------------------------------------------- /spritesanddice/templates/disqus.html: -------------------------------------------------------------------------------- 1 | {% if request.site.hostname == 'www.spritesanddice.com' %} 2 |
    3 | 20 | 23 | 24 | {% endif %} 25 | -------------------------------------------------------------------------------- /spritesanddice/templates/google_analytics.html: -------------------------------------------------------------------------------- 1 | {% if request.site.hostname == 'www.spritesanddice.com' %} 2 | 3 | 4 | 11 | {% endif %} 12 | -------------------------------------------------------------------------------- /spritesanddice/templates/meta_tags.html: -------------------------------------------------------------------------------- 1 | {% load static wagtailuserbar wagtailfontawesome wagtailimages_tags menu_tags %} 2 | 3 | {% with settings.spritesanddice.SiteSettings as meta %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% image meta.default_social_thumb original as meta_image %} 17 | 18 | 19 | 20 | {% block additional_meta %}{% endblock %} 21 | {% endwith %} 22 | -------------------------------------------------------------------------------- /spritesanddice/templates/navigation/footer.html: -------------------------------------------------------------------------------- 1 | {% load menu_tags %} 2 | 22 | -------------------------------------------------------------------------------- /spritesanddice/templates/navigation/header.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 26 | -------------------------------------------------------------------------------- /spritesanddice/templates/navigation/mobile_menu.html: -------------------------------------------------------------------------------- 1 | {% load static wagtailimages_tags menu_tags %} 2 | 3 | 34 |
    35 | -------------------------------------------------------------------------------- /spritesanddice/templates/navigation/mobile_social_links.html: -------------------------------------------------------------------------------- 1 |
  • 2 | 3 | Facebook 4 | 5 |
  • 6 |
  • 7 | 8 | Patreon 9 | 10 |
  • 11 |
  • 12 | 13 | Discord 14 | 15 |
  • 16 |
  • 17 | 18 | Twitter 19 | 20 |
  • 21 |
  • 22 | 23 | Email 24 | 25 |
  • 26 | -------------------------------------------------------------------------------- /spritesanddice/templates/navigation/navbar.html: -------------------------------------------------------------------------------- 1 | {% load static wagtailimages_tags menu_tags %} 2 | 49 | 50 | {% include "navigation/mobile_menu.html" %} 51 | -------------------------------------------------------------------------------- /spritesanddice/templates/navigation/pagination.html: -------------------------------------------------------------------------------- 1 | {% load menu_tags %} 2 | {% if pages.paginator.num_pages > 1%} 3 | 38 | {% endif %} 39 | -------------------------------------------------------------------------------- /spritesanddice/templates/navigation/sidebar.html: -------------------------------------------------------------------------------- 1 | {% load menu_tags %} 2 | 5 | -------------------------------------------------------------------------------- /spritesanddice/templates/navigation/sidebar_posts.html: -------------------------------------------------------------------------------- 1 | {% load wagtailimages_tags menu_tags %} 2 | 3 | {% for category in categories %} 4 | {# Don't show empty categories #} 5 | {% if category.children|length > 0 %} 6 | 21 | {% endif %} 22 | {% endfor %} 23 | -------------------------------------------------------------------------------- /spritesanddice/templates/navigation/social-link-icons.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /spritesanddice/templates/page_meta.html: -------------------------------------------------------------------------------- 1 | {% extends "meta_tags.html" %} 2 | 3 | {% load wagtailimages_tags menu_tags %} 4 | 5 | {% block og_title %}{% if page.seo_title %}{{page.seo_title}}{% else %}{{page.title}}{% endif %}{% endblock %} 6 | 7 | {% block og_description %}{{page.subtitle}}{% endblock %} 8 | {% block meta_description %}{{page.subtitle}}{% endblock %} 9 | 10 | {% block og_image %}{% image page.header_image width-1080 as header_image %}https://www.spritesanddice.com{{header_image.url}}{% endblock %} 11 | 12 | {% block og_url %}{{page.full_url}}{% endblock %} 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /spritesanddice/templates/password_required.html: -------------------------------------------------------------------------------- 1 | {% extends "page/base_page.html" %} 2 | {% load i18n wagtailcore_tags wagtailimages_tags menu_tags %} 3 | 4 | {% block body_class %}password-protected-page{% endblock %} 5 | 6 | {% block title %}Password Required{% endblock %} 7 | 8 | {% block page_feed %} 9 |
    10 |
    11 | 12 |

    {% trans "Password required" %}

    13 | {% block password_required_message %} 14 |

    {% trans "This page is not intended for the public just yet. If you have a password, enter it below." %}

    15 | {% endblock %} 16 | 17 |
    18 | {% csrf_token %} 19 | 20 | 21 | {{form.return_url}} 22 | 23 | 24 | 25 |
    26 |
    27 |
    28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /spritesanddice/templates/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /spritesanddice/templates/rss.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% autoescape off %} 4 | 5 | 6 | Sprites and Dice - Life is short, so have fun gaming.{# TODO: Site slogan should be an admin setting #} 7 | Sprites and Dice Posts Feed 8 | https://www.spritesanddice.com/rss.xml 9 | 10 | 11 | 12 | en 13 | 14 | Games & Hobbies 15 | 16 | contact@spritesanddice.com 17 | 18 | wyatt@spritesanddice.com (Wyatt Krause) 19 | 20 | jon@spritesanddice.com (Jon Glover) 21 | 22 | {% for page in pages %} 23 | 24 | {{page.title}} 25 | https://www.spritesanddice.com{{page.url}} 26 | {{page.search_description}} 27 | {{page.xml_pubdate}} 28 | {{page.xml_lastbuilddate}} 29 | {{page.author.email}} 30 | https://www.spritesanddice.com{{page.url}}#disqus_thread 31 | 32 | {% endfor %} 33 | 34 | {% endautoescape %} 35 | 36 | -------------------------------------------------------------------------------- /spritesanddice/templates/wagtailadmin/admin_base.html: -------------------------------------------------------------------------------- 1 | {% extends "wagtailadmin/admin_base.html" %} 2 | {% load static %} 3 | 4 | {% block branding_favicon %} 5 | 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /spritesanddice/templates/wagtailadmin/base.html: -------------------------------------------------------------------------------- 1 | {% extends "wagtailadmin/base.html" %} 2 | {% load static %} 3 | 4 | {% block branding_logo %} 5 | Sprites and Dice 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /spritesanddice/templates/wagtailadmin/home.html: -------------------------------------------------------------------------------- 1 | {% extends "wagtailadmin/base.html" %} 2 | 3 | {% load wagtailadmin_tags static i18n admin_tags %} 4 | 5 | {% block titletag %}{% trans "Dashboard" %}{% endblock %} 6 | 7 | {% block bodyclass %}homepage{% endblock %} 8 | 9 | {% block extra_css %} 10 | {{ block.super }} 11 | 12 | {% endblock %} 13 | 14 | {% block content %} 15 | 16 |
    17 |
    18 |
    19 | 20 | Sprites and Dice 21 | 22 |
    23 |
    24 |
    25 |
    26 | 40 |
    41 |
    42 | Podcast 43 |
    44 |
    45 | Game 46 |
    47 |
    48 |
    49 |
    50 |
    51 | 52 | {% if panels %} 53 | 54 | {% for panel in panels %} 55 | {{ panel.render }} 56 | {% endfor %} 57 | 58 |
    59 |
    60 | {% review_copies %} 61 |
    62 |
    63 | 64 | {% endif %} 65 | 66 | {% endblock %} 67 | -------------------------------------------------------------------------------- /spritesanddice/templates/wagtailadmin/login.html: -------------------------------------------------------------------------------- 1 | {% extends "wagtailadmin/login.html" %} 2 | {% load static %} 3 | 4 | {% block above_login %} 5 | Sprites and Dice 6 | {% endblock %} 7 | 8 | {% block branding_login %}{% endblock %} 9 | -------------------------------------------------------------------------------- /spritesanddice/templates/wagtailadmin/review_copies.html: -------------------------------------------------------------------------------- 1 | {% load menu_tags wagtailimages_tags %} 2 | 3 |
    4 |

    Available Review Copies

    5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for game in games %} 15 | 16 | 21 | 26 | 31 | 32 | {% endfor %} 33 | 34 |
    GameAvailable
    17 | 18 | {% image game.box_art height-80 %} 19 | 20 | 22 | 23 | {{game.title}} 24 | 25 | 27 | 28 | {{game.available_copies}} 29 | 30 |
    35 |
    36 | -------------------------------------------------------------------------------- /spritesanddice/templates/wagtailadmin/skeleton.html: -------------------------------------------------------------------------------- 1 | 2 | {% load static i18n %} 3 | {% get_current_language as LANGUAGE_CODE %} 4 | 5 | 6 | 7 | 8 | {# Dev note - This file literally only exists to customize the title tag #} 9 | S+D Admin | {% block titletag %}{% endblock %} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% block css %}{% endblock %} 20 | 21 | {% block branding_favicon %}{% endblock %} 22 | 23 | 24 | 27 | 33 | 34 | {% block js %}{% endblock %} 35 | 36 |
    37 | {% block furniture %}{% endblock %} 38 |
    39 | 40 | 41 | -------------------------------------------------------------------------------- /spritesanddice/templates/wagtailadmin/userbar/base.html: -------------------------------------------------------------------------------- 1 | {% load static i18n %} 2 | 3 | 4 |
  • 5 | 6 | 7 | {% if request.user.get_short_name|length > 0 %} 8 | {{ request.user.get_short_name }} 9 | {% else %} 10 | {{ request.user.get_username }} 11 | {% endif %} 12 | 13 | {% if request.user.wagtail_userprofile.avatar %} 14 | {{ request.user.get_full_name }} 19 | {% else %} 20 | {{ request.user.get_full_name }} 24 | {% endif %} 25 | 45 | 46 |
  • 47 | 48 | 49 | -------------------------------------------------------------------------------- /spritesanddice/templatetags/admin_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | from game.models import Game 4 | 5 | from page.models import BlogFolder 6 | 7 | register = template.Library() 8 | 9 | @register.inclusion_tag('wagtailadmin/review_copies.html') 10 | def review_copies(): 11 | games = filter(lambda game: game.available_copies() > 0, Game.objects.all()) 12 | return { 'games': games } 13 | 14 | @register.simple_tag() 15 | def folders(): 16 | return BlogFolder.objects.all().exact_type(BlogFolder) 17 | -------------------------------------------------------------------------------- /spritesanddice/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls import include, url 3 | from django.urls import path, re_path 4 | from django.contrib import admin 5 | from django.views.generic import TemplateView 6 | 7 | from wagtail.admin import urls as wagtailadmin_urls 8 | from wagtail.core import urls as wagtail_urls 9 | from wagtail.documents import urls as wagtaildocs_urls 10 | 11 | from page import views as page_views 12 | 13 | from podcast import urls as podcast_urls 14 | from podcast import views as podcast_views 15 | 16 | from users import urls as user_urls 17 | from users import views as user_views 18 | 19 | from search import views as search_views 20 | 21 | urlpatterns = [ 22 | url(r'^django-admin/', admin.site.urls), 23 | 24 | url(r'^admin/snippets/', include(podcast_urls)), 25 | 26 | path('calendar/', include('event.urls', namespace='event')), 27 | 28 | url(r'^admin/', include(wagtailadmin_urls)), 29 | url(r'^documents/', include(wagtaildocs_urls)), 30 | url(r'^users/', include(user_urls), name='users'), 31 | 32 | url(r'^tags/(?P[\w-]+)/', page_views.tag_page, name='pages_by_tag'), 33 | url(r'^tags/?$', page_views.tag_index, name='tags'), 34 | 35 | url(r'^search/$', search_views.search, name='search'), 36 | 37 | url(r'^robots\.txt$', TemplateView.as_view(template_name='robots.txt', content_type='text/plain')), 38 | 39 | url(r'^podcast\.xml$', podcast_views.get_podcast_feed, name='xml'), 40 | 41 | url(r'^rss\.xml$', page_views.get_rss_feed, name='rss'), 42 | 43 | # For anything not caught by a more specific rule above, hand over to 44 | # Wagtail's page serving mechanism. This should be the last pattern in 45 | # the list: 46 | url(r'', include(wagtail_urls)), 47 | ] 48 | 49 | 50 | if settings.DEBUG: 51 | from django.conf.urls.static import static 52 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 53 | 54 | # Serve static and media files from development server 55 | urlpatterns += staticfiles_urlpatterns() 56 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 57 | -------------------------------------------------------------------------------- /spritesanddice/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for spritesanddice project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "spritesanddice.settings.production") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /spritesanddice/wsgi_dev.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for spritesanddice project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "spritesanddice.settings.dev") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/users/__init__.py -------------------------------------------------------------------------------- /users/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.utils.translation import ugettext_lazy as _ 3 | 4 | from users.models import User 5 | 6 | from wagtail.admin.edit_handlers import RichTextFieldPanel 7 | from wagtail.admin.forms import WagtailAdminModelForm 8 | from wagtail.admin.rich_text import get_rich_text_editor_widget 9 | from wagtail.core.fields import RichTextField 10 | from wagtail.users.forms import UserEditForm, UserCreationForm, AvatarPreferencesForm 11 | from wagtail.users.models import UserProfile 12 | 13 | from pprint import pprint 14 | 15 | # For users to edit their own Bio in Account Settings 16 | class AuthorBioForm(WagtailAdminModelForm): 17 | title = forms.CharField() 18 | bio = RichTextField() 19 | 20 | show_email = forms.BooleanField(required=False, label="Show email publicly?") 21 | twitter = forms.CharField(required=False, label="Twitter Username") 22 | website = forms.URLField(required=False, label="Personal Website") 23 | 24 | class Meta: 25 | model = User 26 | fields = ( 27 | "title", 28 | "bio", 29 | 30 | "show_email", 31 | "twitter", 32 | "website", 33 | ) 34 | 35 | 36 | # For admins to edit users' bios in the User Edit form 37 | class CustomUserEditForm(UserEditForm): 38 | title = forms.CharField() 39 | bio = RichTextField() 40 | 41 | show_email = forms.BooleanField(required=False, label="Show email publicly?") 42 | twitter = forms.CharField(required=False, label="Twitter Username") 43 | website = forms.URLField(required=False, label="Personal Website") 44 | 45 | avatar = forms.ImageField(required=False, label=_("Upload a profile picture")) 46 | 47 | # Use a custom save hook to save the submitted avatar to the User's UserProfile 48 | # since it's not accessible via the UserEditForm 49 | def save(self): 50 | user = self.instance 51 | avatar = self.cleaned_data.get('avatar') 52 | 53 | if avatar: 54 | try: # Create New Profile 55 | profile = UserProfile.objects.get(user=user) 56 | profile.avatar = avatar 57 | profile.save() 58 | except: # Save new Profile 59 | profile = UserProfile(user=user, avatar=avatar) 60 | profile.save() 61 | 62 | user.save() 63 | super(CustomUserEditForm, self).save(commit=True) 64 | return user 65 | -------------------------------------------------------------------------------- /users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-04-17 00:08 2 | 3 | import django.contrib.auth.models 4 | import django.contrib.auth.validators 5 | from django.db import migrations, models 6 | import django.utils.timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ('auth', '0011_update_proxy_permissions'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='User', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('password', models.CharField(max_length=128, verbose_name='password')), 23 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 24 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 25 | ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), 26 | ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), 27 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 28 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), 29 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 30 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 31 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 32 | ('title', models.CharField(blank=True, max_length=250)), 33 | ('bio', models.TextField(blank=True, max_length=600)), 34 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), 35 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), 36 | ], 37 | options={ 38 | 'verbose_name': 'user', 39 | 'verbose_name_plural': 'users', 40 | 'abstract': False, 41 | }, 42 | managers=[ 43 | ('objects', django.contrib.auth.models.UserManager()), 44 | ], 45 | ), 46 | ] 47 | -------------------------------------------------------------------------------- /users/migrations/0002_auto_20200422_0253.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-04-22 02:53 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='user', 15 | name='facebook', 16 | field=models.URLField(blank=True, max_length=250), 17 | ), 18 | migrations.AddField( 19 | model_name='user', 20 | name='patreon', 21 | field=models.URLField(blank=True, max_length=250), 22 | ), 23 | migrations.AddField( 24 | model_name='user', 25 | name='show_email', 26 | field=models.BooleanField(default=False, verbose_name='Show email publicly?'), 27 | ), 28 | migrations.AddField( 29 | model_name='user', 30 | name='steam', 31 | field=models.URLField(blank=True, max_length=250), 32 | ), 33 | migrations.AddField( 34 | model_name='user', 35 | name='twitter', 36 | field=models.CharField(blank=True, max_length=250), 37 | ), 38 | migrations.AddField( 39 | model_name='user', 40 | name='website', 41 | field=models.URLField(blank=True, max_length=250), 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /users/migrations/0003_auto_20200422_0311.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-04-22 03:11 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0002_auto_20200422_0253'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='user', 15 | name='show_email', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /users/migrations/0004_auto_20200422_0311.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.3 on 2020-04-22 03:11 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('users', '0003_auto_20200422_0311'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='user', 15 | name='facebook', 16 | ), 17 | migrations.RemoveField( 18 | model_name='user', 19 | name='patreon', 20 | ), 21 | migrations.RemoveField( 22 | model_name='user', 23 | name='steam', 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sprites-and-Dice/sprites-and-dice/d314d24a6ae7c8bc891ebeab7c63dd27651d771f/users/migrations/__init__.py -------------------------------------------------------------------------------- /users/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db import models 3 | 4 | from wagtail.core.fields import RichTextField 5 | 6 | class User(AbstractUser): 7 | title = models.CharField(max_length=250, blank=True) 8 | bio = models.TextField(max_length=600, blank=True) 9 | # bio = RichTextField(blank=True) # Does not currently work - check in on https://github.com/wagtail/wagtail/issues/5961 for updates 10 | 11 | # Social Media Info 12 | show_email = models.BooleanField(default=False) 13 | twitter = models.CharField(max_length=250, blank=True) 14 | website = models.URLField(max_length=250, blank=True) 15 | 16 | # Make it easier to read usernames in chooser forms 17 | def __str__(self): 18 | return self.get_full_name() 19 | 20 | # IE "gif", "png" 21 | def avatar_file_type(self): 22 | if self.wagtail_userprofile.avatar: 23 | return self.wagtail_userprofile.avatar.file.name[-3:] 24 | return None 25 | -------------------------------------------------------------------------------- /users/templates/users/author.html: -------------------------------------------------------------------------------- 1 | {% if user %} 2 | {{user.get_full_name}} 3 | {% endif %} 4 | -------------------------------------------------------------------------------- /users/templates/users/author_bio.html: -------------------------------------------------------------------------------- 1 | {% load menu_tags %} 2 | 3 | {% if user %} 4 |
    5 | 6 |
    7 | 8 | {{user.get_full_name}}'s Profile Picture 12 | 13 | {% include "users/author_social_links.html" %} 14 |
    15 | 16 |
    17 |

    {{user.get_full_name}}

    18 | {% if user.title %} 19 |
    {{user.title}}
    20 | {% endif %} 21 | 22 |
    23 | 24 | 27 | 28 |
    29 | {% endif %} 30 | -------------------------------------------------------------------------------- /users/templates/users/author_social_links.html: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /users/templates/users/user_grid.html: -------------------------------------------------------------------------------- 1 |
    2 | {% for user in users %} 3 | 13 | {% endfor %} 14 |
    15 | -------------------------------------------------------------------------------- /users/templates/users/user_index.html: -------------------------------------------------------------------------------- 1 | {% extends "page/base_page.html" %} 2 | {% load static wagtailimages_tags customuser_tags menu_tags %} 3 | 4 | {% block title %}Users{% endblock %} 5 | {% block og_title %}Users{% endblock %} 6 | 7 | {% block body_class %}user-index{% endblock %} 8 | 9 | {% block page_feed %} 10 | {% include "users/user_grid.html" %} 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /users/templates/wagtailadmin/account/change_bio.html: -------------------------------------------------------------------------------- 1 | {% extends "wagtailadmin/base.html" %} 2 | {% load i18n %} 3 | 4 | {% block titletag %}{% trans "Change bio" %}{% endblock %} 5 | {% block content %} 6 | {% trans "Change bio" as change_str %} 7 | {% include "wagtailadmin/shared/header.html" with title=change_str %} 8 | 9 |
    10 |
    11 | {% csrf_token %} 12 |
      13 | {% for field in form %} 14 | {% include "wagtailadmin/shared/field_as_li.html" with field=field %} 15 | {% endfor %} 16 |
    17 | 18 |
    19 |
    20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /users/templates/wagtailusers/users/edit.html: -------------------------------------------------------------------------------- 1 | {% extends "wagtailusers/users/edit.html" %} 2 | 3 | {% load wagtailadmin_tags i18n menu_tags %} 4 | 5 | {% block extra_fields %} 6 | {% include "wagtailadmin/shared/field_as_li.html" with field=form.avatar %} 7 | {% include "wagtailadmin/shared/field_as_li.html" with field=form.title %} 8 | {% include "wagtailadmin/shared/field_as_li.html" with field=form.bio %} 9 | {% include "wagtailadmin/shared/field_as_li.html" with field=form.show_email %} 10 | {% include "wagtailadmin/shared/field_as_li.html" with field=form.twitter %} 11 | {% include "wagtailadmin/shared/field_as_li.html" with field=form.website %} 12 | {% endblock extra_fields %} 13 | -------------------------------------------------------------------------------- /users/templatetags/customuser_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | @register.inclusion_tag('users/author.html') 6 | def author(user=None): 7 | return { 8 | 'user': user 9 | } 10 | 11 | @register.inclusion_tag('users/author_bio.html') 12 | def author_bio(user=None): 13 | return { 14 | 'user': user 15 | } 16 | -------------------------------------------------------------------------------- /users/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from . import views 4 | 5 | app_name = 'users' 6 | urlpatterns = [ 7 | url(r'^(?P[\w-]+)/?$', views.user_page, name='page'), 8 | url(r'', views.user_index, name='all_users'), 9 | ] 10 | -------------------------------------------------------------------------------- /users/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import redirect, render 2 | from django.utils.translation import ugettext_lazy as _ 3 | from django.shortcuts import get_object_or_404 4 | from django.http import Http404, HttpResponse, HttpResponseRedirect 5 | from django.db.models import Q 6 | 7 | from wagtail.admin import messages 8 | 9 | from users.forms import AuthorBioForm 10 | 11 | from users.models import User 12 | 13 | from page.models import BlogPage 14 | 15 | def change_bio(request): 16 | if request.method == 'POST': 17 | form = AuthorBioForm(request.POST, instance=request.user) 18 | 19 | if form.is_valid(): 20 | form.save() 21 | messages.success(request, _("Your bio has been changed successfully!")) 22 | return redirect('wagtailadmin_account') 23 | else: 24 | form = AuthorBioForm(instance=request.user) 25 | 26 | return render(request, 'wagtailadmin/account/change_bio.html', { 27 | 'form': form, 28 | }) 29 | 30 | def user_index(request): 31 | users = User.objects.all() 32 | return render(request, 'users/user_index.html', { 'users': users }) 33 | 34 | def user_page(request, username=None): 35 | user = User.objects.filter(username=username).first() 36 | if user: 37 | return render(request, 'page/user_page.html', { 'user': user }) 38 | 39 | # No username found, check if the URL matches a user's full name 40 | else: 41 | # Drupal-era user URLs were `/users/full-name-slug/` 42 | # attempt to un-slugify the username and search by full name 43 | first_name = username.split('-')[0] 44 | last_name = ' '.join(username.split('-')[1:]) 45 | 46 | # Some old last names have hyphens, so search for last names with spaces OR hyphens 47 | users = User.objects 48 | users = users.filter(first_name__iexact=first_name) 49 | users = users.filter(Q(last_name__iexact=last_name.replace(' ','-')) | Q(last_name__iexact=last_name)) 50 | 51 | user = users.first() 52 | 53 | # 301 Redirect to the new /users/ URL 54 | if user: 55 | username = user.username 56 | return HttpResponseRedirect('/users/{}/'.format(username), status=301) 57 | # 302 Redirect to /users/ index 58 | else: 59 | return HttpResponseRedirect('/users/', status=302) 60 | -------------------------------------------------------------------------------- /users/wagtail_hooks.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | from django.utils.translation import ugettext_lazy as _ 3 | from django.urls import reverse 4 | 5 | from users.views import change_bio 6 | 7 | from wagtail.core import hooks 8 | 9 | @hooks.register('register_admin_urls') 10 | def register_admin_urls(): 11 | return [ 12 | url(r'^account/bio/$', change_bio, name='wagtailadmin_custom_account_change_bio'), 13 | ] 14 | 15 | @hooks.register('register_account_menu_item') 16 | def register_account_change_bio(request): 17 | return { 18 | 'url': reverse('wagtailadmin_custom_account_change_bio'), 19 | 'label': _('Change bio'), 20 | 'help_text': _('Update your author bio.'), 21 | } 22 | --------------------------------------------------------------------------------