From: David Kerkeslager Date: Thu, 13 Aug 2020 05:16:08 +0000 (-0400) Subject: Get a basic blog up and running X-Git-Url: https://code.kerkeslager.com/?p=styx.blog;a=commitdiff_plain;h=9422a20eca5d3d6f9c1b369744532808f5e9d893 Get a basic blog up and running --- diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/admin.py b/core/admin.py new file mode 100644 index 0000000..a225f61 --- /dev/null +++ b/core/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin + +from . import models + +class PostAdmin(admin.ModelAdmin): + list_display = ( + 'title', + 'publication_utc', + ) + +admin.site.register(models.Post, PostAdmin) diff --git a/core/apps.py b/core/apps.py new file mode 100644 index 0000000..26f78a8 --- /dev/null +++ b/core/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + name = 'core' diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..5fa67c6 --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 3.1 on 2020-08-13 04:09 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Post', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=256, null=True)), + ('slug', models.SlugField()), + ('publication_utc', models.DateTimeField(blank=True, null=True)), + ('body_markdown', models.TextField(null=True)), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/core/migrations/__init__.py b/core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/models.py b/core/models.py new file mode 100644 index 0000000..16fe65d --- /dev/null +++ b/core/models.py @@ -0,0 +1,37 @@ +import re + +from django.contrib.auth.models import User +from django.db import models +from django.utils.safestring import mark_safe + +import commonmark + +class Post(models.Model): + title = models.CharField(max_length=256, null=True) + slug = models.SlugField() + author = models.ForeignKey(User, on_delete=models.CASCADE) + publication_utc = models.DateTimeField(null=True, blank=True) + body_markdown = models.TextField(null=True) + + def __str__(self): + return self.title + + @property + def body_html(self): + return mark_safe(commonmark.commonmark(self.body_markdown)) + + @property + def teaser_html(self): + paragraphs = re.split(r'\n(\s*\n)+', self.body_markdown) + + if len(paragraphs) == 0: + return '' + + teaser_markdown = paragraphs[0] + + for p in paragraphs[1:]: + if len(teaser_markdown) > 512: + break + teaser_markdown += '\n\n' + p + + return mark_safe(commonmark.commonmark(teaser_markdown)) diff --git a/core/templates/core/base.html b/core/templates/core/base.html new file mode 100644 index 0000000..ba5ab1f --- /dev/null +++ b/core/templates/core/base.html @@ -0,0 +1,39 @@ + + + + + + + + {% block title %}{% endblock %} + + + + + +
+

Styx

+
+ + + +
+ {% block content %}{% endblock %} +
+ + + + diff --git a/core/templates/core/page_nav.html b/core/templates/core/page_nav.html new file mode 100644 index 0000000..c823dc7 --- /dev/null +++ b/core/templates/core/page_nav.html @@ -0,0 +1,17 @@ + diff --git a/core/templates/core/post_detail.html b/core/templates/core/post_detail.html new file mode 100644 index 0000000..a539a14 --- /dev/null +++ b/core/templates/core/post_detail.html @@ -0,0 +1,15 @@ +{% extends 'core/base.html' %} + +{% block content %} + +
+

{{ object.title }}

+ + + +
+ {{ object.body_html }} +
+
+ +{% endblock %} diff --git a/core/templates/core/post_list.html b/core/templates/core/post_list.html new file mode 100644 index 0000000..8d6561c --- /dev/null +++ b/core/templates/core/post_list.html @@ -0,0 +1,27 @@ +{% extends 'core/base.html' %} + +{% block content %} + +{% include 'core/page_nav.html' %} + +{% for post in page_obj %} +
+

+ + {{ post.title }} + +

+ +
+ {{ post.teaser_html }} +
+ + + Read more + +
+{% endfor %} + +{% include 'core/page_nav.html' %} + +{% endblock %} diff --git a/core/templates/core/style.css b/core/templates/core/style.css new file mode 100644 index 0000000..d208566 --- /dev/null +++ b/core/templates/core/style.css @@ -0,0 +1,114 @@ +html { height: 100%; font-family: "Lucida Grande", Verdana, Arial, sans-serif; +} + +h1, h2, h3, h4, h5, h6 { + margin: 0; + font-family: Palatino, serif; +} + +time { + font-family: Verdana, sans-serif; +} + +p { + line-height: 1.8; + margin-bottom: 1.2rem; +} + +p:last-child { + margin-bottom: 0; +} + +body { + display: flex; + flex-direction: column; + align-items: center; + + margin: 0; + min-height: 100%; +} + +body > header { + margin: 5rem 0; +} + +body > header > h1 { + font-size: 5rem; +} + +body > nav { + margin-bottom: 4rem; +} + +body > main { + width: calc(100% - 4rem); + max-width: 40rem; + min-height: 100%; +} + +body > footer { + display: flex; + flex-direction: column; + align-items: center; + + margin: 5rem 0; +} + +body > footer .licenses { + display: flex; + flex-direction: row; + align-items: center; + margin-top: 1rem; +} + +body > footer .licenses > div { + display: flex; + flex-direction: column; + align-items: flex-start; + + margin-left: 1rem; +} + +body > footer .licenses > div > span:not(:first-child) { + margin-top: 1rem; +} + +article { + display: flex; + flex-direction: column; + + margin: 2.5rem 0; +} + +article h1 a, +article h1 a:visited { + text-decoration: none; +} + +article h1 a:hover, +article h1 a:active{ + text-decoration: underline; +} + +article h1 { + font-size: 2rem; +} + +article a.read-more { + margin-top: 1rem; +} + +@media (prefers-color-scheme: dark) { + html { + color: #dfdede; + background: #121212; + } + + a { + color: #dfdede; + } + + article.teaser:not(:first-of-type) { + border-top: 1px solid #dfdddd; + } +} diff --git a/core/tests.py b/core/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/core/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/core/urls.py b/core/urls.py new file mode 100644 index 0000000..4addd71 --- /dev/null +++ b/core/urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path('', views.PostListView.as_view(), name='post-list'), + path('p//', views.PostDetailView.as_view(), name='post-detail'), +] diff --git a/core/views.py b/core/views.py new file mode 100644 index 0000000..168fa3e --- /dev/null +++ b/core/views.py @@ -0,0 +1,21 @@ +import datetime + +from django.contrib.auth.models import User +from django.views.generic.detail import DetailView +from django.views.generic.list import ListView + +from . import models + +class PostDetailView(DetailView): + model = models.Post + +class PostListView(ListView): + model = models.Post + paginate_by = 10 + + def get_queryset(self): + now = datetime.datetime.utcnow() + + return super().get_queryset().filter( + publication_utc__lte=now, + ).order_by('-publication_utc') diff --git a/requirements.txt b/requirements.txt index 4aba371..3a001d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ Django==3.1 +commonmark==0.9.1 diff --git a/styx/settings.py b/styx/settings.py index 6b58f2a..f57fcf7 100644 --- a/styx/settings.py +++ b/styx/settings.py @@ -52,6 +52,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'core', ] MIDDLEWARE = [ diff --git a/styx/urls.py b/styx/urls.py index c1dbf92..94c89d9 100644 --- a/styx/urls.py +++ b/styx/urls.py @@ -14,11 +14,11 @@ Including another URLconf 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.urls import include, path -from . import views +from core import urls as core_urls urlpatterns = [ path('admin/', admin.site.urls), - path('', views.index), + path('', include(core_urls)), ] diff --git a/styx/views.py b/styx/views.py deleted file mode 100644 index 453e180..0000000 --- a/styx/views.py +++ /dev/null @@ -1,4 +0,0 @@ -from django.http import HttpResponse - -def index(request): - return HttpResponse('Hello, world')