diff --git a/blog/__init__.py b/blog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/blog/admin.py b/blog/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/blog/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/blog/apps.py b/blog/apps.py new file mode 100644 index 0000000..94788a5 --- /dev/null +++ b/blog/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BlogConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'blog' diff --git a/blog/migrations/0001_initial.py b/blog/migrations/0001_initial.py new file mode 100644 index 0000000..da895fe --- /dev/null +++ b/blog/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.2 on 2024-02-29 17:53 + +import django.db.models.deletion +import wagtail.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('wagtailcore', '0091_remove_revision_submitted_for_moderation'), + ] + + operations = [ + migrations.CreateModel( + name='BlogIndexPage', + 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')), + ('intro', wagtail.fields.RichTextField(blank=True)), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + ] diff --git a/blog/migrations/0002_blogpage.py b/blog/migrations/0002_blogpage.py new file mode 100644 index 0000000..3b19f9b --- /dev/null +++ b/blog/migrations/0002_blogpage.py @@ -0,0 +1,29 @@ +# Generated by Django 5.0.2 on 2024-02-29 19:28 + +import django.db.models.deletion +import wagtail.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0001_initial'), + ('wagtailcore', '0091_remove_revision_submitted_for_moderation'), + ] + + operations = [ + migrations.CreateModel( + name='BlogPage', + 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')), + ('date', models.DateField(verbose_name='Post date')), + ('intro', models.CharField(max_length=250)), + ('body', wagtail.fields.RichTextField(blank=True)), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + ] diff --git a/blog/migrations/0003_blogpagegalleryimage.py b/blog/migrations/0003_blogpagegalleryimage.py new file mode 100644 index 0000000..5f6fade --- /dev/null +++ b/blog/migrations/0003_blogpagegalleryimage.py @@ -0,0 +1,30 @@ +# Generated by Django 5.0.2 on 2024-02-29 19:40 + +import django.db.models.deletion +import modelcluster.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0002_blogpage'), + ('wagtailimages', '0025_alter_image_file_alter_rendition_file'), + ] + + operations = [ + migrations.CreateModel( + name='BlogPageGalleryImage', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('sort_order', models.IntegerField(blank=True, editable=False, null=True)), + ('caption', models.CharField(blank=True, max_length=250)), + ('image', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtailimages.image')), + ('page', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='gallery_images', to='blog.blogpage')), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + ), + ] diff --git a/blog/migrations/0004_author.py b/blog/migrations/0004_author.py new file mode 100644 index 0000000..7c541be --- /dev/null +++ b/blog/migrations/0004_author.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.2 on 2024-02-29 19:48 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0003_blogpagegalleryimage'), + ('wagtailimages', '0025_alter_image_file_alter_rendition_file'), + ] + + operations = [ + migrations.CreateModel( + name='Author', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('author_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image')), + ], + options={ + 'verbose_name_plural': 'Authors', + }, + ), + ] diff --git a/blog/migrations/0005_blogpage_authors.py b/blog/migrations/0005_blogpage_authors.py new file mode 100644 index 0000000..a9d74be --- /dev/null +++ b/blog/migrations/0005_blogpage_authors.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0.2 on 2024-02-29 19:56 + +import modelcluster.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0004_author'), + ] + + operations = [ + migrations.AddField( + model_name='blogpage', + name='authors', + field=modelcluster.fields.ParentalManyToManyField(blank=True, to='blog.author'), + ), + ] diff --git a/blog/migrations/0006_blogpagetag_blogpage_tags.py b/blog/migrations/0006_blogpagetag_blogpage_tags.py new file mode 100644 index 0000000..097948c --- /dev/null +++ b/blog/migrations/0006_blogpagetag_blogpage_tags.py @@ -0,0 +1,33 @@ +# Generated by Django 5.0.2 on 2024-02-29 20:00 + +import django.db.models.deletion +import modelcluster.contrib.taggit +import modelcluster.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0005_blogpage_authors'), + ('taggit', '0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx'), + ] + + operations = [ + migrations.CreateModel( + name='BlogPageTag', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('content_object', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='tagged_items', to='blog.blogpage')), + ('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_items', to='taggit.tag')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='blogpage', + name='tags', + field=modelcluster.contrib.taggit.ClusterTaggableManager(blank=True, help_text='A comma-separated list of tags.', through='blog.BlogPageTag', to='taggit.Tag', verbose_name='Tags'), + ), + ] diff --git a/blog/migrations/0007_blogtagindexpage.py b/blog/migrations/0007_blogtagindexpage.py new file mode 100644 index 0000000..c670845 --- /dev/null +++ b/blog/migrations/0007_blogtagindexpage.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.2 on 2024-02-29 20:07 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0006_blogpagetag_blogpage_tags'), + ('wagtailcore', '0091_remove_revision_submitted_for_moderation'), + ] + + operations = [ + migrations.CreateModel( + name='BlogTagIndexPage', + 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')), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + ] diff --git a/blog/migrations/__init__.py b/blog/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/blog/models.py b/blog/models.py new file mode 100644 index 0000000..cf3f23b --- /dev/null +++ b/blog/models.py @@ -0,0 +1,108 @@ +from django import forms +from django.db import models +from modelcluster.fields import ParentalKey, ParentalManyToManyField +from modelcluster.contrib.taggit import ClusterTaggableManager +from taggit.models import TaggedItemBase + +from wagtail.models import Page, Orderable +from wagtail.fields import RichTextField +from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel +from wagtail.search import index + +from wagtail.snippets.models import register_snippet + +class BlogIndexPage(Page): + intro = RichTextField(blank=True) + + def get_context(self, request): + # Update context to include only published posts, ordered by reverse-chron + context = super().get_context(request) + blogpages = self.get_children().live().order_by('-first_published_at') + context['blogpages'] = blogpages + return context + + content_panels = Page.content_panels + [ + FieldPanel('intro') + ] + +class BlogTagIndexPage(Page): + + def get_context(self, request): + tag = request.GET.get('tag') + blogpages = BlogPage.objects.filter(tags__name=tag) + + # Update template context + context = super().get_context(request) + context['blogpages'] = blogpages + return context + +class BlogPageTag(TaggedItemBase): + content_object = ParentalKey( + 'BlogPage', + related_name='tagged_items', + on_delete=models.CASCADE + ) + +class BlogPage(Page): + date = models.DateField("Post date") + intro = models.CharField(max_length=250) + body = RichTextField(blank=True) + + authors = ParentalManyToManyField('blog.Author', blank=True) + + tags = ClusterTaggableManager(through=BlogPageTag, blank=True) + + def main_image(self): + gallery_item = self.gallery_images.first() + if gallery_item: + return gallery_item.image + else: + return None + + search_fields = Page.search_fields + [ + index.SearchField('intro'), + index.SearchField('body'), + ] + + content_panels = Page.content_panels + [ + MultiFieldPanel([ + FieldPanel('date'), + FieldPanel('authors', widget=forms.CheckboxSelectMultiple), + FieldPanel('tags'), + ], heading="Blog information"), + FieldPanel('intro'), + FieldPanel('body'), + InlinePanel('gallery_images', label="Gallery images"), + ] + + +class BlogPageGalleryImage(Orderable): + page = ParentalKey(BlogPage, on_delete=models.CASCADE, related_name='gallery_images') + image = models.ForeignKey( + 'wagtailimages.Image', on_delete=models.CASCADE, related_name='+' + ) + caption = models.CharField(blank=True, max_length=250) + + panels = [ + FieldPanel('image'), + FieldPanel('caption'), + ] + +@register_snippet +class Author(models.Model): + name = models.CharField(max_length=255) + author_image = models.ForeignKey( + 'wagtailimages.Image', null=True, blank=True, + on_delete=models.SET_NULL, related_name='+' + ) + + panels = [ + FieldPanel('name'), + FieldPanel('author_image'), + ] + + def __str__(self): + return self.name + + class Meta: + verbose_name_plural = 'Authors' \ No newline at end of file diff --git a/blog/templates/blog/blog_index_page.html b/blog/templates/blog/blog_index_page.html new file mode 100644 index 0000000..5203dce --- /dev/null +++ b/blog/templates/blog/blog_index_page.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} + +{% load wagtailcore_tags wagtailimages_tags %} + +{% block body_class %}template-blogindexpage{% endblock %} + +{% block content %} +

{{ page.title }}

+ +
{{ page.intro|richtext }}
+ +{% for post in blogpages %} + {% with post=post.specific %} +

{{ post.title }}

+ + + {% with post.main_image as main_image %} + {% if main_image %}{% image main_image fill-160x100 %}{% endif %} + {% endwith %} + +

{{ post.intro }}

+ {{ post.body|richtext }} + {% endwith %} +{% endfor %} + +{% endblock %} \ No newline at end of file diff --git a/blog/templates/blog/blog_page.html b/blog/templates/blog/blog_page.html new file mode 100644 index 0000000..4a46a21 --- /dev/null +++ b/blog/templates/blog/blog_page.html @@ -0,0 +1,50 @@ +{% extends "base.html" %} + +{% load wagtailcore_tags wagtailimages_tags %} + +{% block body_class %}template-blogpage{% endblock %} + +{% block content %} +

{{ page.title }}

+

{{ page.date }}

+ + {% with authors=page.authors.all %} + {% if authors %} +

Posted by:

+ + {% endif %} + {% endwith %} + +
{{ page.intro }}
+ + {{ page.body|richtext }} + + {% for item in page.gallery_images.all %} +
+ {% image item.image fill-320x240 %} +

{{ item.caption }}

+
+ {% endfor %} + +

Return to blog

+ + {% with tags=page.tags.all %} + {% if tags %} +
+

Tags

+ {% for tag in tags %} + {{ tag }} + {% endfor %} +
+ {% endif %} + +{% endwith %} + +{% endblock %} \ No newline at end of file diff --git a/blog/templates/blog/blog_tag_index_page.html b/blog/templates/blog/blog_tag_index_page.html new file mode 100644 index 0000000..15f6608 --- /dev/null +++ b/blog/templates/blog/blog_tag_index_page.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} +{% load wagtailcore_tags %} + +{% block content %} + + {% if request.GET.tag %} +

Showing pages tagged "{{ request.GET.tag }}"

+ {% endif %} + + {% for blogpage in blogpages %} + +

+ {{ blogpage.title }}
+ Revised: {{ blogpage.latest_revision_created_at }}
+

+ + {% empty %} + No pages found with that tag. + {% endfor %} + +{% endblock %} \ No newline at end of file diff --git a/blog/tests.py b/blog/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/blog/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/blog/views.py b/blog/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/blog/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/iamkonstantin_web/settings/base.py b/iamkonstantin_web/settings/base.py index b52ff57..777adc7 100644 --- a/iamkonstantin_web/settings/base.py +++ b/iamkonstantin_web/settings/base.py @@ -24,6 +24,7 @@ BASE_DIR = os.path.dirname(PROJECT_DIR) # Application definition INSTALLED_APPS = [ + "blog", "home", "search", "wagtail.contrib.forms", diff --git a/media/images/097d70b8f56ef0dbeaefd4a981ccc510.2e16d0ba.fill-160x100.png b/media/images/097d70b8f56ef0dbeaefd4a981ccc510.2e16d0ba.fill-160x100.png new file mode 100644 index 0000000..53861fa Binary files /dev/null and b/media/images/097d70b8f56ef0dbeaefd4a981ccc510.2e16d0ba.fill-160x100.png differ diff --git a/media/images/097d70b8f56ef0dbeaefd4a981ccc510.2e16d0ba.fill-320x240.png b/media/images/097d70b8f56ef0dbeaefd4a981ccc510.2e16d0ba.fill-320x240.png new file mode 100644 index 0000000..a695f88 Binary files /dev/null and b/media/images/097d70b8f56ef0dbeaefd4a981ccc510.2e16d0ba.fill-320x240.png differ diff --git a/media/images/097d70b8f56ef0dbeaefd4a981ccc510.max-165x165.png b/media/images/097d70b8f56ef0dbeaefd4a981ccc510.max-165x165.png new file mode 100644 index 0000000..5723675 Binary files /dev/null and b/media/images/097d70b8f56ef0dbeaefd4a981ccc510.max-165x165.png differ diff --git a/media/images/_508d3ac3-6905-4b23-b662-a3604050ac8.2e16d0ba.fill-40x60.jpg b/media/images/_508d3ac3-6905-4b23-b662-a3604050ac8.2e16d0ba.fill-40x60.jpg new file mode 100644 index 0000000..ba35712 Binary files /dev/null and b/media/images/_508d3ac3-6905-4b23-b662-a3604050ac8.2e16d0ba.fill-40x60.jpg differ diff --git a/media/images/_508d3ac3-6905-4b23-b662-a3604050ac80.max-165x165.jpg b/media/images/_508d3ac3-6905-4b23-b662-a3604050ac80.max-165x165.jpg new file mode 100644 index 0000000..d363d9c Binary files /dev/null and b/media/images/_508d3ac3-6905-4b23-b662-a3604050ac80.max-165x165.jpg differ diff --git a/media/images/moon_and_planets_design.2e16d0ba.fill-320x240.jpg b/media/images/moon_and_planets_design.2e16d0ba.fill-320x240.jpg new file mode 100644 index 0000000..87942ed Binary files /dev/null and b/media/images/moon_and_planets_design.2e16d0ba.fill-320x240.jpg differ diff --git a/media/images/moon_and_planets_design.max-165x165.jpg b/media/images/moon_and_planets_design.max-165x165.jpg new file mode 100644 index 0000000..c290590 Binary files /dev/null and b/media/images/moon_and_planets_design.max-165x165.jpg differ diff --git a/media/original_images/097d70b8f56ef0dbeaefd4a981ccc510.png b/media/original_images/097d70b8f56ef0dbeaefd4a981ccc510.png new file mode 100644 index 0000000..eb1805b Binary files /dev/null and b/media/original_images/097d70b8f56ef0dbeaefd4a981ccc510.png differ diff --git a/media/original_images/_508d3ac3-6905-4b23-b662-a3604050ac80.jpeg b/media/original_images/_508d3ac3-6905-4b23-b662-a3604050ac80.jpeg new file mode 100644 index 0000000..94a9f3a Binary files /dev/null and b/media/original_images/_508d3ac3-6905-4b23-b662-a3604050ac80.jpeg differ diff --git a/media/original_images/moon_and_planets_design.jpeg b/media/original_images/moon_and_planets_design.jpeg new file mode 100644 index 0000000..d0c1440 Binary files /dev/null and b/media/original_images/moon_and_planets_design.jpeg differ