add a URL to upload files
This commit is contained in:
parent
d061110d88
commit
dcdb3d87be
3 changed files with 91 additions and 8 deletions
|
@ -3,6 +3,7 @@ from django.conf import settings
|
||||||
|
|
||||||
from .models import Think
|
from .models import Think
|
||||||
|
|
||||||
|
|
||||||
class CreateThinkForm(forms.ModelForm):
|
class CreateThinkForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Think
|
model = Think
|
||||||
|
@ -15,11 +16,13 @@ class CreateThinkForm(forms.ModelForm):
|
||||||
instance.root.mkdir(exist_ok=True,parents=True)
|
instance.root.mkdir(exist_ok=True,parents=True)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class RemixThinkForm(forms.ModelForm):
|
class RemixThinkForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Think
|
model = Think
|
||||||
fields = []
|
fields = []
|
||||||
|
|
||||||
|
|
||||||
class RenameThinkForm(forms.ModelForm):
|
class RenameThinkForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Think
|
model = Think
|
||||||
|
@ -49,6 +52,7 @@ class RenameThinkForm(forms.ModelForm):
|
||||||
instance = super().save(commit)
|
instance = super().save(commit)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class SaveFileForm(forms.ModelForm):
|
class SaveFileForm(forms.ModelForm):
|
||||||
path = forms.CharField()
|
path = forms.CharField()
|
||||||
content = forms.CharField(required=False, widget=forms.Textarea)
|
content = forms.CharField(required=False, widget=forms.Textarea)
|
||||||
|
@ -72,6 +76,7 @@ class SaveFileForm(forms.ModelForm):
|
||||||
|
|
||||||
return super().save(commit)
|
return super().save(commit)
|
||||||
|
|
||||||
|
|
||||||
class RenameFileForm(forms.ModelForm):
|
class RenameFileForm(forms.ModelForm):
|
||||||
path = forms.CharField()
|
path = forms.CharField()
|
||||||
newpath = forms.CharField()
|
newpath = forms.CharField()
|
||||||
|
@ -100,6 +105,7 @@ class RenameFileForm(forms.ModelForm):
|
||||||
|
|
||||||
return super().save(commit)
|
return super().save(commit)
|
||||||
|
|
||||||
|
|
||||||
class DeleteFileForm(forms.ModelForm):
|
class DeleteFileForm(forms.ModelForm):
|
||||||
path = forms.CharField()
|
path = forms.CharField()
|
||||||
|
|
||||||
|
@ -121,6 +127,7 @@ class DeleteFileForm(forms.ModelForm):
|
||||||
|
|
||||||
return super().save(commit)
|
return super().save(commit)
|
||||||
|
|
||||||
|
|
||||||
class RunCommandForm(forms.ModelForm):
|
class RunCommandForm(forms.ModelForm):
|
||||||
command = forms.CharField()
|
command = forms.CharField()
|
||||||
|
|
||||||
|
@ -128,9 +135,51 @@ class RunCommandForm(forms.ModelForm):
|
||||||
model = Think
|
model = Think
|
||||||
fields = []
|
fields = []
|
||||||
|
|
||||||
|
|
||||||
class GitCommitForm(forms.ModelForm):
|
class GitCommitForm(forms.ModelForm):
|
||||||
message = forms.CharField()
|
message = forms.CharField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Think
|
model = Think
|
||||||
fields = []
|
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)
|
||||||
|
|
|
@ -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>/upload-files', UploadFileView.as_view(), name='upload_files'),
|
||||||
path('think/<slug:slug>/log', LogView.as_view(), name='log'),
|
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/status', JJStatusView.as_view(), name='jj_status'),
|
||||||
path('think/<slug:slug>/jj/commit', JJCommitView.as_view(), name='jj_commit'),
|
path('think/<slug:slug>/jj/commit', JJCommitView.as_view(), name='jj_commit'),
|
||||||
|
|
|
@ -6,6 +6,7 @@ from django.views import generic
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
import json
|
import json
|
||||||
|
import mimetypes
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import shutil
|
import shutil
|
||||||
import shlex
|
import shlex
|
||||||
|
@ -16,6 +17,7 @@ from .make import ThingMaker
|
||||||
from .models import Think
|
from .models import Think
|
||||||
from .random_slug import random_slug
|
from .random_slug import random_slug
|
||||||
|
|
||||||
|
|
||||||
class LoginRequiredMixin(AccessMixin):
|
class LoginRequiredMixin(AccessMixin):
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
|
@ -25,10 +27,12 @@ class LoginRequiredMixin(AccessMixin):
|
||||||
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ThinkMixin(LoginRequiredMixin):
|
class ThinkMixin(LoginRequiredMixin):
|
||||||
model = Think
|
model = Think
|
||||||
context_object_name = 'think'
|
context_object_name = 'think'
|
||||||
|
|
||||||
|
|
||||||
class IndexView(ThinkMixin, generic.ListView):
|
class IndexView(ThinkMixin, generic.ListView):
|
||||||
template_name = 'thinks/index.html'
|
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 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)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
class RemixThinkView(ThinkMixin, generic.UpdateView):
|
class RemixThinkView(ThinkMixin, generic.UpdateView):
|
||||||
template_name = 'thinks/remix.html'
|
template_name = 'thinks/remix.html'
|
||||||
form_class = forms.RemixThinkForm
|
form_class = forms.RemixThinkForm
|
||||||
|
@ -79,6 +85,7 @@ class RemixThinkView(ThinkMixin, generic.UpdateView):
|
||||||
return redirect(think.get_absolute_url())
|
return redirect(think.get_absolute_url())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ThinkView(ThinkMixin, generic.DetailView):
|
class ThinkView(ThinkMixin, generic.DetailView):
|
||||||
template_name = "thinks/think.html"
|
template_name = "thinks/think.html"
|
||||||
|
|
||||||
|
@ -124,28 +131,37 @@ class ThinkView(ThinkMixin, generic.DetailView):
|
||||||
|
|
||||||
files = sorted(files, key=lambda x: x['name'].lower())
|
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'] = {
|
data = context['think_editor_data'] = {
|
||||||
'preview_url': think.get_static_url(),
|
'preview_url': think.get_static_url(),
|
||||||
'slug': think.slug,
|
'slug': think.slug,
|
||||||
'files': files,
|
'files': files,
|
||||||
'file_path': str(relpath),
|
'file_path': str(relpath),
|
||||||
'file_content': content,
|
|
||||||
'is_dir': path is None or path.is_dir(),
|
'is_dir': path is None or path.is_dir(),
|
||||||
'no_preview': self.request.GET.get('no-preview') is not None,
|
'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':
|
if path is not None and path.suffix == '.elm':
|
||||||
with open('public/elm-packages.json') as f:
|
with open('public/elm-packages.json') as f:
|
||||||
data['elm_packages'] = json.load(f)
|
data['elm_packages'] = json.load(f)
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class RenameThinkView(ThinkMixin, generic.UpdateView):
|
class RenameThinkView(ThinkMixin, generic.UpdateView):
|
||||||
form_class = forms.RenameThinkForm
|
form_class = forms.RenameThinkForm
|
||||||
template_name = 'thinks/rename.html'
|
template_name = 'thinks/rename.html'
|
||||||
|
@ -160,18 +176,22 @@ class RenameThinkView(ThinkMixin, generic.UpdateView):
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class DeleteThinkView(ThinkMixin, generic.DeleteView):
|
class DeleteThinkView(ThinkMixin, generic.DeleteView):
|
||||||
template_name = 'thinks/delete.html'
|
template_name = 'thinks/delete.html'
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse('index')
|
return reverse('index')
|
||||||
|
|
||||||
|
|
||||||
class ReadFileView(ThinkMixin, generic.DetailView):
|
class ReadFileView(ThinkMixin, generic.DetailView):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
think = self.get_object()
|
think = self.get_object()
|
||||||
relpath = self.kwargs['path']
|
relpath = self.kwargs['path']
|
||||||
path = think.root / relpath
|
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):
|
class SaveFileView(ThinkMixin, generic.UpdateView):
|
||||||
|
@ -191,6 +211,7 @@ class SaveFileView(ThinkMixin, generic.UpdateView):
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return self.object.get_absolute_url()+'?path='+str(self.path)
|
return self.object.get_absolute_url()+'?path='+str(self.path)
|
||||||
|
|
||||||
|
|
||||||
class RenameFileView(ThinkMixin, generic.UpdateView):
|
class RenameFileView(ThinkMixin, generic.UpdateView):
|
||||||
form_class = forms.RenameFileForm
|
form_class = forms.RenameFileForm
|
||||||
template_name = 'thinks/rename_file.html'
|
template_name = 'thinks/rename_file.html'
|
||||||
|
@ -203,6 +224,7 @@ class RenameFileView(ThinkMixin, generic.UpdateView):
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return self.object.get_absolute_url()+'?path='+str(self.path)
|
return self.object.get_absolute_url()+'?path='+str(self.path)
|
||||||
|
|
||||||
|
|
||||||
class DeleteFileView(ThinkMixin, generic.UpdateView):
|
class DeleteFileView(ThinkMixin, generic.UpdateView):
|
||||||
form_class = forms.DeleteFileForm
|
form_class = forms.DeleteFileForm
|
||||||
template_name = 'thinks/delete_file.html'
|
template_name = 'thinks/delete_file.html'
|
||||||
|
@ -214,6 +236,7 @@ class DeleteFileView(ThinkMixin, generic.UpdateView):
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return self.object.get_absolute_url()+'?path='+str(self.path.parent)
|
return self.object.get_absolute_url()+'?path='+str(self.path.parent)
|
||||||
|
|
||||||
|
|
||||||
class RunCommandView(ThinkMixin, generic.UpdateView):
|
class RunCommandView(ThinkMixin, generic.UpdateView):
|
||||||
form_class = forms.RunCommandForm
|
form_class = forms.RunCommandForm
|
||||||
|
|
||||||
|
@ -228,6 +251,14 @@ class RunCommandView(ThinkMixin, generic.UpdateView):
|
||||||
)
|
)
|
||||||
return JsonResponse({'stdout': res.stdout, 'stderr': res.stderr})
|
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):
|
class LogView(ThinkMixin, generic.DetailView):
|
||||||
template_name = 'thinks/think.html'
|
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')
|
return HttpResponse(think.get_log(), content_type='text/plain; charset=utf-8')
|
||||||
|
|
||||||
|
|
||||||
class JJStatusView(ThinkMixin, generic.detail.DetailView):
|
class JJStatusView(ThinkMixin, generic.detail.DetailView):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
status = self.get_object().jj_controller.status()
|
status = self.get_object().jj_controller.status()
|
||||||
return JsonResponse({'status': status})
|
return JsonResponse({'status': status})
|
||||||
|
|
||||||
|
|
||||||
class JJCommitView(ThinkMixin, generic.UpdateView):
|
class JJCommitView(ThinkMixin, generic.UpdateView):
|
||||||
form_class = forms.GitCommitForm
|
form_class = forms.GitCommitForm
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue