diff --git a/.gitignore b/.gitignore index d04f489..9e79bb7 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ static/ assets/node_modules/ assets/.sass-cache/ *style.css.map +.vscode/ \ No newline at end of file diff --git a/account/models.py b/account/models.py index 92a0f48..3a5e8ea 100644 --- a/account/models.py +++ b/account/models.py @@ -5,6 +5,9 @@ from django.utils.translation import ugettext_lazy as _ from django.contrib.auth import password_validation +from wagtail.snippets.models import register_snippet + + def upload_avatar_to(instance, filename): filename, ext = os.path.splitext(filename) @@ -41,6 +44,7 @@ def create_superuser(self, email, password, **extra_fields): return self.create_user(email, password, **extra_fields) +@register_snippet class User(AbstractBaseUser, PermissionsMixin): """ Custom user model """ diff --git a/assets/js/main.js b/assets/js/main.js index baa296b..51e1cef 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -81,7 +81,7 @@ $(document).ready(function() { }, 0); - $.get('/recent/', function( response ) { + $.get('/blog/recent/', function( response ) { var template = $("#recent_blog_tpl").html(); $("#recent_blog").html(_.template(template)({posts:response})); }); diff --git a/blog/migrations/0002_auto_20200206_0701.py b/blog/migrations/0002_auto_20200206_0701.py new file mode 100644 index 0000000..3c3ecf7 --- /dev/null +++ b/blog/migrations/0002_auto_20200206_0701.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.2 on 2020-02-06 07:01 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='blogpage', + name='main_image', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='public.SwiftkindImage'), + ), + ] diff --git a/blog/models.py b/blog/models.py index b536349..3d1083c 100644 --- a/blog/models.py +++ b/blog/models.py @@ -16,6 +16,7 @@ from modelcluster.fields import ParentalKey from modelcluster.contrib.taggit import ClusterTaggableManager from taggit.models import TaggedItemBase, Tag +from public.image import SwiftkindImage class BlogPageTag(TaggedItemBase): @@ -29,7 +30,7 @@ class BlogPage(Page): """ Blog page models """ main_image = models.ForeignKey( - 'wagtailimages.Image', + 'public.SwiftkindImage', null=True, blank=True, on_delete=models.SET_NULL, @@ -45,7 +46,7 @@ class BlogPage(Page): content_panels = Page.content_panels + [ ImageChooserPanel('main_image'), - FieldPanel('body'), + FieldPanel('body', classname='full'), FieldPanel('tags') ] diff --git a/blog/urls.py b/blog/urls.py index ffbea12..2289b9c 100644 --- a/blog/urls.py +++ b/blog/urls.py @@ -6,7 +6,5 @@ from .views import RecentBlogView urlpatterns = [ - re_path(r'cms/', include(wagtailadmin_urls)), - re_path(r'blog/', include(wagtail_urls)), - path('recent/', RecentBlogView.as_view(), name='recent_posts') + path('recent/', RecentBlogView.as_view(), name='recent_posts'), ] \ No newline at end of file diff --git a/public/blocks.py b/public/blocks.py new file mode 100644 index 0000000..2e66fa3 --- /dev/null +++ b/public/blocks.py @@ -0,0 +1,430 @@ +import os + +from django import forms +from django.conf import settings +from django.forms.utils import ErrorList +from django.core.exceptions import ValidationError + +from wagtail.core.models import Page +from wagtail.core.fields import RichTextField +from wagtail.images.blocks import ImageChooserBlock +from wagtail.embeds.blocks import EmbedBlock +from wagtail.documents.blocks import DocumentChooserBlock +from wagtail.snippets.blocks import SnippetChooserBlock +from wagtail.core.blocks import ( + CharBlock, + RichTextBlock, + StreamBlock, + StructBlock, + TextBlock, + PageChooserBlock, + URLBlock, + RawHTMLBlock, + ChooserBlock, + EmailBlock +) +from account.models import User + + +# Home Page +class HeroBlock(StructBlock): + hero_title = RawHTMLBlock(required=False, help_text="Raw HTML format") + hero_body = TextBlock(required=False) + button_label = CharBlock(required=False) + button_link = StreamBlock([ + ('page', PageChooserBlock(label="Page", required=False, help_text="Choose only one link", icon='doc-full')), + ('external_url', URLBlock(label="External URL", required=False, help_text="Choose only one link")), + ], required=False, max_num=1) + + class Meta: + icon = "title" + template = "public/blocks/homepage_block/homepage_hero.html" + + +class DevelopmentContentBlock(StructBlock): + content_title = CharBlock(required=False) + content_services_title = RichTextBlock(required=False) + content_body = TextBlock(required=False) + button_label = CharBlock(required=False) + button_link = StreamBlock([ + ('page', PageChooserBlock(label="page", required=False, help_text="Choose only one link", icon="doc-full")), + ('external_url', URLBlock(label="external URL", required=False, help_text="Choose only one link", icon="site")), + ], required=False, max_num=1) + image = StreamBlock([ + ('svg_image', DocumentChooserBlock(label="SVG Image", required=False, help_text="SVG format only", icon="image")), + ('other_image', ImageChooserBlock(label="Other Image", required=False, help_text="JPEG/PNG format", icon="image")), + ], required=False, max_num=1) + + class Meta: + icon = "form" + + +class DevelopmentItemsBlock(StructBlock): + item_title = CharBlock(required=False) + item_body = TextBlock(required=False) + + class Meta: + icon = "list-ul" + + +class DevelopmentBlock(StreamBlock): + content = DevelopmentContentBlock() + items = DevelopmentItemsBlock() + + class Meta: + icon = "form" + template = "public/blocks/homepage_block/homepage_development.html" + + +class DesignBlock(StructBlock): + design_title = CharBlock(required=False) + design_service_title = RichTextBlock(required=False) + design_body = TextBlock(required=False) + image = StreamBlock([ + ('svg_image', DocumentChooserBlock(label="SVG Image", required=False, help_text="SVG format only", icon="image")), + ('other_image', ImageChooserBlock(label="Other Image", required=False, help_text="JPEG/PNG format", icon="image")), + ], required=False) + design_footer = TextBlock(required=False) + + class Meta: + icon = "form" + template = "public/blocks/homepage_block/homepage_design.html" + + +class TeamContentBlock(StructBlock): + team_title = CharBlock(required=False) + team_body = TextBlock(required=False) + + class Meta: + icon = "form" + + +class TeamChooserBlock(ChooserBlock): + target_model = User + widget = forms.Select + + class Meta: + icon = "icon" + + # Return the key value for the select field + def value_for_form(self, value): + if isinstance(value, self.target_model): + return value.pk + else: + return value + + +class TeamMembersBlock(StructBlock): + members = TeamChooserBlock() + avatar = StreamBlock([ + ('svg_image', DocumentChooserBlock(label="SVG Image", required=False, help_text="SVG format only", icon="image")), + ('other_image', ImageChooserBlock(label="Other Image", required=False, help_text="JPEG/PNG format", icon="image")), + ], required=False, max_num=1) + position = CharBlock(required=False,) + + class Meta: + icon = "user" + + +class TeamBlock(StreamBlock): + content = TeamContentBlock() + members = TeamMembersBlock() + + class Meta: + icon = "group" + template = "public/blocks/homepage_block/homepage_team.html" + + +class ClientBlock(StructBlock): + client_body = TextBlock(required=False) + + class Meta: + icon = "form" + template = "public/blocks/homepage_block/homepage_client.html" + + +class HiringBlock(StructBlock): + hiring_title = CharBlock(required=False) + hiring_body = TextBlock(required=False) + button_label = CharBlock(required=False) + button_link = StreamBlock([ + ('page', PageChooserBlock(label="page", required=False, help_text="Choose only one link", icon="doc-full")), + ('external_url', URLBlock(label="external URL", required=False, help_text="Choose only one link", icon="site")), + ], required=False, max_num=1) + + class Meta: + icon = "doc-full" + template = "public/blocks/homepage_block/homepage_hiring.html" + + +class TestimonyMembersBlock(StructBlock): + testimony = TextBlock(required=False) + member = CharBlock(required=False) + position = CharBlock(required=False) + company_label = CharBlock(required=False) + company_link = StreamBlock([ + ('page', PageChooserBlock(label="page", required=False, help_text="Choose only one link", icon="doc-full")), + ('external_url', URLBlock(label="external URL", required=False, help_text="Choose only one link", icon="site")), + ], required=False, max_num=1) + + class Meta: + icon = "user" + + +class TestimonyTitleBlock(StructBlock): + title = CharBlock(required=False) + + class Meta: + icon = "title" + + +class TestimonyBlock(StreamBlock): + testimony_title = TestimonyTitleBlock() + members = TestimonyMembersBlock() + + class Meta: + icon = "group" + template = "public/blocks/homepage_block/homepage_testimony.html" + + +class ContactBlock(StructBlock): + iframe_link = StreamBlock([ + ('page', PageChooserBlock(label="page", required=False, help_text="Choose only one link", icon="doc-full")), + ('external_url', URLBlock(label="external URL", required=False, help_text="Choose only one link", icon="site")), + ], required=False, max_num=1) + contact_title = CharBlock(required=False) + contact_body = TextBlock(required=False) + email = EmailBlock(required=False) + + class Meta: + icon = "form" + template = "public/blocks/homepage_block/homepage_contact.html" + + +class HomePageBlock(StreamBlock): + hero_block = HeroBlock() + development_block = DevelopmentBlock() + design_block = DesignBlock() + team_block = TeamBlock() + client_block = ClientBlock() + hiring_block = HiringBlock() + testimony_block = TestimonyBlock() + contact_block = ContactBlock() + + +# Project Page +class ProjectBlock(StructBlock): + image = StreamBlock([ + ('svg_image', DocumentChooserBlock(label="SVG Image", required=False, help_text="SVG format only", icon="image")), + ('other_image', ImageChooserBlock(label="Other Image", required=False, help_text="JPEG/PNG format", icon="image")), + ], required=False, max_num=1) + image_link = StreamBlock([ + ('page', PageChooserBlock(label="page", required=False, help_text="Choose only one link", icon="doc-full")), + ('external_url', URLBlock(label="external URL", required=False, help_text="Choose only one link", icon="site")), + ], required=False, max_num=1) + project_title = CharBlock(required=False) + project_content_1 = TextBlock(required=False) + project_content_2 = TextBlock(required=False) + + class Meta: + icon = "doc-full" + + +class ProjectPageBlock(StreamBlock): + project = ProjectBlock() + + +# Process Page +class ProcessHeroBlock(StructBlock): + process_title_1 = CharBlock(required=False) + process_content_1 = TextBlock(required=False) + process_title_2 = CharBlock(required=False) + process_content_2 = TextBlock(required=False) + image = StreamBlock([ + ('svg_image', DocumentChooserBlock(label="SVG Image", required=False, help_text="SVG format only", icon="image")), + ('other_image', ImageChooserBlock(label="Other Image", required=False, help_text="JPEG/PNG format", icon="image")), + ], required=False, max_num=1) + + class Meta: + icon = "title" + template = "public/blocks/process_block/process_hero.html" + + +class StageBlock(StreamBlock): + stage = RawHTMLBlock(required=False) + image = StreamBlock([ + ('svg_image', DocumentChooserBlock(label="SVG Image", required=False, help_text="SVG format only", icon="image")), + ('other_image', ImageChooserBlock(label="Other Image", required=False, help_text="JPEG/PNG format", icon="image")), + ], required=False, max_num=1, icon="image") + + class Meta: + icon = "list-ul" + template = "public/blocks/process_block/process_stage.html" + + +class StandpointBlock(StructBlock): + title = CharBlock(required=False) + statement = TextBlock(required=False) + member = CharBlock(required=False) + position = CharBlock(required=False) + + class Meta: + icon = "user" + template = "public/blocks/process_block/process_standpoint.html" + + +class LogoTitleBlock(StructBlock): + title = CharBlock(required=False) + + class Meta: + icon= "title" + + +class LogoDetailsBlock(StructBlock): + details_title = CharBlock(required=False) + details_content = TextBlock(required=False) + image = StreamBlock([ + ('svg_image', DocumentChooserBlock(label="SVG Image", required=False, help_text="SVG format only", icon="image")), + ('other_image', ImageChooserBlock(label="Other Image", required=False, help_text="JPEG/PNG format", icon="image")), + ], required=False, max_num=1) + + class Meta: + icon = "form" + + +class LogoBlock(StreamBlock): + title = LogoTitleBlock() + details = LogoDetailsBlock() + + class Meta: + icon = "list-ul" + template = "public/blocks/process_block/process_logo.html" + + +class ProcessPageBlock(StreamBlock): + hero_block = ProcessHeroBlock() + stage_block = StageBlock() + standpoint_block = StandpointBlock() + logo_block = LogoBlock() + + +# Web App Page +# Workflow and Marketplace +class WebAppHeaderBlock(StructBlock): + main_image = StreamBlock([ + ('svg_image', DocumentChooserBlock(label="SVG Image", required=False, help_text="SVG format only", icon="image")), + ('other_image', ImageChooserBlock(label="Other Image", required=False, help_text="JPEG/PNG format", icon="image")), + ], required=False, max_num=1) + label = CharBlock(required=False) + + class Meta: + icon = "image" + template = "public/blocks/webapp_block/webapp_header.html" + + +class WebAppBriefBlock(StructBlock): + tagline_title = CharBlock(required=False) + tagline_content_1 = TextBlock(required=False) + tagline_content_2 = TextBlock(required=False) + brief_title = CharBlock(required=False) + brief_content = TextBlock(required=False) + + class Meta: + icon = "doc-full" + template = "public/blocks/webapp_block/webapp_brief.html" + + +class WebAppImageBlock(StructBlock): + image = StreamBlock([ + ('svg_image', DocumentChooserBlock(label="SVG Image", required=False, help_text="SVG format only", icon="image")), + ('other_image', ImageChooserBlock(label="Other Image", required=False, help_text="JPEG/PNG format", icon="image")), + ], required=False) + + class Meta: + icon = "image" + template = "public/blocks/webapp_block/webapp_image.html" + + +class WebAppResearchTitleBlock(StructBlock): + phase = CharBlock(required=False) + research_title = CharBlock(required=False) + research_body = TextBlock(required=False) + + class Meta: + icon = "title" + + +class WebAppResearchTestsBlock(StructBlock): + research_test = CharBlock(required=False) + research_result = CharBlock(required=False) + + class Meta: + icon = "doc-full" + + +class WebAppResearchInterviewBlock(StreamBlock): + research_title = WebAppResearchTitleBlock() + research = WebAppResearchTestsBlock() + questions = CharBlock(required=False, icon="help") + + class Meta: + icon = "form" + template = "public/blocks/webapp_block/webapp_research_interview.html" + + +class WebAppResearchMembersBlock(StructBlock): + avatar = StreamBlock([ + ('svg_image', DocumentChooserBlock(label="SVG Image", required=False, help_text="SVG format only", icon="image")), + ('other_image', ImageChooserBlock(label="Other Image", required=False, help_text="JPEG/PNG format", icon="image")), + ], required=False, max_num=1) + member = CharBlock(required=False) + testimony = TextBlock(required=False) + + class Meta: + icon = "user" + + +class WebAppResearchDiscussionBlock(StreamBlock): + research_title = WebAppResearchTitleBlock() + member = WebAppResearchMembersBlock() + + class Meta: + icon = "group" + template = "public/blocks/webapp_block/webapp_research_discussion.html" + + +class WebAppResearchFlowBlock(StreamBlock): + research_title = WebAppResearchTitleBlock() + image = WebAppHeaderBlock() + + class Meta: + icon = "image" + template = "public/blocks/webapp_block/webapp_research_flow.html" + + +class NextProjectBlock(StructBlock): + title = CharBlock(required=False) + project_name = CharBlock(required=False) + project_link = StreamBlock([ + ('page', PageChooserBlock(label="page", required=False, help_text="Choose only one link", icon="doc-full")), + ('external_url', URLBlock(label="external URL", required=False, help_text="Choose only one link", icon="site")), + ], required=False, max_num=1) + project_label = CharBlock(required=False) + image = StreamBlock([ + ('svg_image', DocumentChooserBlock(label="SVG Image", required=False, help_text="SVG format only", icon="image")), + ('other_image', ImageChooserBlock(label="Other Image", required=False, help_text="JPEG/PNG format", icon="image")), + ], required=False, max_num=1) + + class Meta: + icon = "arrow-right" + template = "public/blocks/webapp_block/webapp_next.html" + + +class WebAppPageBlock(StreamBlock): + header_block = WebAppHeaderBlock() + brief_block = WebAppBriefBlock() + image_block = WebAppImageBlock() + interview_block = WebAppResearchInterviewBlock() + discussion_block = WebAppResearchDiscussionBlock() + flow_block = WebAppResearchFlowBlock() + next_block = NextProjectBlock() \ No newline at end of file diff --git a/public/image.py b/public/image.py new file mode 100644 index 0000000..4037e01 --- /dev/null +++ b/public/image.py @@ -0,0 +1,125 @@ +import os.path +from io import BytesIO +from PIL import Image as PILImage + +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.core.files.storage import default_storage as storage + +from wagtail.images.models import Image, AbstractImage, AbstractRendition +from unidecode import unidecode + + +def get_upload_to(self, filename): + folder_name = 'original_images' + filename = self.file.field.storage.get_valid_name(filename) + + # do a unidecode in the filename and then + # replace non-ascii characters in filename with _ , to sidestep issues with filesystem encoding + filename = "".join((i if ord(i) < 128 else '_') for i in unidecode(filename)) + + # # Truncate filename so it fits in the 100 character limit + # # https://code.djangoproject.com/ticket/9893 + full_path = os.path.join(folder_name, filename) + if len(full_path) >= 95: + chars_to_trim = len(full_path) - 94 + prefix, extension = os.path.splitext(filename) + filename = prefix[:-chars_to_trim] + extension + full_path = os.path.join(folder_name, filename) + + return full_path + + +class SwiftkindImage(AbstractImage): + """ + Image Model - Custom + """ + + file = models.ImageField( + verbose_name=_('file'), upload_to=get_upload_to, width_field='width', height_field='height' + ) + + admin_form_fields = Image.admin_form_fields + () + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._disable_signals = False + + def save_without_signals(self): + """ + This allows for updating the model from code running inside post_save() + signals without going into an infinite loop: + """ + self._disable_signals = True + self.save() + self._disable_signals = False + + def save(self, force_insert=False, force_update=False, using=None, + update_fields=None): + # Opening the uploaded image + old_obj = None + if self.pk: + old_obj = SwiftkindImage.objects.get(pk=self.pk) + + hires_output = [] + + for x in range(3): + hires_output.append(BytesIO()) + + hires_img = PILImage.open(self.file) + + width = hires_img.size[0] + height = hires_img.size[1] + + for x in range(3): + BASE_WIDTH = hires_img.size[0]*((3-x)/3) + BASE_HEIGHT = hires_img.size[1]*((3-x)/3) + + width = int(BASE_WIDTH) + height = int(BASE_HEIGHT) + rendition = hires_img.resize((width, height), PILImage.ANTIALIAS) + rendition.save(hires_output[x], format=hires_img.format, quality=90) + + super(SwiftkindImage, self).save(force_insert, force_update, using, update_fields) + + # Create a 1x and 2x Rendition + same_name = old_obj and getattr(old_obj.file, 'name') == self.file.name + + if not old_obj or not same_name: + rendition_original = self.get_rendition('original') + rendition_url = rendition_original.url + + file_name = os.path.join('images', rendition_url.split('/')[-1]) + + for x in range(3): + rend_name = '@'+str(3-x)+'x' + if x==2: + rend_name = '' + + if hires_img.format == 'JPEG': + hires_img.format = 'JPG' + + rendition_name = '{}.original{}.{}'.format(file_name.split('.')[0], + rend_name,hires_img.format.lower()) + + image_path = storage.open(rendition_name, 'wb') + image_path.write(hires_output[x].getvalue()) + hires_output[x].seek(0) + image_path.close() + + +class SwiftkindRendition(AbstractRendition): + """ + Rendition Model - Custom + """ + + image = models.ForeignKey(SwiftkindImage, on_delete=models.CASCADE, related_name='renditions') + + class Meta: + """ + Meta props + """ + + unique_together = ( + ('image', 'filter_spec', 'focal_point_key'), + ) \ No newline at end of file diff --git a/public/migrations/0001_initial.py b/public/migrations/0001_initial.py new file mode 100644 index 0000000..613b9e2 --- /dev/null +++ b/public/migrations/0001_initial.py @@ -0,0 +1,42 @@ +# Generated by Django 2.2.2 on 2019-07-16 09:50 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import modelcluster.fields +import wagtail.core.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('wagtailcore', '0041_group_collection_permissions_verbose_name_plural'), + ] + + operations = [ + migrations.CreateModel( + name='HomePage', + fields=[ + ('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')), + ('body', wagtail.core.fields.RichTextField(blank=True)), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + migrations.CreateModel( + name='TeamMembersPage', + fields=[ + ('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')), + ('members', modelcluster.fields.ParentalManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + ] diff --git a/public/migrations/0002_auto_20190718_0827.py b/public/migrations/0002_auto_20190718_0827.py new file mode 100644 index 0000000..8c89573 --- /dev/null +++ b/public/migrations/0002_auto_20190718_0827.py @@ -0,0 +1,54 @@ +# Generated by Django 2.2.2 on 2019-07-18 08:27 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0041_group_collection_permissions_verbose_name_plural'), + ('public', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='homepage', + name='body', + ), + migrations.AddField( + model_name='homepage', + name='featured_section_1', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailcore.Page', verbose_name='Featured section 1'), + ), + migrations.AddField( + model_name='homepage', + name='featured_section_1_title', + field=models.CharField(blank=True, max_length=256, null=True), + ), + migrations.AddField( + model_name='homepage', + name='featured_section_2', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailcore.Page', verbose_name='Featured section 2'), + ), + migrations.AddField( + model_name='homepage', + name='featured_section_2_title', + field=models.CharField(blank=True, max_length=256, null=True), + ), + migrations.AddField( + model_name='homepage', + name='hero_cta_link', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailcore.Page', verbose_name='Hero CTA link'), + ), + migrations.AddField( + model_name='homepage', + name='hero_text', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='homepage', + name='hero_title', + field=models.CharField(blank=True, max_length=525, null=True), + ), + ] diff --git a/public/migrations/0003_auto_20190718_0849.py b/public/migrations/0003_auto_20190718_0849.py new file mode 100644 index 0000000..b2b3704 --- /dev/null +++ b/public/migrations/0003_auto_20190718_0849.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.2 on 2019-07-18 08:49 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailimages', '0001_squashed_0021'), + ('public', '0002_auto_20190718_0827'), + ] + + operations = [ + migrations.AddField( + model_name='homepage', + name='feature_section_1_cover', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.Image'), + ), + migrations.AddField( + model_name='homepage', + name='feature_section_2_cover', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.Image'), + ), + ] diff --git a/public/migrations/0004_teammemberspage_description.py b/public/migrations/0004_teammemberspage_description.py new file mode 100644 index 0000000..cc9c57a --- /dev/null +++ b/public/migrations/0004_teammemberspage_description.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.2 on 2019-07-23 07:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('public', '0003_auto_20190718_0849'), + ] + + operations = [ + migrations.AddField( + model_name='teammemberspage', + name='description', + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/public/migrations/0005_auto_20200206_0552.py b/public/migrations/0005_auto_20200206_0552.py new file mode 100644 index 0000000..9283df3 --- /dev/null +++ b/public/migrations/0005_auto_20200206_0552.py @@ -0,0 +1,99 @@ +# Generated by Django 2.2.2 on 2020-02-06 05:52 + +from django.db import migrations, models +import django.db.models.deletion +import public.blocks +import wagtail.core.blocks +import wagtail.core.fields +import wagtail.documents.blocks +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailforms', '0003_capitalizeverbose'), + ('wagtailcore', '0041_group_collection_permissions_verbose_name_plural'), + ('wagtailredirects', '0006_redirect_increase_max_length'), + ('public', '0004_teammemberspage_description'), + ] + + operations = [ + migrations.CreateModel( + name='ProcessPage', + fields=[ + ('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')), + ('body', wagtail.core.fields.StreamField([('hero_block', wagtail.core.blocks.StructBlock([('process_title_1', wagtail.core.blocks.CharBlock(required=False)), ('process_content_1', wagtail.core.blocks.TextBlock(required=False)), ('process_title_2', wagtail.core.blocks.CharBlock(required=False)), ('process_content_2', wagtail.core.blocks.TextBlock(required=False)), ('image', wagtail.core.blocks.StreamBlock([('svg_image', wagtail.documents.blocks.DocumentChooserBlock(help_text='SVG format only', icon='image', label='SVG Image', required=False)), ('other_image', wagtail.images.blocks.ImageChooserBlock(help_text='JPEG/PNG format', icon='image', label='Other Image', required=False))], max_num=1, required=False))])), ('stage_block', wagtail.core.blocks.StreamBlock([('stage', wagtail.core.blocks.RawHTMLBlock(required=False))])), ('standpoint_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(required=False)), ('statement', wagtail.core.blocks.TextBlock(required=False)), ('member', wagtail.core.blocks.CharBlock(required=False)), ('position', wagtail.core.blocks.CharBlock(required=False))])), ('logo_block', wagtail.core.blocks.StreamBlock([('title', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(required=False))])), ('details', wagtail.core.blocks.StructBlock([('details_title', wagtail.core.blocks.CharBlock(required=False)), ('details_content', wagtail.core.blocks.TextBlock(required=False)), ('image', wagtail.core.blocks.StreamBlock([('svg_image', wagtail.documents.blocks.DocumentChooserBlock(help_text='SVG format only', icon='image', label='SVG Image', required=False)), ('other_image', wagtail.images.blocks.ImageChooserBlock(help_text='JPEG/PNG format', icon='image', label='Other Image', required=False))], max_num=1, required=False))]))]))], blank=True, verbose_name='Page body')), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + migrations.CreateModel( + name='ProjectPage', + fields=[ + ('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')), + ('project', wagtail.core.fields.StreamField([('project', wagtail.core.blocks.StructBlock([('image', wagtail.core.blocks.StreamBlock([('svg_image', wagtail.documents.blocks.DocumentChooserBlock(help_text='SVG format only', icon='image', label='SVG Image', required=False)), ('other_image', wagtail.images.blocks.ImageChooserBlock(help_text='JPEG/PNG format', icon='image', label='Other Image', required=False))], max_num=1, required=False)), ('image_link', wagtail.core.blocks.StreamBlock([('page', wagtail.core.blocks.PageChooserBlock(help_text='Choose only one link', icon='doc-full', label='page', required=False)), ('external_url', wagtail.core.blocks.URLBlock(help_text='Choose only one link', icon='site', label='external URL', required=False))], max_num=1, required=False)), ('project_title', wagtail.core.blocks.CharBlock(required=False)), ('project_content_1', wagtail.core.blocks.TextBlock(required=False)), ('project_content_2', wagtail.core.blocks.TextBlock(required=False))]))], blank=True, verbose_name='Page projects')), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + migrations.CreateModel( + name='WebAppPage', + fields=[ + ('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')), + ('body', wagtail.core.fields.StreamField([('header_block', wagtail.core.blocks.StructBlock([('main_image', wagtail.core.blocks.StreamBlock([('svg_image', wagtail.documents.blocks.DocumentChooserBlock(help_text='SVG format only', icon='image', label='SVG Image', required=False)), ('other_image', wagtail.images.blocks.ImageChooserBlock(help_text='JPEG/PNG format', icon='image', label='Other Image', required=False))], max_num=1, required=False)), ('label', wagtail.core.blocks.CharBlock(required=False))])), ('brief_block', wagtail.core.blocks.StructBlock([('tagline_title', wagtail.core.blocks.CharBlock(required=False)), ('tagline_content_1', wagtail.core.blocks.TextBlock(required=False)), ('tagline_content_2', wagtail.core.blocks.TextBlock(required=False)), ('brief_title', wagtail.core.blocks.CharBlock(required=False)), ('brief_content', wagtail.core.blocks.TextBlock(required=False))])), ('image_block', wagtail.core.blocks.StructBlock([('image', wagtail.core.blocks.StreamBlock([('svg_image', wagtail.documents.blocks.DocumentChooserBlock(help_text='SVG format only', icon='image', label='SVG Image', required=False)), ('other_image', wagtail.images.blocks.ImageChooserBlock(help_text='JPEG/PNG format', icon='image', label='Other Image', required=False))], required=False))])), ('interview_block', wagtail.core.blocks.StreamBlock([('research_title', wagtail.core.blocks.StructBlock([('phase', wagtail.core.blocks.CharBlock(required=False)), ('research_title', wagtail.core.blocks.CharBlock(required=False)), ('research_body', wagtail.core.blocks.TextBlock(required=False))])), ('research', wagtail.core.blocks.StructBlock([('research_test', wagtail.core.blocks.CharBlock(required=False)), ('research_result', wagtail.core.blocks.CharBlock(required=False))])), ('questions', wagtail.core.blocks.CharBlock(icon='help', required=False))])), ('discussion_block', wagtail.core.blocks.StreamBlock([('research_title', wagtail.core.blocks.StructBlock([('phase', wagtail.core.blocks.CharBlock(required=False)), ('research_title', wagtail.core.blocks.CharBlock(required=False)), ('research_body', wagtail.core.blocks.TextBlock(required=False))])), ('member', wagtail.core.blocks.StructBlock([('avatar', wagtail.core.blocks.StreamBlock([('svg_image', wagtail.documents.blocks.DocumentChooserBlock(help_text='SVG format only', icon='image', label='SVG Image', required=False)), ('other_image', wagtail.images.blocks.ImageChooserBlock(help_text='JPEG/PNG format', icon='image', label='Other Image', required=False))], max_num=1, required=False)), ('testimony', wagtail.core.blocks.TextBlock(required=False)), ('member', wagtail.core.blocks.CharBlock(required=False))]))])), ('flow_block', wagtail.core.blocks.StreamBlock([('research_title', wagtail.core.blocks.StructBlock([('phase', wagtail.core.blocks.CharBlock(required=False)), ('research_title', wagtail.core.blocks.CharBlock(required=False)), ('research_body', wagtail.core.blocks.TextBlock(required=False))])), ('image', wagtail.core.blocks.StructBlock([('main_image', wagtail.core.blocks.StreamBlock([('svg_image', wagtail.documents.blocks.DocumentChooserBlock(help_text='SVG format only', icon='image', label='SVG Image', required=False)), ('other_image', wagtail.images.blocks.ImageChooserBlock(help_text='JPEG/PNG format', icon='image', label='Other Image', required=False))], max_num=1, required=False)), ('label', wagtail.core.blocks.CharBlock(required=False))]))])), ('next_block', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(required=False)), ('project_name', wagtail.core.blocks.CharBlock(required=False)), ('project_link', wagtail.core.blocks.StreamBlock([('page', wagtail.core.blocks.PageChooserBlock(help_text='Choose only one link', icon='doc-full', label='page', required=False)), ('external_url', wagtail.core.blocks.URLBlock(help_text='Choose only one link', icon='site', label='external URL', required=False))], max_num=1, required=False)), ('project_label', wagtail.core.blocks.CharBlock(required=False)), ('image', wagtail.core.blocks.StreamBlock([('svg_image', wagtail.documents.blocks.DocumentChooserBlock(help_text='SVG format only', icon='image', label='SVG Image', required=False)), ('other_image', wagtail.images.blocks.ImageChooserBlock(help_text='JPEG/PNG format', icon='image', label='Other Image', required=False))], max_num=1, required=False))]))], blank=True, verbose_name='Page body')), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + migrations.RemoveField( + model_name='homepage', + name='feature_section_1_cover', + ), + migrations.RemoveField( + model_name='homepage', + name='feature_section_2_cover', + ), + migrations.RemoveField( + model_name='homepage', + name='featured_section_1', + ), + migrations.RemoveField( + model_name='homepage', + name='featured_section_1_title', + ), + migrations.RemoveField( + model_name='homepage', + name='featured_section_2', + ), + migrations.RemoveField( + model_name='homepage', + name='featured_section_2_title', + ), + migrations.RemoveField( + model_name='homepage', + name='hero_cta_link', + ), + migrations.RemoveField( + model_name='homepage', + name='hero_text', + ), + migrations.RemoveField( + model_name='homepage', + name='hero_title', + ), + migrations.AddField( + model_name='homepage', + name='body', + field=wagtail.core.fields.StreamField([('hero_block', wagtail.core.blocks.StructBlock([('hero_title', wagtail.core.blocks.RawHTMLBlock(help_text='Raw HTML format', required=False)), ('hero_body', wagtail.core.blocks.TextBlock(required=False)), ('button_label', wagtail.core.blocks.CharBlock(required=False)), ('button_link', wagtail.core.blocks.StreamBlock([('page', wagtail.core.blocks.PageChooserBlock(help_text='Choose only one link', icon='doc-full', label='Page', required=False)), ('external_url', wagtail.core.blocks.URLBlock(help_text='Choose only one link', label='External URL', required=False))], max_num=1, required=False))])), ('development_block', wagtail.core.blocks.StreamBlock([('content', wagtail.core.blocks.StructBlock([('content_title', wagtail.core.blocks.CharBlock(required=False)), ('content_services_title', wagtail.core.blocks.RichTextBlock(required=False)), ('content_body', wagtail.core.blocks.TextBlock(required=False)), ('button_label', wagtail.core.blocks.CharBlock(required=False)), ('button_link', wagtail.core.blocks.StreamBlock([('page', wagtail.core.blocks.PageChooserBlock(help_text='Choose only one link', icon='doc-full', label='page', required=False)), ('external_url', wagtail.core.blocks.URLBlock(help_text='Choose only one link', icon='site', label='external URL', required=False))], max_num=1, required=False)), ('image', wagtail.core.blocks.StreamBlock([('svg_image', wagtail.documents.blocks.DocumentChooserBlock(help_text='SVG format only', icon='image', label='SVG Image', required=False)), ('other_image', wagtail.images.blocks.ImageChooserBlock(help_text='JPEG/PNG format', icon='image', label='Other Image', required=False))], max_num=1, required=False))])), ('items', wagtail.core.blocks.StructBlock([('item_title', wagtail.core.blocks.CharBlock(required=False)), ('item_body', wagtail.core.blocks.TextBlock(required=False))]))])), ('design_block', wagtail.core.blocks.StructBlock([('design_title', wagtail.core.blocks.CharBlock(required=False)), ('design_service_title', wagtail.core.blocks.RichTextBlock(required=False)), ('design_body', wagtail.core.blocks.TextBlock(required=False)), ('image', wagtail.core.blocks.StreamBlock([('svg_image', wagtail.documents.blocks.DocumentChooserBlock(help_text='SVG format only', icon='image', label='SVG Image', required=False)), ('other_image', wagtail.images.blocks.ImageChooserBlock(help_text='JPEG/PNG format', icon='image', label='Other Image', required=False))], required=False)), ('design_footer', wagtail.core.blocks.TextBlock(required=False))])), ('team_block', wagtail.core.blocks.StreamBlock([('content', wagtail.core.blocks.StructBlock([('team_title', wagtail.core.blocks.CharBlock(required=False)), ('team_body', wagtail.core.blocks.TextBlock(required=False))])), ('members', wagtail.core.blocks.StructBlock([('members', public.blocks.TeamChooserBlock()), ('avatar', wagtail.core.blocks.StreamBlock([('svg_image', wagtail.documents.blocks.DocumentChooserBlock(help_text='SVG format only', icon='image', label='SVG Image', required=False)), ('other_image', wagtail.images.blocks.ImageChooserBlock(help_text='JPEG/PNG format', icon='image', label='Other Image', required=False))], max_num=1, required=False)), ('position', wagtail.core.blocks.CharBlock(required=False))]))])), ('client_block', wagtail.core.blocks.StructBlock([('client_body', wagtail.core.blocks.TextBlock(required=False))])), ('hiring_block', wagtail.core.blocks.StructBlock([('hiring_title', wagtail.core.blocks.CharBlock(required=False)), ('hiring_body', wagtail.core.blocks.TextBlock(required=False)), ('button_label', wagtail.core.blocks.CharBlock(required=False)), ('button_link', wagtail.core.blocks.StreamBlock([('page', wagtail.core.blocks.PageChooserBlock(help_text='Choose only one link', icon='doc-full', label='page', required=False)), ('external_url', wagtail.core.blocks.URLBlock(help_text='Choose only one link', icon='site', label='external URL', required=False))], max_num=1, required=False))])), ('testimony_block', wagtail.core.blocks.StreamBlock([('testimony_title', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(required=False))])), ('members', wagtail.core.blocks.StructBlock([('testimony', wagtail.core.blocks.TextBlock(required=False)), ('member', wagtail.core.blocks.CharBlock(required=False)), ('position', wagtail.core.blocks.CharBlock(required=False)), ('company_label', wagtail.core.blocks.CharBlock(required=False)), ('company_link', wagtail.core.blocks.StreamBlock([('page', wagtail.core.blocks.PageChooserBlock(help_text='Choose only one link', icon='doc-full', label='page', required=False)), ('external_url', wagtail.core.blocks.URLBlock(help_text='Choose only one link', icon='site', label='external URL', required=False))], max_num=1, required=False))]))])), ('contact_block', wagtail.core.blocks.StructBlock([('iframe_link', wagtail.core.blocks.StreamBlock([('page', wagtail.core.blocks.PageChooserBlock(help_text='Choose only one link', icon='doc-full', label='page', required=False)), ('external_url', wagtail.core.blocks.URLBlock(help_text='Choose only one link', icon='site', label='external URL', required=False))], max_num=1, required=False)), ('contact_title', wagtail.core.blocks.CharBlock(required=False)), ('contact_body', wagtail.core.blocks.TextBlock(required=False)), ('email', wagtail.core.blocks.EmailBlock(required=False))]))], blank=True, verbose_name='Page body'), + ), + migrations.DeleteModel( + name='TeamMembersPage', + ), + ] diff --git a/public/migrations/0006_swiftkindimage_swiftkindrendition.py b/public/migrations/0006_swiftkindimage_swiftkindrendition.py new file mode 100644 index 0000000..e5799e3 --- /dev/null +++ b/public/migrations/0006_swiftkindimage_swiftkindrendition.py @@ -0,0 +1,62 @@ +# Generated by Django 2.2.2 on 2020-02-06 06:39 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import public.image +import taggit.managers +import wagtail.core.models +import wagtail.images.models +import wagtail.search.index + + +class Migration(migrations.Migration): + + dependencies = [ + ('taggit', '0002_auto_20150616_2121'), + ('wagtailcore', '0041_group_collection_permissions_verbose_name_plural'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('public', '0005_auto_20200206_0552'), + ] + + operations = [ + migrations.CreateModel( + name='SwiftkindImage', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255, verbose_name='title')), + ('width', models.IntegerField(editable=False, verbose_name='width')), + ('height', models.IntegerField(editable=False, verbose_name='height')), + ('created_at', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='created at')), + ('focal_point_x', models.PositiveIntegerField(blank=True, null=True)), + ('focal_point_y', models.PositiveIntegerField(blank=True, null=True)), + ('focal_point_width', models.PositiveIntegerField(blank=True, null=True)), + ('focal_point_height', models.PositiveIntegerField(blank=True, null=True)), + ('file_size', models.PositiveIntegerField(editable=False, null=True)), + ('file_hash', models.CharField(blank=True, editable=False, max_length=40)), + ('file', models.ImageField(height_field='height', upload_to=public.image.get_upload_to, verbose_name='file', width_field='width')), + ('collection', models.ForeignKey(default=wagtail.core.models.get_root_collection_id, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtailcore.Collection', verbose_name='collection')), + ('tags', taggit.managers.TaggableManager(blank=True, help_text=None, through='taggit.TaggedItem', to='taggit.Tag', verbose_name='tags')), + ('uploaded_by_user', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='uploaded by user')), + ], + options={ + 'abstract': False, + }, + bases=(wagtail.search.index.Indexed, models.Model), + ), + migrations.CreateModel( + name='SwiftkindRendition', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('filter_spec', models.CharField(db_index=True, max_length=255)), + ('file', models.ImageField(height_field='height', upload_to=wagtail.images.models.get_rendition_upload_to, width_field='width')), + ('width', models.IntegerField(editable=False)), + ('height', models.IntegerField(editable=False)), + ('focal_point_key', models.CharField(blank=True, default='', editable=False, max_length=16)), + ('image', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='renditions', to='public.SwiftkindImage')), + ], + options={ + 'unique_together': {('image', 'filter_spec', 'focal_point_key')}, + }, + ), + ] diff --git a/public/models.py b/public/models.py index 71a8362..0406813 100644 --- a/public/models.py +++ b/public/models.py @@ -1,3 +1,73 @@ from django.db import models +from django import forms +from django.conf import settings -# Create your models here. +from wagtail.core.models import Page +from wagtail.core.fields import StreamField +from wagtail.admin.edit_handlers import StreamFieldPanel + + +from modelcluster.fields import ParentalManyToManyField, ParentalKey + +from wagtail.snippets.edit_handlers import SnippetChooserPanel +from wagtail.images.edit_handlers import ImageChooserPanel + +from .blocks import ( + HomePageBlock, + ProjectPageBlock, + ProcessPageBlock, + WebAppPageBlock +) + +from wagtail.snippets.models import register_snippet +from account.models import User +from .image import SwiftkindImage + + +class HomePage(Page): + """ Home page models + """ + parent_page_types = ['wagtailcore.Page'] + body = StreamField( + HomePageBlock(), verbose_name="Page body", blank=True, + ) + + content_panels = Page.content_panels + [ + StreamFieldPanel('body'), + ] + + +class ProjectPage(Page): + """ Project page models + """ + project = StreamField( + ProjectPageBlock(), verbose_name="Page projects", blank=True, + ) + + content_panels = Page.content_panels + [ + StreamFieldPanel('project'), + ] + + +class ProcessPage(Page): + """ Process page models + """ + body = StreamField( + ProcessPageBlock(), verbose_name="Page body", blank=True, + ) + + content_panels = Page.content_panels + [ + StreamFieldPanel('body'), + ] + + +class WebAppPage(Page): + """ Web app page + """ + body = StreamField( + WebAppPageBlock(), verbose_name="Page body", blank=True, + ) + + content_panels = Page.content_panels + [ + StreamFieldPanel('body'), + ] diff --git a/public/urls.py b/public/urls.py index 90cbf20..c01d3d9 100644 --- a/public/urls.py +++ b/public/urls.py @@ -2,10 +2,5 @@ from django.views.generic.base import TemplateView urlpatterns = [ - path('', TemplateView.as_view(template_name='public/index.html'), name="index"), - path('process/', TemplateView.as_view(template_name='public/process.html'), name="process"), - path('projects/', TemplateView.as_view(template_name='public/projects.html'), name="projects"), path('maintenance/', TemplateView.as_view(template_name='public/maintenance.html'), name="maintenance"), - path('market/', TemplateView.as_view(template_name='public/marketplace.html'), name="marketplace"), - path('workflow/', TemplateView.as_view(template_name='public/workflow.html'), name="workflow"), ] \ No newline at end of file diff --git a/swiftkind/settings.py b/swiftkind/settings.py index 5cf22ff..4d2fffe 100644 --- a/swiftkind/settings.py +++ b/swiftkind/settings.py @@ -21,6 +21,7 @@ INSTALLED_APPS = [ 'blog', 'account', + 'public', 'wagtail.contrib.forms', 'wagtail.contrib.redirects', @@ -143,6 +144,7 @@ WAGTAIL_USER_EDIT_FORM = 'account.forms.CustomUserEditForm' WAGTAIL_USER_CREATION_FORM = 'account.forms.CustomUserCreationForm' WAGTAIL_USER_CUSTOM_FIELDS = ['email', 'position', 'avatar'] +WAGTAILIMAGES_IMAGE_MODEL = 'public.SwiftkindImage' # Allow any settings to be defined in local_settings.py which should be # ignored in your version control system allowing for settings to be diff --git a/swiftkind/urls.py b/swiftkind/urls.py index a287778..cdc382f 100644 --- a/swiftkind/urls.py +++ b/swiftkind/urls.py @@ -4,9 +4,15 @@ from django.urls import path, include from django.views.generic import TemplateView +from wagtail.admin import urls as wagtailadmin_urls +from wagtail.documents import urls as wagtaildocs_urls +from wagtail.core import urls as wagtail_urls urlpatterns = [ path('admin/', admin.site.urls), + path('documents/', include(wagtaildocs_urls)), + path('cms/', include(wagtailadmin_urls)), + path('blog/', include('blog.urls')), path('', include('public.urls')), - path('', include('blog.urls')), + path('', include(wagtail_urls)), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 3e50131..f5f6bcf 100644 --- a/templates/base.html +++ b/templates/base.html @@ -63,7 +63,7 @@
{{ post.body | truncatechars:"300" | richtext }}
5 Mins read