lots of changes

Added a creation_time field to thinks - using the modified time on the
  filesystem wasn't reliable.

Styled the login page.

The index shows the most recent thinks at the top.

Allow authentication by an Authorization header, with a token specified
in settings.py.

Lots of improvements to the editor, including showing the log, and a
form to install Elm packages when editing .elm files.

The Makefile for a project is automatically run each time a file is saved.
This commit is contained in:
Christian Lawson-Perfect 2025-02-07 07:02:35 +00:00
parent 3d5c0c6c73
commit 500eb38774
13 changed files with 1474 additions and 396 deletions

View file

@ -1,19 +1,30 @@
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.mixins import AccessMixin
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.views import generic
from django.urls import reverse
from itertools import groupby
import json
from pathlib import Path
import shutil
import shlex
import subprocess
from . import forms
from .make import ThingMaker
from .models import Think
from .random_slug import random_slug
class LoginRequiredMixin(AccessMixin):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
token = request.META.get('HTTP_AUTHORIZATION')
if token != f'Bearer {settings.API_TOKEN}':
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
class ThinkMixin(LoginRequiredMixin):
model = Think
context_object_name = 'think'
@ -26,10 +37,19 @@ class IndexView(ThinkMixin, generic.ListView):
context['templates'] = Think.objects.filter(is_template=True)
context['recent_thinks'] = Think.objects.filter(is_template=False).order_by('-creation_time')[:3]
context['thinks'] = sorted(Think.objects.filter(is_template=False), key=lambda t: (t.category if t.category else '', -t.creation_time.timestamp()))
return context
def get(self, request, *args, **kwargs):
if request.accepts('application/json') and not request.accepts('text/html'):
self.object_list = self.get_queryset()
context = self.get_context_data()
return JsonResponse({'templates': [t.as_json() for t in context['templates']], 'thinks': [t.as_json() for t in context['thinks']]})
return super().get(request, *args, **kwargs)
class CreateThinkView(ThinkMixin, generic.CreateView):
template_name = 'thinks/new.html'
form_class = forms.CreateThinkForm
@ -82,17 +102,23 @@ class ThinkView(ThinkMixin, generic.DetailView):
else:
directory = path.parent
files = [{'name': p.name, 'path': str(p.relative_to(root)), 'is_dir': p.is_dir()} for p in directory.iterdir()]
if directory.exists():
files = [{'name': p.name, 'path': str(p.relative_to(root)), 'is_dir': p.is_dir()} for p in directory.iterdir()]
else:
files = []
if directory != root:
files.insert(0, {'name': '..', 'path': str(directory.parent.relative_to(root)), 'is_dir': True})
files = sorted(files, key=lambda x: x['name'].lower())
if path is not None and path.is_file():
with open(path) as f:
content = f.read()
else:
content = ''
context['think_editor_data'] = {
data = context['think_editor_data'] = {
'preview_url': think.get_static_url(),
'slug': think.slug,
'files': files,
@ -102,6 +128,10 @@ class ThinkView(ThinkMixin, generic.DetailView):
'no_preview': self.request.GET.get('no-preview') is not None,
}
if path is not None and path.suffix == '.elm':
with open('public/elm-packages.json') as f:
data['elm_packages'] = json.load(f)
return context
class RenameThinkView(ThinkMixin, generic.UpdateView):
@ -138,7 +168,13 @@ class SaveFileView(ThinkMixin, generic.UpdateView):
def form_valid(self, form):
self.path = form.cleaned_data['path'].relative_to(self.object.root)
return super().form_valid(form)
thing = form.save()
maker = ThingMaker(thing)
result = maker.make(self.path)
return JsonResponse(result or {"error": "not built"})
def get_success_url(self):
return self.object.get_absolute_url()+'?path='+str(self.path)