diff options
Diffstat (limited to 'app/pages')
-rw-r--r-- | app/pages/__init__.py | 0 | ||||
-rw-r--r-- | app/pages/admin.py | 55 | ||||
-rw-r--r-- | app/pages/build.py | 25 | ||||
-rw-r--r-- | app/pages/migrations/0001_initial.py | 49 | ||||
-rw-r--r-- | app/pages/migrations/0002_auto_20211031_1354.py | 30 | ||||
-rw-r--r-- | app/pages/migrations/__init__.py | 0 | ||||
-rw-r--r-- | app/pages/models.py | 61 | ||||
-rw-r--r-- | app/pages/static.py | 123 | ||||
-rw-r--r-- | app/pages/templates/pages/about.html | 32 | ||||
-rw-r--r-- | app/pages/templates/pages/homepage.html | 32 | ||||
-rw-r--r-- | app/pages/templates/pages/page_detail.html | 38 | ||||
-rw-r--r-- | app/pages/templates/pages/page_detail.txt | 8 | ||||
-rw-r--r-- | app/pages/urls.py | 13 | ||||
-rw-r--r-- | app/pages/views.py | 40 |
14 files changed, 506 insertions, 0 deletions
diff --git a/app/pages/__init__.py b/app/pages/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/pages/__init__.py diff --git a/app/pages/admin.py b/app/pages/admin.py new file mode 100644 index 0000000..3c7d900 --- /dev/null +++ b/app/pages/admin.py @@ -0,0 +1,55 @@ +from django.contrib import admin +from utils.widgets import LGEntryForm + +from django import forms +from django.forms import Textarea +from django.db import models + +from pages.models import Page, HomePage + + +class PageEntryForm(forms.ModelForm): + class Meta: + model = Page + fields = '__all__' + widgets = { + 'body_markdown': forms.Textarea(attrs={'rows': 50, 'cols': 100}), + } + + +@admin.register(Page) +class PageAdmin(admin.ModelAdmin): + form = LGEntryForm + list_display = ('title', 'site', 'slug', 'path', 'app', 'build') + search_fields = ['title', 'body_markdown'] + prepopulated_fields = {"slug": ('title',)} + fieldsets = ( + ('Page', { + 'fields': ('title', 'sub_title', 'body_markdown', ('build', 'enable_comments'), ('site','slug', 'path', 'app'), 'featured_image', 'pub_date'), + 'classes': ('show', 'extrapretty', 'wide') + }), + ('Metadata', { + 'classes': ('collapse closed',), + 'fields': ('meta_description',), + }) + ) + + class Media: + js = ('image-loader.js', 'product-loader.js', 'next-prev-links.js') + css = { + "all": ("my_styles.css",) + } + + + + +@admin.register(HomePage) +class HomePageAdmin(admin.ModelAdmin): + form = LGEntryForm + filter_horizontal = ('popular',) + + class Media: + js = ('image-loader.js', 'next-prev-links.js') + css = { + "all": ("my_styles.css",) + } diff --git a/app/pages/build.py b/app/pages/build.py new file mode 100644 index 0000000..9545ce3 --- /dev/null +++ b/app/pages/build.py @@ -0,0 +1,25 @@ +from django.template.loader import render_to_string +from django.template import Context +from django.urls import reverse +from django.conf import settings + +from builder.base import BuildNew + + +class BuildPages(BuildNew): + + def build(self): + self.build_detail_view() + + def get_model_queryset(self): + return self.model.objects.filter(build=True,site=self.site) + + +class BuildHome(BuildNew): + + def get_model_queryset(self): + return self.model.objects.get(pk=1) + + def build(self): + response = self.client.get('/') + self.write_file('/', response.content) diff --git a/app/pages/migrations/0001_initial.py b/app/pages/migrations/0001_initial.py new file mode 100644 index 0000000..fe5ef96 --- /dev/null +++ b/app/pages/migrations/0001_initial.py @@ -0,0 +1,49 @@ +# Generated by Django 3.2.8 on 2021-10-06 20:30 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('sites', '0002_alter_domain_unique'), + ('media', '__first__'), + ('posts', '__first__'), + ] + + operations = [ + migrations.CreateModel( + name='Page', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200)), + ('sub_title', models.CharField(blank=True, max_length=300)), + ('slug', models.SlugField()), + ('body_html', models.TextField(blank=True)), + ('body_markdown', models.TextField()), + ('meta_description', models.CharField(blank=True, max_length=256)), + ('path', models.CharField(blank=True, max_length=200)), + ('app', models.CharField(blank=True, max_length=50)), + ('build', models.BooleanField(default=True)), + ('enable_comments', models.BooleanField(default=False)), + ('pub_date', models.DateTimeField(blank=True, null=True, verbose_name='Date published')), + ('featured_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='media.luximage')), + ('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.site')), + ], + ), + migrations.CreateModel( + name='HomePage', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('image_offset_vertical', models.CharField(help_text='add negative top margin to shift image (include css unit)', max_length=20)), + ('tag_line', models.CharField(blank=True, max_length=200, null=True)), + ('template_name', models.CharField(blank=True, help_text='full path', max_length=200, null=True)), + ('featured', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='banner', to='posts.post')), + ('featured_image', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='media.luximage')), + ('popular', models.ManyToManyField(related_name='popular', to='posts.Post')), + ], + ), + ] diff --git a/app/pages/migrations/0002_auto_20211031_1354.py b/app/pages/migrations/0002_auto_20211031_1354.py new file mode 100644 index 0000000..8fa8e90 --- /dev/null +++ b/app/pages/migrations/0002_auto_20211031_1354.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2.8 on 2021-10-31 13:54 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('posts', '0001_initial'), + ('pages', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='homepage', + name='featured', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='banner', to='posts.post'), + ), + migrations.AlterField( + model_name='homepage', + name='image_offset_vertical', + field=models.CharField(blank=True, help_text='add negative top margin to shift image (include css unit)', max_length=20, null=True), + ), + migrations.AlterField( + model_name='homepage', + name='popular', + field=models.ManyToManyField(blank=True, null=True, related_name='popular', to='posts.Post'), + ), + ] diff --git a/app/pages/migrations/__init__.py b/app/pages/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/pages/migrations/__init__.py diff --git a/app/pages/models.py b/app/pages/models.py new file mode 100644 index 0000000..7198d59 --- /dev/null +++ b/app/pages/models.py @@ -0,0 +1,61 @@ +import re +from django.db import models +from django.contrib.sitemaps import Sitemap +from django.contrib.sites.models import Site + +from media.models import LuxImage +from posts.models import Post +from utils.util import markdown_to_html, render_images + + +class Page(models.Model): + title = models.CharField(max_length=200) + sub_title = models.CharField(max_length=300, blank=True) + slug = models.SlugField() + body_html = models.TextField(blank=True) + body_markdown = models.TextField() + meta_description = models.CharField(max_length=256, blank=True) + path = models.CharField(max_length=200, blank=True) + app = models.CharField(max_length=50, blank=True) + build = models.BooleanField(default=True) + enable_comments = models.BooleanField(default=False) + site = models.ForeignKey(Site, on_delete=models.CASCADE) + featured_image = models.ForeignKey(LuxImage, on_delete=models.CASCADE, null=True, blank=True) + pub_date = models.DateTimeField('Date published', null=True, blank=True) + + def __str__(self): + return self.title + + def get_absolute_url(self): + if self.path: + return "/%s/%s" % (self.path, self.slug) + else: + return "/%s" % (self.slug) + + def save(self): + # run markdown + md = render_images(self.body_markdown) + self.body_html = markdown_to_html(md) + super(Page, self).save() + + +class HomePage(models.Model): + """ + simple model to control the featured article on the homepage + also allows me to fudge the "popular" section to be what I want + """ + image_offset_vertical = models.CharField(max_length=20, help_text="add negative top margin to shift image (include css unit)", blank=True, null=True) + featured_image = models.ForeignKey(LuxImage, on_delete=models.CASCADE) + tag_line = models.CharField(max_length=200, null=True, blank=True) + featured = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="banner", null=True, blank=True) + popular = models.ManyToManyField(Post, related_name="popular", null=True, blank=True) + template_name = models.CharField(max_length=200, help_text="full path", null=True, blank=True) + + +class PageSitemap(Sitemap): + changefreq = "never" + priority = 1.0 + protocol = "https" + + def items(self): + return Page.objects.filter(build=True) diff --git a/app/pages/static.py b/app/pages/static.py new file mode 100644 index 0000000..3ef1a71 --- /dev/null +++ b/app/pages/static.py @@ -0,0 +1,123 @@ +from django.db import models +from django.contrib.sitemaps import Sitemap +import markdown + + +import os +import yaml +from django.conf import settings +from django.template.loader import render_to_string +from django.template import Context +from mdx_attr_list.mdx_attr_list import AttrListExtension + + +def markdown_processor(txt): + md = markdown.Markdown( + extensions=[AttrListExtension(),'footnotes',], + output_format='html5', + safe_mode=False + ) + return md.convert(txt) +''' +class Page(models.Model): + title = models.CharField(max_length=200) + slug = models.SlugField() + body_html = models.TextField(blank=True) + body_markdown = models.TextField() + meta_description = models.CharField(max_length=256, null=True, blank=True) + + def __unicode__(self): + return self.title + + def get_absolute_url(self): + return "/%s/" % (self.slug) + + def save(self): + #run markdown + self.body_html = markdown_processor(self.body_markdown) + super(Page, self).save() +''' + +class PageSitemap(Sitemap): + changefreq = "never" + priority = 1.0 + protocol = "https" + + def items(self): + p = PageGenerator(settings.PROJ_ROOT + '_pages') + return p.objects(include_in_sitemap=True) + #return Page.objects.all() + + +class PageGenerator(object): + + def __init__(self, path, *args, **kwargs): + self._objects = [] + for (dirpath, dirnames, filenames) in os.walk(path): + self.dirpath = dirpath + self.file_list = filter(lambda item: not (item.startswith('.') or item.endswith('~') or item.endswith('.md')), filenames) + self.get_files() + + def get_files(self): + for f in self.file_list: + p = Page(self.dirpath + '/' + f) + self._objects.append(p) + + def objects(self, *args, **kwargs): + filtered_list = [] + if kwargs: + for item in self._objects: + found = False + for k, v in kwargs.items(): + if getattr(item, k) == v and not found: + found = True + filtered_list.append(item) + elif getattr(item, k) != v and found: + filtered_list.remove(item) + return filtered_list + return self._objects + + def write_files(self): + for obj in self.objects(): + c = Context({'object': obj, 'SITE_URL': settings.SITE_URL}) + t = render_to_string(["details/%s.html" % obj.template], c) + s = render_to_string('details/page.txt', c) + _FileWriter('', t, ext="html", filename=obj.slug) + _FileWriter('', s, ext="txt", filename=obj.slug) + + +class _FileWriter(object): + """ + Given a path and text object; write the page to disc + """ + def __init__(self, path, text_object, ext='html', filename='index'): + self.path = '%s%s' % (settings.FLATFILES_ROOT, path) + if not os.path.isdir(self.path): + os.makedirs(self.path) + fpath = '%s%s.%s' % (self.path, filename, ext) + self.write(fpath, text_object) + + def write(self, fpath, text_object): + file = open(fpath, 'wb') + file.write(text_object.encode('utf-8')) + file.close() + + +class _FileLoader(object): + + def __init__(self, filename, *args, **kwargs): + self.filename = filename + metadata = self.read() + for k, v in metadata.items(): + setattr(self, k, v) + if self.body_markdown: + self.body_html = markdown_processor(self.body_markdown) + + def read(self): + with open(self.filename, "r", encoding="utf-8") as f: + contents = f.read() + metayaml, self.body_markdown = contents.split('\n---') + return yaml.load(metayaml) + +class Page(_FileLoader): + pass diff --git a/app/pages/templates/pages/about.html b/app/pages/templates/pages/about.html new file mode 100644 index 0000000..a3eb9f9 --- /dev/null +++ b/app/pages/templates/pages/about.html @@ -0,0 +1,32 @@ +{% extends 'base.html' %} +{% load typogrify_tags %} +{% load comments %} +{% block pagetitle %}Cumulus Learning | {{object.title}}{% endblock %} +{% block metadescription %}{{object.meta_description}}{% endblock %} +{%block htmlclass%}class="detail"{%endblock%} +{%block bodyid%}id="{{object.title|slugify}}"{%endblock%} +{% block breadcrumbs %}{% include "lib/breadcrumbs.html" with breadcrumbs=breadcrumbs %}{% endblock %} +{% block primary %}<main> + <div class="post-article" style="margin-top: 4rem"> + {{object.body_html|safe|smartypants|widont}} + </div> + </article> + {% if object.enable_comments %} +{% get_comment_count for object as comment_count %} +{%if comment_count > 0 %} +<p class="comments--header">{{comment_count}} Comment{{ comment_count|pluralize }}</p> +{% render_comment_list for object %} +{%endif%} +<div class="comment--form--wrapper {%if comment_count > 0%}comment-form-border{%endif%}"> +{% render_comment_form for object %} +</div> +{%endif%} + </main> +{% endblock %} +{% block js %} +<script src="/media/js/lightbox.js" type="text/javascript"></script> +<script>var opts={captions:true,onload:function(){var im=document.getElementById("jslghtbx-contentwrapper");var link=im.appendChild(document.createElement('a')) +link.href=im.firstChild.src;link.innerHTML="open ";link.target="_blank";link.setAttribute('class','p-link');im.appendChild(link);}};var lightbox=new Lightbox();lightbox.load(opts);</script> +{% endblock %} + + diff --git a/app/pages/templates/pages/homepage.html b/app/pages/templates/pages/homepage.html new file mode 100644 index 0000000..537dd31 --- /dev/null +++ b/app/pages/templates/pages/homepage.html @@ -0,0 +1,32 @@ +{% extends 'base.html' %} +{% load typogrify_tags %} +{% block sitename %} +<head itemscope itemtype="http://schema.org/WebSite"> + <title itemprop='name'></title> + <link rel="canonical" href="https://tk.net/">{%endblock%} + {%block extrahead%} + {%endblock%} +{%block bodyid%}id="home"{%endblock%} + +{% block primary %} +<main> + <article class="content about"> + <div class="big-top"> + <h1>Hi, I'm Corrinne Gilbertson,</h1> + <p>a <a href="/what-is-structured-word-inquiry">literacy instructor</a>, <a href="">reading specialist</a>, & <a href="">linguist</a>. + <p>I provide <a href="">group classes</a> and <a href="">private tutoring</a> to help<br /> + struggling readers and spellers build a meaningful<br /> understanding of the English language.</p> + <p>Interested? Email me: <a href="mailto:corrinne@cumuluslearning.net">corrinne@cumuluslearning.net</a></p> + </div> + <div class="sign-form"> + <h2>Sign Up To Learn More</h2> + <p>Drop your email in the box to be notified about group classes</p> + <p><iframe target='_parent' style="border:none !important; background:white; width:100% !important; height: 300px;" title="embedded form for class notifications" src="{% url 'lttr:subscribe' 'classes' %}"></iframe></p> + <p>Head over to the <a href="/classes/">class list</a> for more info.</p> + </div> + <div class="post-article" style="margin-top: 7rem"> + {{object.body_html|safe|smartypants|widont}} + </div> + </article> + </main> +{% endblock %} diff --git a/app/pages/templates/pages/page_detail.html b/app/pages/templates/pages/page_detail.html new file mode 100644 index 0000000..8b1741f --- /dev/null +++ b/app/pages/templates/pages/page_detail.html @@ -0,0 +1,38 @@ +{% extends 'base.html' %} +{% load typogrify_tags %} +{% load comments %} +{% block pagetitle %}Cumulus Learning | {{object.title}}{% endblock %} +{% block metadescription %}{{object.meta_description}}{% endblock %} +{%block htmlclass%}class="detail"{%endblock%} +{%block bodyid%}id="{{object.title|slugify}}"{%endblock%} +{% block breadcrumbs %}{% include "lib/breadcrumbs.html" with breadcrumbs=breadcrumbs %}{% endblock %} +{% block primary %} +<main role="main" class="archive-wrapper"> + <article> + <header class="archive-intro"> + <h1 class="archive-hed">{{object.title}}</h1> + {%if object.sub_title %}<h2 class="post-subtitle">{{object.sub_title}}</h2>{%endif%} + </header> + <div class="post-article"> + {{object.body_html|safe|smartypants|widont}} + </div> + </article> + {% if object.enable_comments %} +{% get_comment_count for object as comment_count %} +{%if comment_count > 0 %} +<p class="comments--header">{{comment_count}} Comment{{ comment_count|pluralize }}</p> +{% render_comment_list for object %} +{%endif%} +<div class="comment--form--wrapper {%if comment_count > 0%}comment-form-border{%endif%}"> +{% render_comment_form for object %} +</div> +{%endif%} + </main> +{% endblock %} +{% block js %} +<script src="/media/js/lightbox.js" type="text/javascript"></script> +<script>var opts={captions:true,onload:function(){var im=document.getElementById("jslghtbx-contentwrapper");var link=im.appendChild(document.createElement('a')) +link.href=im.firstChild.src;link.innerHTML="open ";link.target="_blank";link.setAttribute('class','p-link');im.appendChild(link);}};var lightbox=new Lightbox();lightbox.load(opts);</script> +{% endblock %} + + diff --git a/app/pages/templates/pages/page_detail.txt b/app/pages/templates/pages/page_detail.txt new file mode 100644 index 0000000..547ce79 --- /dev/null +++ b/app/pages/templates/pages/page_detail.txt @@ -0,0 +1,8 @@ +{{object.title|safe}} +{% for letter in object.title %}={%endfor%} + + by Scott Gilbertson + <{{SITE_URL}}{{object.get_absolute_url}}> + {{object.pub_date|date:"l, d F Y"}} + +{{object.body_markdown|safe}} diff --git a/app/pages/urls.py b/app/pages/urls.py new file mode 100644 index 0000000..c40eee0 --- /dev/null +++ b/app/pages/urls.py @@ -0,0 +1,13 @@ +from django.urls import path, re_path + +from . import views + +app_name = "pages" + +urlpatterns = [ + path( + r'', + views.PageDetailView.as_view(), + name="detail" + ), +] diff --git a/app/pages/views.py b/app/pages/views.py new file mode 100644 index 0000000..7ea2439 --- /dev/null +++ b/app/pages/views.py @@ -0,0 +1,40 @@ +from utils.views import LuxDetailView +from django.views.generic import DetailView + +from posts.models import Post, PostType +from .models import Page, HomePage + + +class PageDetailView(LuxDetailView): + model = Page + + def get_template_names(self): + obj = self.get_object() + return ["pages/%s.html" % (obj.slug), 'pages/page_detail.html'] + + +class PageDetailTXTView(LuxDetailView): + model = Page + slug_field = "slug" + + def get_template_names(self): + obj = self.get_object() + return 'pages/%s/page_detail.txt'% obj.site.name + + +class HomePageList(DetailView): + """ + Return a main entry and list of Entries in reverse chronological order + """ + model = HomePage + + def get_template_names(self): + return ["pages/%s.html" % self.template_name, 'pages/homepage.html'] + + def get_context_data(self, **kwargs): + # Call the base implementation first to get a context + context = super(HomePageList, self).get_context_data(**kwargs) + context['object_list'] = Post.objects.filter(post_type=PostType.HOMEPAGE).filter(status__exact=1).order_by('-pub_date')[1:9] + return context + + |