summaryrefslogtreecommitdiff
path: root/app/pages
diff options
context:
space:
mode:
Diffstat (limited to 'app/pages')
-rw-r--r--app/pages/__init__.py0
-rw-r--r--app/pages/admin.py55
-rw-r--r--app/pages/build.py25
-rw-r--r--app/pages/migrations/0001_initial.py49
-rw-r--r--app/pages/migrations/0002_auto_20211031_1354.py30
-rw-r--r--app/pages/migrations/__init__.py0
-rw-r--r--app/pages/models.py61
-rw-r--r--app/pages/static.py123
-rw-r--r--app/pages/templates/pages/about.html32
-rw-r--r--app/pages/templates/pages/homepage.html32
-rw-r--r--app/pages/templates/pages/page_detail.html38
-rw-r--r--app/pages/templates/pages/page_detail.txt8
-rw-r--r--app/pages/urls.py13
-rw-r--r--app/pages/views.py40
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>, &amp; <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
+
+