diff --git a/services/cms/.gitignore b/services/cms/.gitignore new file mode 100644 index 0000000..c59768b --- /dev/null +++ b/services/cms/.gitignore @@ -0,0 +1 @@ +media/ \ No newline at end of file diff --git a/services/cms/blog/__init__.py b/services/cms/blog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/cms/blog/admin.py b/services/cms/blog/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/services/cms/blog/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/services/cms/blog/apps.py b/services/cms/blog/apps.py new file mode 100644 index 0000000..1003897 --- /dev/null +++ b/services/cms/blog/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class BlogConfig(AppConfig): + name = "blog" diff --git a/services/cms/blog/migrations/0001_initial.py b/services/cms/blog/migrations/0001_initial.py new file mode 100644 index 0000000..cec22d9 --- /dev/null +++ b/services/cms/blog/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# Generated by Django 2.1.2 on 2018-11-11 02:20 + +from django.db import migrations, models +import django.db.models.deletion +import wagtail.core.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [("wagtailcore", "0040_page_draft_title")] + + 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.core.fields.RichTextField(blank=True)), + ], + options={"abstract": False}, + bases=("wagtailcore.page",), + ) + ] diff --git a/services/cms/blog/migrations/0002_blogpage.py b/services/cms/blog/migrations/0002_blogpage.py new file mode 100644 index 0000000..0e04e49 --- /dev/null +++ b/services/cms/blog/migrations/0002_blogpage.py @@ -0,0 +1,34 @@ +# Generated by Django 2.1.2 on 2018-11-11 02:58 + +from django.db import migrations, models +import django.db.models.deletion +import wagtail.core.fields + + +class Migration(migrations.Migration): + + dependencies = [("wagtailcore", "0040_page_draft_title"), ("blog", "0001_initial")] + + 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.core.fields.RichTextField(blank=True)), + ], + options={"abstract": False}, + bases=("wagtailcore.page",), + ) + ] diff --git a/services/cms/blog/migrations/0003_blogpagegalleryimage.py b/services/cms/blog/migrations/0003_blogpagegalleryimage.py new file mode 100644 index 0000000..a34d198 --- /dev/null +++ b/services/cms/blog/migrations/0003_blogpagegalleryimage.py @@ -0,0 +1,52 @@ +# Generated by Django 2.1.2 on 2018-11-16 18:45 + +from django.db import migrations, models +import django.db.models.deletion +import modelcluster.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ("wagtailimages", "0021_image_file_hash"), + ("blog", "0002_blogpage"), + ] + + operations = [ + migrations.CreateModel( + name="BlogPageGalleryImage", + fields=[ + ( + "id", + models.AutoField( + 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/services/cms/blog/migrations/0004_auto_20181119_1950.py b/services/cms/blog/migrations/0004_auto_20181119_1950.py new file mode 100644 index 0000000..8263194 --- /dev/null +++ b/services/cms/blog/migrations/0004_auto_20181119_1950.py @@ -0,0 +1,33 @@ +# Generated by Django 2.1.2 on 2018-11-19 19:50 + +from django.db import migrations, models +import django.db.models.deletion +import modelcluster.contrib.taggit +import modelcluster.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('taggit', '0002_auto_20150616_2121'), + ('blog', '0003_blogpagegalleryimage'), + ] + + operations = [ + migrations.CreateModel( + name='BlogPageTag', + fields=[ + ('id', models.AutoField(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='blog_blogpagetag_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/services/cms/blog/migrations/0005_blogtagindexpage.py b/services/cms/blog/migrations/0005_blogtagindexpage.py new file mode 100644 index 0000000..8fea19f --- /dev/null +++ b/services/cms/blog/migrations/0005_blogtagindexpage.py @@ -0,0 +1,25 @@ +# Generated by Django 2.1.2 on 2018-11-19 20:04 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0040_page_draft_title'), + ('blog', '0004_auto_20181119_1950'), + ] + + 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/services/cms/blog/migrations/__init__.py b/services/cms/blog/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/services/cms/blog/models.py b/services/cms/blog/models.py new file mode 100644 index 0000000..139cea0 --- /dev/null +++ b/services/cms/blog/models.py @@ -0,0 +1,117 @@ +from django.db import models + +from modelcluster.fields import ParentalKey +from modelcluster.contrib.taggit import ClusterTaggableManager +from taggit.models import TaggedItemBase + +from wagtail.core.models import Page, Orderable +from wagtail.core.fields import RichTextField +from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel +from wagtail.images.edit_handlers import ImageChooserPanel +from wagtail.search import index + + +class BlogIndexPage(Page): + """Entry point of the blog. Displays its children `BlogPages`""" + + intro = RichTextField(blank=True) + + content_panels = Page.content_panels + [FieldPanel("intro", classname="full")] + + def get_context(self, request, *args, **kwargs): + """ + Overrides the default context to include only published pages, ordered by reverse chronological order. + These child pages can be accessed in the template using `blogpages` instead of page.get_children. + """ + context = super().get_context(request) + blogpages = self.get_children().live().order_by("-first_published_at") + context["blogpages"] = blogpages + return context + + +class BlogPageTag(TaggedItemBase): + """Provides a simple tagging feature to Blog Pages""" + + content_object = ParentalKey( + "BlogPage", related_name="tagged_items", on_delete=models.CASCADE + ) + + +class BlogPage(Page): + """Models a blog page entry.""" + + date = models.DateField("Post date") + intro = models.CharField(max_length=250) + body = RichTextField(blank=True) + tags = ClusterTaggableManager(through=BlogPageTag, blank=True) + + def main_image(self): + """Returns the first image associated with a `BlogPage` from `BlogPageGalleryImage`""" + 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 + [ + # Grouping Date and tags together for readability in the Admin panel + MultiFieldPanel( + [FieldPanel("date"), FieldPanel("tags")], heading="Blog information" + ), + FieldPanel("intro"), + FieldPanel("body", classname="full"), + InlinePanel("gallery_images", label="Gallery images"), + ] + + +class BlogPageGalleryImage(Orderable): + """ + An image gallery descendant to a `BlogPage` + + Inherits a `sort_order` field from the `Orderable` object which keeps track of image ordering. + The `ParentalKey` works similarly to a `ForeignKey` but also defines this class as a child of `BlogPage` model. + This means that the image gallery associated with a particular `BlogPage` instance is treated as a part of the page + in operations like submitting for moderation, and tracking version history. + + The `BlogPageGalleryImage.image` is a `ForeignKey to the Wagtail built-in `Image` model where images themselves are + stored. The `ImageChooserPanel` provides a popup interface for choosing an existing image or uploading a new one. + This allows for the same image to exist in multiple galleries - effectively creating a many-to-many relationship + between pages and images. + """ + + page = ParentalKey( + BlogPage, on_delete=models.CASCADE, related_name="gallery_images" + ) + image = models.ForeignKey( + # on_delete here means that is an image is deleted from the system, the gallery entry is deleted as well. + "wagtailimages.Image", + on_delete=models.CASCADE, + related_name="+", + ) + caption = models.CharField(blank=True, max_length=250) + + panels = [ImageChooserPanel("image"), FieldPanel("caption")] + + +class BlogTagIndexPage(Page): + """ + Models the Index page of blogs by tag. + + Even though this model does not define any fields of its own, it is a subclass of Page and is added to the Wagtail + ecosystem. This means that it can be given a title and a URL in the admin + + """ + + def get_context(self, request, *args, **kwargs): + """Filters by tage passed in request query string""" + tag = request.GET.get('tag') + blogpages = BlogPage.objects.filter(tags__name=tag) + + context = super().get_context(request) + context['blogpages'] = blogpages + return context \ No newline at end of file diff --git a/services/cms/blog/templates/blog/blog_index_page.html b/services/cms/blog/templates/blog/blog_index_page.html new file mode 100644 index 0000000..74b579f --- /dev/null +++ b/services/cms/blog/templates/blog/blog_index_page.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} + +{% load wagtailcore_tags wagtailimages_tags %} + +{% block body_class %}helvetica{% endblock %} + +{% block content %} + + {% include 'header.html' %} + +
+

{{ page.title }}

+ + {% for post in blogpages %} + {% with post=post.specific %} +
+ +
+
+

{{ post.title }}

+

{{ post.intro }}

+
+
+ {% with post.main_image as main_image %} + {% if main_image %}{% image main_image fill-320x240 class="db" %}{% endif %} + {% endwith %} +
+
+

By {{ post.owner }}

+ +
+
+ {% endwith %} + {% endfor %} + +
+ + {% include 'footer.html' %} + +{% endblock %} diff --git a/services/cms/blog/templates/blog/blog_page.html b/services/cms/blog/templates/blog/blog_page.html new file mode 100644 index 0000000..65b4152 --- /dev/null +++ b/services/cms/blog/templates/blog/blog_page.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} + +{% load wagtailcore_tags wagtailimages_tags %} + +{% block body_class %}helvetica{% endblock %} + +{% block content %} + {% include 'header.html' %} +
+

{{ page.title }}

+

{{ page.date }}

+ {% if page.tags.all.count %} +
+ {% for tag in page.tags.all %} + {{ tag }} + {% endfor %} +
+ {% endif %} + + {{ page.body|richtext }} + + {% for item in page.gallery_images.all %} +
+ {% image item.image fill-320x240 %} +

{{ item.caption }}

+
+ {% endfor %} + +
+{% endblock %} \ No newline at end of file diff --git a/services/cms/blog/templates/blog/blog_tag_index_page.html b/services/cms/blog/templates/blog/blog_tag_index_page.html new file mode 100644 index 0000000..28e803f --- /dev/null +++ b/services/cms/blog/templates/blog/blog_tag_index_page.html @@ -0,0 +1,38 @@ +{% extends "base.html" %} +{% load wagtailcore_tags wagtailimages_tags %} + +{% block body_class %}helvetica{% endblock %} + +{% block content %} +{% include 'header.html' %} +
+ {% if request.GET.tag|length %} +

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

+ {% endif %} + + {% for post in blogpages %} + {% with post=post.specific %} +
+ +
+
+

{{ post.title }}

+

{{ post.intro }}

+
+
+ {% with post.main_image as main_image %} + {% if main_image %}{% image main_image fill-320x240 class="db" %}{% endif %} + {% endwith %} +
+
+

By {{ post.owner }}

+ +
+
+ {% endwith %} + {% empty %} + No pages found with that tag. + {% endfor %} +
+ +{% endblock %} \ No newline at end of file diff --git a/services/cms/blog/tests.py b/services/cms/blog/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/services/cms/blog/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/services/cms/blog/views.py b/services/cms/blog/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/services/cms/blog/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/services/cms/cms/settings/base.py b/services/cms/cms/settings/base.py index 07a787f..ac5d375 100644 --- a/services/cms/cms/settings/base.py +++ b/services/cms/cms/settings/base.py @@ -25,6 +25,7 @@ BASE_DIR = os.path.dirname(PROJECT_DIR) INSTALLED_APPS = [ "home", + "blog", "search", "wagtail.contrib.forms", "wagtail.contrib.redirects", diff --git a/services/cms/cms/settings/dev.py b/services/cms/cms/settings/dev.py index c800aaa..ace826a 100644 --- a/services/cms/cms/settings/dev.py +++ b/services/cms/cms/settings/dev.py @@ -14,7 +14,7 @@ EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", - "NAME": "datasketch", + "NAME": "datasketch_dev", "USER": "datasketch", "PASSWORD": os.environ.get("DB_PASSWD") if os.environ.get("DB_PASSWD") else "", "HOST": os.environ.get("DB_HOST") if os.environ.get("DB_HOST") else "127.0.0.1", diff --git a/services/cms/cms/static/css/cms.css b/services/cms/cms/static/css/cms.css index e69de29..b38f66d 100644 --- a/services/cms/cms/static/css/cms.css +++ b/services/cms/cms/static/css/cms.css @@ -0,0 +1,9 @@ +.bg-grad-blue { + background-image: linear-gradient(-208deg, #86C3D0 0%, #4E90A3 66%); } + +.bg-grad-green { + background-image: linear-gradient(to top right, #41b782, #86D169); } + +.hover-bg-orange:hover { + background-color: #FF6300; + transition: all .3s ease; } diff --git a/services/cms/cms/static/icon/basic_book_pen.svg b/services/cms/cms/static/icon/basic_book_pen.svg new file mode 100644 index 0000000..c73ba42 --- /dev/null +++ b/services/cms/cms/static/icon/basic_book_pen.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/services/cms/cms/static/icon/basic_settings.svg b/services/cms/cms/static/icon/basic_settings.svg new file mode 100644 index 0000000..8785054 --- /dev/null +++ b/services/cms/cms/static/icon/basic_settings.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/services/cms/cms/static/icon/basic_webpage_txt.svg b/services/cms/cms/static/icon/basic_webpage_txt.svg new file mode 100644 index 0000000..9bd072f --- /dev/null +++ b/services/cms/cms/static/icon/basic_webpage_txt.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/services/cms/cms/static/img/programming_background.jpg b/services/cms/cms/static/img/programming_background.jpg new file mode 100644 index 0000000..8c72da9 Binary files /dev/null and b/services/cms/cms/static/img/programming_background.jpg differ diff --git a/services/cms/cms/static/scss/cms.scss b/services/cms/cms/static/scss/cms.scss new file mode 100644 index 0000000..99c3029 --- /dev/null +++ b/services/cms/cms/static/scss/cms.scss @@ -0,0 +1,12 @@ +.bg-grad-blue { + background-image: linear-gradient(-208deg, #86C3D0 0%, #4E90A3 66%); +} + +.bg-grad-green { + background-image: linear-gradient(to top right, #41b782, #86D169); +} + +.hover-bg-orange:hover { + background-color: #FF6300; + transition: all .3s ease; +} diff --git a/services/cms/cms/templates/404.html b/services/cms/cms/templates/404.html index 3a5500e..e119fa8 100644 --- a/services/cms/cms/templates/404.html +++ b/services/cms/cms/templates/404.html @@ -3,7 +3,7 @@ {% block body_class %}template-404{% endblock %} {% block content %} -

Page not found

+

Page not found

-

Sorry, this page could not be found.

+

Sorry, this page could not be found.

{% endblock %} diff --git a/services/cms/cms/templates/500.html b/services/cms/cms/templates/500.html index 72b6406..c7248dd 100644 --- a/services/cms/cms/templates/500.html +++ b/services/cms/cms/templates/500.html @@ -1,13 +1,13 @@ - - - Internal server error - - - -

Internal server error

+ + + Internal server error + + + +

Internal server error

-

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

- +

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

+ diff --git a/services/cms/cms/templates/base.html b/services/cms/cms/templates/base.html index 87ed833..379bfd9 100644 --- a/services/cms/cms/templates/base.html +++ b/services/cms/cms/templates/base.html @@ -2,40 +2,41 @@ - - - - {% block title %} - {% if self.seo_title %}{{ self.seo_title }}{% else %}{{ self.title }}{% endif %} - {% endblock %} - {% block title_suffix %} - {% with self.get_site.site_name as site_name %} - {% if site_name %}- {{ site_name }}{% endif %} - {% endwith %} - {% endblock %} - - - - - {# Global stylesheets #} - - - - {% block extra_css %} - {# Override this in templates to add extra stylesheets #} - {% endblock %} - - - - {% wagtailuserbar %} - - {% block content %}{% endblock %} - - {# Global javascript #} - - - {% block extra_js %} - {# Override this in templates to add extra javascript #} - {% endblock %} - + + + + {% block title %} + {% if self.seo_title %}{{ self.seo_title }}{% else %}{{ self.title }}{% endif %} + {% endblock %} + {% block title_suffix %} + {% with self.get_site.site_name as site_name %} + {% if site_name %}- {{ site_name }}{% endif %} + {% endwith %} + {% endblock %} + + + + + {# Global stylesheets #} + + + + {% block extra_css %} + {# Override this in templates to add extra stylesheets #} + {% endblock %} + + + +{% wagtailuserbar %} + + +{% block content %}{% endblock %} + +{# Global javascript #} + + +{% block extra_js %} + {# Override this in templates to add extra javascript #} +{% endblock %} + diff --git a/services/cms/cms/templates/footer.html b/services/cms/cms/templates/footer.html new file mode 100644 index 0000000..a424763 --- /dev/null +++ b/services/cms/cms/templates/footer.html @@ -0,0 +1,10 @@ +{% load wagtailcore_tags %} + + \ No newline at end of file diff --git a/services/cms/cms/templates/header.html b/services/cms/cms/templates/header.html new file mode 100644 index 0000000..262bd20 --- /dev/null +++ b/services/cms/cms/templates/header.html @@ -0,0 +1,20 @@ +{% load wagtailcore_tags %} + +
+ + +
\ No newline at end of file diff --git a/services/cms/home/templates/home/home_page.html b/services/cms/home/templates/home/home_page.html index 968c6ff..9ed7e9e 100644 --- a/services/cms/home/templates/home/home_page.html +++ b/services/cms/home/templates/home/home_page.html @@ -1,10 +1,74 @@ {% extends "base.html" %} +{% load static %} + {% load wagtailcore_tags %} -{% block body_class %}helvetica{% endblock %} +{% block body_class %}helvetica bg-black-10{% endblock %} {% block content %} -

Welcome to Datasketch.io!

- {{ page.body|richtext }} +
+
+ {% include "header.html" %} +
+

Hire Datasketch

+

A full stack shop for web design, application development, and training

+ Connect + or + Read our blog +
+
+
+
+

Just a man trying to make a buck

+
+
+
+ +

Training

+
+
+

+ As a college educator I learned what does and does not work for students. I strive to bring each of my students + along thier learning journey. +

+
+
+
+ +

Websites

+
+
+

+ Design & Development, eCommerce, Responsive Design. I can deliver you a stunning website. +

+
+
+
+ +

App Development

+
+
+

+ Quite affectionate and outgoing. + She loves to get chin scratches and will + roll around on the floor waiting for you give her more of them. +

+
+
+
+
+
+

Together we can make great things.

+
+ + +
+ +
+ +
+
+
+ {% include "footer.html" %} {% endblock %}