lots more stuff

This commit is contained in:
Christian Lawson-Perfect 2024-04-26 08:18:18 +00:00
parent 46c9f0a447
commit c4f250573a
19 changed files with 7619 additions and 149 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
__pycache__
db.sqlite3
gunicorn.conf.py
public/
secret_key.txt
think_data/

View file

@ -23,7 +23,10 @@ class RemixThinkForm(forms.ModelForm):
class RenameThinkForm(forms.ModelForm): class RenameThinkForm(forms.ModelForm):
class Meta: class Meta:
model = Think model = Think
fields = ['slug'] fields = ['slug', 'category', 'is_template']
widgets = {
'category': forms.TextInput(attrs={'list': 'categories'})
}
def __init__(self, *args, instance=None, **kwargs): def __init__(self, *args, instance=None, **kwargs):
self.original_root = None if instance is None else instance.root self.original_root = None if instance is None else instance.root

View file

@ -104,6 +104,7 @@ class MakeHandler(FileSystemEventHandler):
command = ['make'] + config.get('default_make', []) command = ['make'] + config.get('default_make', [])
res = subprocess.run(command, cwd=d, capture_output=True, encoding='utf-8') res = subprocess.run(command, cwd=d, capture_output=True, encoding='utf-8')
with open(d / '.make.log', 'w') as f: with open(d / '.make.log', 'w') as f:
f.write(f"{datetime.now()}\n")
f.write(res.stdout) f.write(res.stdout)
f.write(res.stderr) f.write(res.stderr)
else: else:

View file

@ -0,0 +1,22 @@
# Generated by Django 5.0.3 on 2024-03-05 19:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('thinks', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='think',
options={'ordering': ('slug',)},
),
migrations.AddField(
model_name='think',
name='is_template',
field=models.BooleanField(default=False),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 5.0.3 on 2024-04-26 07:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('thinks', '0002_alter_think_options_think_is_template'),
]
operations = [
migrations.AddField(
model_name='think',
name='category',
field=models.CharField(blank=True, max_length=100, null=True),
),
]

View file

@ -1,6 +1,7 @@
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.timezone import datetime, make_aware
THINKS_DIR = settings.THINKS_DIR THINKS_DIR = settings.THINKS_DIR
@ -8,6 +9,9 @@ THINKS_DIR = settings.THINKS_DIR
class Think(models.Model): class Think(models.Model):
slug = models.SlugField() slug = models.SlugField()
category = models.CharField(max_length=100, blank=True, null=True)
is_template = models.BooleanField(default=False)
class Meta: class Meta:
ordering = ('slug',) ordering = ('slug',)
@ -43,3 +47,17 @@ class Think(models.Model):
return f.read() return f.read()
return None return None
def get_log(self):
log_file = self.file_path('.make.log')
if not log_file.exists():
return ''
with open(log_file) as f:
log = f.read()
return log
@property
def creation_time(self):
return make_aware(datetime.fromtimestamp(self.root.stat().st_ctime))

View file

@ -0,0 +1,7 @@
import './code-editor.mjs';
export default async function init_app() {
const flags = JSON.parse(document.getElementById('think-editor-data').textContent);
flags.csrf_token = document.getElementById('csrftoken')?.textContent || '';
const app = Elm.App.init({node: document.body, flags});
}

View file

@ -0,0 +1,201 @@
:root {
--spacing: 1em;
--half-spacing: calc(0.5 * var(--spacing));
--double-spacing: calc(2 * var(--spacing));
--editor-size: 50%;
}
* {
box-sizing: border-box;
}
body {
font-family: sans-serif;
display: grid;
grid-template-rows: auto 1fr;
min-height: 100vh;
margin: 0;
padding: var(--half-spacing);
& > header {
padding: var(--spacing);
& h1 {
margin: 0;
}
}
}
header {
& #think-controls {
display: flex;
gap: var(--spacing);
margin: 0;
}
}
#editor-size-input + output {
width: 5em;
}
.file-path {
font-family: monospace;
}
.think-editor {
display: flex;
gap: var(--spacing);
height: 100%;
& > #main-nav > nav {
display: flex;
flex-direction: column;
gap: var(--spacing);
& #file-tree {
margin: 0;
overflow: auto;
flex-grow: 1;
& > li {
margin-top: var(--half-spacing);
& > a {
text-decoration: none;
}
}
& .dir {
font-weight: bold;
}
& .dir + .file {
margin-top: var(--spacing);
}
}
& form {
display: grid;
grid-template-columns: 1fr 6em;
align-content: start;
align-items: start;
}
& #make-log {
& > pre {
max-width: 20em;
overflow: auto;
}
}
}
& #log[open] {
width: 80ch;
overflow: auto;
}
& #editor {
overflow: hidden;
flex-grow: 1;
flex-basis: var(--editor-size);
max-height: 85vh;
& #editor-controls {
display: flex;
gap: var(--spacing);
justify-content: space-between;
& > details {
text-align: right;
& > summary {
user-select: none;
}
& button {
margin: var(--half-spacing) 0;
}
}
}
& #code-editor {
display: block;
max-width: 50vw;
padding-bottom: 10em;
}
}
& #preview {
display: flex;
flex-direction: column;
&[open] {
flex-grow: 1;
flex-shrink: 1;
flex-basis: calc(100% - var(--editor-size));
}
& > summary {
text-align: right;
}
& > iframe {
width: 100%;
height: 100%;
border: none;
}
&[closed] > iframe {
display: none;
}
}
overflow: hidden;
}
#file-form {
overflow: auto;
max-height: 100%;
max-width: 100%;
}
@media (max-width: 100ch) {
html {
font-size: min(3vw, 16px);
}
.think-editor {
flex-direction: column;
overflow: visible;
& > * ~ * {
border-top: medium solid #888;
margin-top: var(--spacing);
padding-top: var(--spacing);
}
& nav {
flex-direction: row;
flex-wrap: wrap;
& form {
flex-grow: 1;
}
& #file-tree {
max-height: 7em;
}
}
& #editor {
overflow: auto;
& #code-editor {
max-width: none;
}
}
& #preview {
height: 100vh;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,31 +1,64 @@
:root { :root {
--spacing: 1em; --spacing: 1em;
--half-spacing: calc(0.5 * var(--spacing)); --half-spacing: calc(0.5 * var(--spacing));
--double-spacing: calc(2 * var(--spacing));
} }
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
body > header { body {
padding: var(--spacing); font-family: sans-serif;
& h1 { & > header {
margin: 0; padding: var(--spacing);
& h1 {
margin: 0;
}
} }
} }
body.index { body.index {
& #thinks-list { font-size: 20px;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: var(--spacing);
& > .think { & main {
& .readme { padding: 0 var(--spacing);
max-width: 80ch;
white-space: pre-wrap; & #templates-list {
list-style: none;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: var(--double-spacing);
}
& #thinks-list {
display: flex;
flex-direction: column;
gap: var(--double-spacing);
list-style: none;
padding: 0;
& details {
&[open] > summary {
margin-bottom: var(--spacing);
}
& ul {
display: flex;
flex-direction: column;
gap: var(--spacing);
}
}
& .think {
& time {
font-size: smaller;
}
& .readme {
max-width: 80ch;
white-space: pre-wrap;
}
} }
} }
} }
@ -57,6 +90,19 @@ body.thing-editor {
& #file-tree { & #file-tree {
margin: 0; margin: 0;
overflow: auto; overflow: auto;
flex-grow: 1;
& > li {
margin-top: var(--half-spacing);
}
& .dir {
font-weight: bold;
}
& .dir + .file {
margin-top: var(--spacing);
}
} }
& form { & form {
@ -65,6 +111,13 @@ body.thing-editor {
align-content: start; align-content: start;
align-items: start; align-items: start;
} }
& #make-log {
& > pre {
max-width: 20em;
overflow: auto;
}
}
} }
& #editor { & #editor {
@ -89,7 +142,9 @@ body.thing-editor {
& #code-editor { & #code-editor {
display: block; display: block;
max-width: 50vw; max-width: 50vw;
padding-bottom: 10em;
} }
overflow: hidden;
} }
& #preview { & #preview {
@ -101,12 +156,13 @@ body.thing-editor {
border: none; border: none;
} }
} }
overflow: hidden;
} }
} }
#file-form { #file-form {
overflow: auto; overflow: auto;
/*! max-height: 100%; */ max-height: 100%;
max-width: 100%; max-width: 100%;
} }
@media (max-width: 100ch) { @media (max-width: 100ch) {
@ -116,17 +172,33 @@ body.thing-editor {
body.thing-editor { body.thing-editor {
& main { & main {
grid-template: grid-template:
"nav" min-content "editor" 40vh "preview"; "nav" min-content "editor" 40vh "preview"
;
overflow: visible;
& nav { & nav {
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
& form { & form {
flex-grow: 1; flex-grow: 1;
} }
& #file-tree {
max-height: 7em;
}
}
& #editor {
overflow: auto;
& #code-editor {
max-width: none;
}
}
& #preview {
height: 100vh;
} }
} }
} }
} }

View file

@ -0,0 +1,31 @@
{% extends "thinks/base.html" %}
{% block body_class %}login {{block.super}}{% endblock %}
{% block header %}
<h1>Login</h1>
{% endblock %}
{% block main %}
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
{% if next %}
{% if user.is_authenticated %}
<p>Your account doesn't have access to this page. To proceed,
please login with an account that has access.</p>
{% else %}
<p>Please login to see this page.</p>
{% endif %}
{% endif %}
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
{{form}}
<button type="submit">Log in</button>
<input type="hidden" name="next" value="{{ next }}">
</form>
{% endblock %}

View file

@ -8,7 +8,7 @@
<title>{% block title %}Thinks{% endblock %}</title> <title>{% block title %}Thinks{% endblock %}</title>
{% block stylesheets %} {% block stylesheets %}
<link rel="stylesheet" href="{% static "thinks/thinks.css" %}"> <link rel="stylesheet" href="{% static "thinks/thinks.css" %}?{% now "U" %}">
{% endblock stylesheets %} {% endblock stylesheets %}
{% block scripts %} {% block scripts %}

View file

@ -7,18 +7,45 @@
{% endblock header %} {% endblock header %}
{% block main %} {% block main %}
<ul id="thinks-list"> <section id="templates">
{% for think in object_list %} <h2>Templates</h2>
<li class="think"> <ul id="templates-list">
<a href="{% url 'think' think.slug %}">{{think.slug}}</a> {% for think in templates %}
{% with readme=think.get_readme %} <li class="think">
{% if readme %} <a class="remix" href="{% url 'remix_think' think.slug %}">{{think.slug}}</a>
<pre class="readme">{{readme}} <br>
</pre> <small>(<a href="{% url 'think' think.slug %}">edit</a>)</small>
{% endif %} </li>
{% endwith %} {% endfor %}
</li> </ul>
{% endfor %} </section>
</ul>
<a href="{% url 'new_think' %}">New think</a> <section id="thinks">
<h2>Thinks</h2>
<p><a href="{% url 'new_think' %}">New think</a></p>
{% regroup thinks by category as categories %}
<ul id="thinks-list">
{% for category in categories %}
<li>
<details {% if not category.grouper %}open{% endif %}>
<summary>{% if category.grouper %}{{category.grouper}}{% else %}Uncategorised{% endif %}</summary>
<ul>
{% for think in category.list %}
<li class="think">
<a href="{% url 'think' think.slug %}">{{think.slug}}</a>
<time datetime="{{think.creation_time|date:"c"}}">{{think.creation_time}}</time>
{% with readme=think.get_readme %}
{% if readme %}
<pre class="readme">{{readme|safe}}</pre>
{% endif %}
{% endwith %}
</li>
{% endfor %}
</ul>
</details>
</li>
{% endfor %}
</ul>
</dl>
</section>
{% endblock main %} {% endblock main %}

View file

@ -0,0 +1,30 @@
{% extends "thinks/base.html" %}
{% load static %}
{% block title %}{{path}} - {{think.slug}} - {{block.super}}{% endblock title %}
{% block body_class %}thing-editor {{block.super}}{% endblock %}
{% block scripts %}
<script src="{% static "thinks/think-editor.js" %}"></script>
{{think_editor_data|json_script:"think-editor-data"}}
<script id="csrftoken" type="text/plain">{{csrf_token}}</script>
<script type="module"">
import init_app from "{% static "thinks/load-think-editor.mjs" %}";
init_app();
</script>
{% endblock scripts %}
{% block stylesheets %}
<link rel="stylesheet" href="{% static "thinks/think-editor.css" %}?{% now "U" %}">
{% endblock stylesheets %}
{% block header %}
<a href="/">thinks</a>
<h1>{{think.slug}}</h1>
{% endblock header %}
{% block main %}
{% endblock main %}

View file

@ -8,6 +8,11 @@
<form method="post" > <form method="post" >
{{form}} {{form}}
<datalist id="categories">
{% for category in categories %}
<option value="{{category}}"></option>
{% endfor %}
</datalist>
{% csrf_token %} {% csrf_token %}
<button type="submit">Rename</button> <button type="submit">Rename</button>
</form> </form>

View file

@ -1,106 +1,30 @@
{% extends "thinks/base.html" %} {% extends "thinks/base.html" %}
{% load static %}
{% block title %}{{path}} - {{think.slug}} - {{block.super}}{% endblock title %}
{% block body_class %}thing-editor {{block.super}}{% endblock %} {% block body_class %}thing-editor {{block.super}}{% endblock %}
{% block scripts %}
<script src="{% static "thinks/think-editor.js" %}"></script>
{{think_editor_data|json_script:"think-editor-data"}}
<script id="csrftoken" type="text/plain">{{csrf_token}}</script>
<script type="module"">
import init_app from "{% static "thinks/load-think-editor.mjs" %}";
init_app();
</script>
{% endblock scripts %}
{% block stylesheets %}
<link rel="stylesheet" href="{% static "thinks/think-editor.css" %}?{% now "U" %}">
{% endblock stylesheets %}
{% block header %} {% block header %}
<a href="/">thinks</a> <a href="/">thinks</a>
<h1>{{think.slug}}</h1> <h1>{{think.slug}}</h1>
<a href="{% url 'rename_think' slug=think.slug %}">Rename</a>
<a href="{% url 'delete_think' slug=think.slug %}">Delete</a>
<a href="{% url 'remix_think' slug=think.slug %}">Remix</a>
{% endblock header %} {% endblock header %}
{% block main %} {% block main %}
<nav>
<a target="preview" href="{{think.get_static_url}}">Preview</a>
<ul id="file-tree">
{% for name, path in files %}
<li><a href="?path={{path}}">{{name}}</a></li>
{% endfor %}
</ul>
<form id="new-file-form" method="post" action="{% url 'save_file' slug=think.slug %}" enctype="multipart/form-data">
<input aria-labelledby="new-file-button" id="new_file_path" type="text" name="path">
<button id="new-file-button" type="submit">New file</button>
{% csrf_token %}
</form>
<form method="post" action="{% url 'run_command' slug=think.slug %}">
<input aria-labelledby="run-command-button" name="command">
{% csrf_token %}
<button id="run-command-button" type="submit">Run</button>
</form>
</nav>
<section id="editor">
{% if path is not None and not path.is_dir %}
<nav id="editor-controls">
{{path}}
<details>
<summary>actions</summary>
<form method="post" action="{% url 'delete_file' slug=think.slug %}">
{% csrf_token %}
<input type="hidden" name="path" value="{{path}}">
<button type="submit">Delete</button>
</form>
<form method="post" action="{% url 'rename_file' slug=think.slug %}">
{% csrf_token %}
<input type="hidden" name="path" value="{{path}}">
<input type="text" name="newpath" value="{{path}}">
<button type="submit">Rename</button>
</form>
</details>
</nav>
<form id="file-form" method="post" action="{% url 'save_file' slug=think.slug %}" enctype="multipart/form-data">
{{file_form.path.as_hidden}}
{{file_form.content.as_hidden}}
<code-editor id="code-editor">{{content}}</code-editor>
{% csrf_token %}
</form>
{% endif %}
</section>
<section id="preview">
<button type="button" id="reload-preview">Reload</button>
<iframe id="preview-frame" src="{{think.get_static_url}}"></iframe>
</section>
<script>
const file_form = document.getElementById('file-form');
const preview_frame = document.getElementById('preview-frame');
const content_input = document.getElementById('code-editor');
function debounce(fn) {
let last = null;
return function() {
if(last) {
clearTimeout(last);
}
last = setTimeout(fn, 2000);
}
}
function reload_preview() {
preview_frame.src = preview_frame.src;
}
if(content_input) {
const save_content = debounce(async() => {
const value = content_input.value;
console.log('save', value);
file_form.querySelector('[name="content"]').value = value;
await fetch(file_form.action, {method: 'POST', body: new FormData(file_form)});
reload_preview();
});
content_input.addEventListener('change', save_content);
}
console.log('hey');
document.getElementById('reload-preview').addEventListener('click', () => {
reload_preview();
})
</script>
{% endblock main %} {% endblock main %}

View file

@ -4,7 +4,7 @@ from .views import *
urlpatterns = [ urlpatterns = [
path('', IndexView.as_view(), name='index'), path('', IndexView.as_view(), name='index'),
path('think/<slug:slug>', ThinkView.as_view(), name='think'), path('think/<slug:slug>/edit', ThinkView.as_view(), name='think'),
path('think/<slug:slug>/rename', RenameThinkView.as_view(), name='rename_think'), path('think/<slug:slug>/rename', RenameThinkView.as_view(), name='rename_think'),
path('think/<slug:slug>/delete', DeleteThinkView.as_view(), name='delete_think'), path('think/<slug:slug>/delete', DeleteThinkView.as_view(), name='delete_think'),
path('think/<slug:slug>/file/<path:path>', ReadFileView.as_view(), name='read_file'), path('think/<slug:slug>/file/<path:path>', ReadFileView.as_view(), name='read_file'),
@ -12,6 +12,7 @@ urlpatterns = [
path('think/<slug:slug>/rename-file', RenameFileView.as_view(), name='rename_file'), path('think/<slug:slug>/rename-file', RenameFileView.as_view(), name='rename_file'),
path('think/<slug:slug>/delete-file', DeleteFileView.as_view(), name='delete_file'), path('think/<slug:slug>/delete-file', DeleteFileView.as_view(), name='delete_file'),
path('think/<slug:slug>/run-command', RunCommandView.as_view(), name='run_command'), path('think/<slug:slug>/run-command', RunCommandView.as_view(), name='run_command'),
path('think/<slug:slug>/log', LogView.as_view(), name='log'),
path('new', CreateThinkView.as_view(), name='new_think'), path('new', CreateThinkView.as_view(), name='new_think'),
path('new/<slug:slug>', RemixThinkView.as_view(), name='remix_think'), path('new/<slug:slug>', RemixThinkView.as_view(), name='remix_think'),
] ]

View file

@ -4,8 +4,10 @@ from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.views import generic from django.views import generic
from django.urls import reverse from django.urls import reverse
from itertools import groupby
from pathlib import Path from pathlib import Path
import shutil import shutil
import shlex
import subprocess import subprocess
from . import forms from . import forms
@ -19,6 +21,15 @@ class ThinkMixin(LoginRequiredMixin):
class IndexView(ThinkMixin, generic.ListView): class IndexView(ThinkMixin, generic.ListView):
template_name = 'thinks/index.html' template_name = 'thinks/index.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['templates'] = Think.objects.filter(is_template=True)
context['thinks'] = sorted(Think.objects.filter(is_template=False), key=lambda t: (t.category if t.category else '', -t.creation_time.timestamp()))
return context
class CreateThinkView(ThinkMixin, generic.CreateView): class CreateThinkView(ThinkMixin, generic.CreateView):
template_name = 'thinks/new.html' template_name = 'thinks/new.html'
form_class = forms.CreateThinkForm form_class = forms.CreateThinkForm
@ -49,34 +60,31 @@ class RemixThinkView(ThinkMixin, generic.UpdateView):
class ThinkView(ThinkMixin, generic.DetailView): class ThinkView(ThinkMixin, generic.DetailView):
template_name = 'thinks/think.html' template_name = "thinks/think.html"
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs) context = super().get_context_data(*args, **kwargs)
think = self.object think = self.object
root = think.root
strpath = self.request.GET.get('path') strpath = self.request.GET.get('path')
path = think.file_path(strpath) path = think.file_path(strpath)
relpath = path.relative_to(think.root) if path is not None else None relpath = path.relative_to(root) if path is not None else None
if path is None: if path is None:
directory = think.root directory = root
elif path.is_dir(): elif path.is_dir():
directory = path directory = path
else: else:
directory = path.parent directory = path.parent
files = [(p.name, p.relative_to(think.root)) for p in directory.iterdir()] files = [{'name': p.name, 'path': str(p.relative_to(root)), 'is_dir': p.is_dir()} for p in directory.iterdir()]
if directory != think.root: if directory != root:
files.insert(0, ('..', directory.parent.relative_to(think.root))) files.insert(0, {'name': '..', 'path': str(directory.parent.relative_to(root)), 'is_dir': True})
context['files'] = files
context['directory'] = directory
context['path'] = relpath
if path is not None and path.is_file(): if path is not None and path.is_file():
with open(path) as f: with open(path) as f:
@ -84,9 +92,15 @@ class ThinkView(ThinkMixin, generic.DetailView):
else: else:
content = '' content = ''
context['content'] = content context['think_editor_data'] = {
'preview_url': think.get_static_url(),
context['file_form'] = forms.SaveFileForm(instance=think, initial={'content': content, 'path': relpath}) 'slug': think.slug,
'files': files,
'file_path': str(relpath),
'file_content': content,
'is_dir': path is None or path.is_dir(),
'no_preview': self.request.GET.get('no-preview') is not None,
}
return context return context
@ -97,6 +111,13 @@ class RenameThinkView(ThinkMixin, generic.UpdateView):
def get_success_url(self): def get_success_url(self):
return self.object.get_absolute_url() return self.object.get_absolute_url()
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['categories'] = sorted(Think.objects.exclude(category=None).order_by('category').values_list('category',flat=True).distinct())
return context
class DeleteThinkView(ThinkMixin, generic.DeleteView): class DeleteThinkView(ThinkMixin, generic.DeleteView):
template_name = 'thinks/delete.html' template_name = 'thinks/delete.html'
@ -152,9 +173,17 @@ class RunCommandView(ThinkMixin, generic.UpdateView):
think = self.object think = self.object
command = form.cleaned_data['command'] command = form.cleaned_data['command']
res = subprocess.run( res = subprocess.run(
command.split(' '), ['bash','-c',command],
cwd=think.root, cwd=think.root,
encoding='utf8', encoding='utf8',
capture_output=True capture_output=True
) )
return JsonResponse({'stdout': res.stdout, 'stderr': res.stderr}) return JsonResponse({'stdout': res.stdout, 'stderr': res.stderr})
class LogView(ThinkMixin, generic.DetailView):
template_name = 'thinks/think.html'
def get(self, *args, **kwargs):
think = self.get_object()
return HttpResponse(think.get_log(), content_type='text/plain; charset=utf-8')

View file

@ -20,7 +20,8 @@ BASE_DIR = Path(__file__).resolve().parent.parent
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-s8dq=40yx#rhlq@j$#i3^naz9&yx6tut#ikr80uu!r(1ze@3#@' with open('/srv/think.somethingorotherwhatever.com/secret_key.txt') as f:
SECRET_KEY = f.read()
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
@ -127,4 +128,4 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
THINKS_DIR = Path('think_data') THINKS_DIR = Path('think_data')
THINKS_DIR.mkdir(parents=True, exist_ok=True) THINKS_DIR.mkdir(parents=True, exist_ok=True)
THINKS_STATIC_URL = 'http://{slug}.think.somethingorotherwhatever.com' THINKS_STATIC_URL = 'https://{slug}.think.somethingorotherwhatever.com'