add a URL to upload files

This commit is contained in:
Christian Lawson-Perfect 2025-03-18 15:52:59 +00:00
parent d061110d88
commit dcdb3d87be
3 changed files with 91 additions and 8 deletions

View file

@ -3,6 +3,7 @@ from django.conf import settings
from .models import Think
class CreateThinkForm(forms.ModelForm):
class Meta:
model = Think
@ -15,11 +16,13 @@ class CreateThinkForm(forms.ModelForm):
instance.root.mkdir(exist_ok=True,parents=True)
return instance
class RemixThinkForm(forms.ModelForm):
class Meta:
model = Think
fields = []
class RenameThinkForm(forms.ModelForm):
class Meta:
model = Think
@ -49,6 +52,7 @@ class RenameThinkForm(forms.ModelForm):
instance = super().save(commit)
return instance
class SaveFileForm(forms.ModelForm):
path = forms.CharField()
content = forms.CharField(required=False, widget=forms.Textarea)
@ -72,6 +76,7 @@ class SaveFileForm(forms.ModelForm):
return super().save(commit)
class RenameFileForm(forms.ModelForm):
path = forms.CharField()
newpath = forms.CharField()
@ -100,6 +105,7 @@ class RenameFileForm(forms.ModelForm):
return super().save(commit)
class DeleteFileForm(forms.ModelForm):
path = forms.CharField()
@ -121,6 +127,7 @@ class DeleteFileForm(forms.ModelForm):
return super().save(commit)
class RunCommandForm(forms.ModelForm):
command = forms.CharField()
@ -128,9 +135,51 @@ class RunCommandForm(forms.ModelForm):
model = Think
fields = []
class GitCommitForm(forms.ModelForm):
message = forms.CharField()
class Meta:
model = Think
fields = []
class MultipleFileInput(forms.ClearableFileInput):
allow_multiple_selected = True
class MultipleFileField(forms.FileField):
def __init__(self, *args, **kwargs):
kwargs.setdefault("widget", MultipleFileInput())
super().__init__(*args, **kwargs)
def clean(self, data, initial=None):
single_file_clean = super().clean
if isinstance(data, (list, tuple)):
result = [single_file_clean(d, initial) for d in data]
else:
result = [single_file_clean(data, initial)]
return result
class UploadFileForm(forms.ModelForm):
files = MultipleFileField()
class Meta:
model = Think
fields = []
def save(self, commit=False):
think = self.instance
files = self.cleaned_data['files']
for uf in files:
path = think.file_path(uf.name)
print(uf, path)
with open(path, 'wb') as df:
for chunk in uf.chunks():
df.write(chunk)
return super().save(commit)

View file

@ -12,6 +12,7 @@ urlpatterns = [
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>/run-command', RunCommandView.as_view(), name='run_command'),
path('think/<slug:slug>/upload-files', UploadFileView.as_view(), name='upload_files'),
path('think/<slug:slug>/log', LogView.as_view(), name='log'),
path('think/<slug:slug>/jj/status', JJStatusView.as_view(), name='jj_status'),
path('think/<slug:slug>/jj/commit', JJCommitView.as_view(), name='jj_commit'),

View file

@ -6,6 +6,7 @@ from django.views import generic
from django.urls import reverse
from itertools import groupby
import json
import mimetypes
from pathlib import Path
import shutil
import shlex
@ -16,6 +17,7 @@ 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:
@ -25,10 +27,12 @@ class LoginRequiredMixin(AccessMixin):
return super().dispatch(request, *args, **kwargs)
class ThinkMixin(LoginRequiredMixin):
model = Think
context_object_name = 'think'
class IndexView(ThinkMixin, generic.ListView):
template_name = 'thinks/index.html'
@ -50,10 +54,12 @@ class IndexView(ThinkMixin, generic.ListView):
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
class RemixThinkView(ThinkMixin, generic.UpdateView):
template_name = 'thinks/remix.html'
form_class = forms.RemixThinkForm
@ -79,6 +85,7 @@ class RemixThinkView(ThinkMixin, generic.UpdateView):
return redirect(think.get_absolute_url())
class ThinkView(ThinkMixin, generic.DetailView):
template_name = "thinks/think.html"
@ -124,28 +131,37 @@ class ThinkView(ThinkMixin, generic.DetailView):
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 = ''
data = context['think_editor_data'] = {
'preview_url': think.get_static_url(),
'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,
}
if path is not None and path.is_file():
mime_types = {
'.elm': 'text/application+elm',
}
mime_type, encoding = mimetypes.guess_type(path)
if mime_type is None:
mime_type = mime_types.get(path.suffix, 'text/plain')
category, filetype = mime_type.split('/') if mime_type is not None else ('text', 'plain')
binary_categories = ['audio', 'video', 'image']
is_binary = category in binary_categories and mime_type != 'image/svg+xml'
data['mime_type'] = mime_type
if not is_binary:
with open(path) as f:
data['file_content'] = f.read()
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):
form_class = forms.RenameThinkForm
template_name = 'thinks/rename.html'
@ -160,18 +176,22 @@ class RenameThinkView(ThinkMixin, generic.UpdateView):
return context
class DeleteThinkView(ThinkMixin, generic.DeleteView):
template_name = 'thinks/delete.html'
def get_success_url(self):
return reverse('index')
class ReadFileView(ThinkMixin, generic.DetailView):
def get(self, request, *args, **kwargs):
think = self.get_object()
relpath = self.kwargs['path']
path = think.root / relpath
print(path)
if not path.is_relative_to(think.root):
raise Exception(f"Bad path: {relpath}")
return redirect(think.get_static_url() + '/' + relpath)
class SaveFileView(ThinkMixin, generic.UpdateView):
@ -191,6 +211,7 @@ class SaveFileView(ThinkMixin, generic.UpdateView):
def get_success_url(self):
return self.object.get_absolute_url()+'?path='+str(self.path)
class RenameFileView(ThinkMixin, generic.UpdateView):
form_class = forms.RenameFileForm
template_name = 'thinks/rename_file.html'
@ -203,6 +224,7 @@ class RenameFileView(ThinkMixin, generic.UpdateView):
def get_success_url(self):
return self.object.get_absolute_url()+'?path='+str(self.path)
class DeleteFileView(ThinkMixin, generic.UpdateView):
form_class = forms.DeleteFileForm
template_name = 'thinks/delete_file.html'
@ -214,6 +236,7 @@ class DeleteFileView(ThinkMixin, generic.UpdateView):
def get_success_url(self):
return self.object.get_absolute_url()+'?path='+str(self.path.parent)
class RunCommandView(ThinkMixin, generic.UpdateView):
form_class = forms.RunCommandForm
@ -228,6 +251,14 @@ class RunCommandView(ThinkMixin, generic.UpdateView):
)
return JsonResponse({'stdout': res.stdout, 'stderr': res.stderr})
class UploadFileView(ThinkMixin, generic.UpdateView):
form_class = forms.UploadFileForm
def get_success_url(self):
return self.object.get_absolute_url()
class LogView(ThinkMixin, generic.DetailView):
template_name = 'thinks/think.html'
@ -236,11 +267,13 @@ class LogView(ThinkMixin, generic.DetailView):
return HttpResponse(think.get_log(), content_type='text/plain; charset=utf-8')
class JJStatusView(ThinkMixin, generic.detail.DetailView):
def get(self, request, *args, **kwargs):
status = self.get_object().jj_controller.status()
return JsonResponse({'status': status})
class JJCommitView(ThinkMixin, generic.UpdateView):
form_class = forms.GitCommitForm