Allowing to paste multiple files.

This commit is contained in:
Julien Palard 2023-04-24 17:39:50 +02:00
parent 8eefddfda9
commit 3266bd553c
Signed by: mdk
GPG Key ID: 0EFC1AC1006886F8
5 changed files with 136 additions and 64 deletions

View File

@ -1,29 +0,0 @@
from django import forms
from django.forms import CharField, ModelForm
from webtools import settings
from .models import Paste
class PasteForm(ModelForm):
"""Paste model form."""
content = CharField(
max_length=settings.PASTE["max_characters"], strip=False, widget=forms.Textarea
)
class Meta:
model = Paste
fields = [
"filename",
"content",
]
def save(self, commit=True):
"""Overwrites save method."""
paste = super(PasteForm, self).save(commit=False)
paste.compute_size()
if commit:
paste.save()
return paste

View File

@ -4,29 +4,105 @@
{% load compress %} {% load compress %}
{% block content %} {% block content %}
<h1>Paf'Py</h1> <h1>Paf'Py</h1>
<p>Et Paf.</p> <p>Et PAF !</p>
<p>Ce « pastebin-like » sutilise en ligne de commande, soit avec <tt>curl</tt> :</p> <h2>Envoyer un ou plusieurs fichiers</h2>
<div class="highlight"><pre><span></span>curl {{ request.build_absolute_uri }} <span class="o">-F</span>hello.py<span class="o">=</span>@hello.py En utilisant des requêtes <tt>multipart/form-data</tt> il est possible
d'envoyer un fichier :
<div class="highlight"><pre><span></span>curl {{ request.build_absolute_uri }} <span class="o">-F</span>manage.py<span class="o">=</span>@manage.py
| URL | size | filename |
|-------------------------|--------|-----------|
| https://p.afpy.org/g3LE | 251 | manage.py |
</pre></div> </pre></div>
<p>Mais le mieux est de l'utiliser avec une fonction <tt>bash</tt> :</p> ou plusieurs fichiers en même temps :
<div class="highlight"><pre><span></span>pafpy<span class="o">()</span> <div class="highlight"><pre><span></span>curl {{ request.build_absolute_uri }} <span class="o">-F</span>manage.py<span class="o">=</span>@manage.py <span class="o">-F</span>requirements.txt<span class="o">=</span>@requirements.txt
| URL | size | filename |
|-------------------------|--------|------------------|
| https://p.afpy.org/g3LE | 251 | manage.py |
| https://p.afpy.org/k4oT | 547 | requirements.txt |
</pre></div>
C'est l'extension dans le nom du fichier qui permet de choisir la
coloration syntaxique pour chaque fichier.
<h2>Envoyer dans le corps d'une requête</h2>
Il est possible de coller un unique fichier via le corps d'une requête <tt>POST</tt> :
<div class="highlight"><pre>
$ cal | curl -XPOST --data-binary @- {{ request.build_absolute_uri }}
| URL | size | filename |
|-------------------------|--------|----------|
| https://p.afpy.org/mo8X | 184 | request |
</pre></div>
Dans ce cas, il est possible de choisir la coloration syntaxique via l'entête <tt>Content-Type</tt> :
<div class="highlight"><pre>
$ cal | curl -XPOST -H <span class="s2">"Content-Type: text/plain"</span> --data-binary @- {{ request.build_absolute_uri }}
| URL | size | filename |
|-------------------------|--------|-------------|
| https://p.afpy.org/dNuo | 184 | request.txt |
</pre></div>
<h2>Fonction <tt>bash</tt></h2>
<p>Pour ceux qui ne souhaitent pas rédiger des requêtes <tt>curl</tt>
toute la journée, voici une petite fonction <tt>bash</tt> :</p>
<div class="highlight"><pre><span></span>paf<span class="o">()</span>
<span class="o">{</span> <span class="o">{</span>
curl {{request.build_absolute_uri}} -F<span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">=@</span><span class="nv">$1</span><span class="s2">&quot;</span> <span class="k">if</span> <span class="o">[[</span> <span class="nv">$#</span> <span class="o">==</span> <span class="m">0</span> <span class="o">]]</span>
<span class="k">then</span>
curl https://p.afpy.org/ --data-binary @- -H <span class="s2">&quot;Content-Type: text/plain&quot;</span>
<span class="k">else</span>
curl https://p.afpy.org/ <span class="s2">&quot;</span><span class="si">${</span><span class="p">@/*/-F&amp;=@&amp;</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="k">fi</span>
<span class="o">}</span> <span class="o">}</span>
</pre></div> </pre></div>
Demo : Cette fonction est capable d'envoyer un fichier :
<div class="highlight"><pre><span></span>$ pafpy manage.py <div class="highlight"><pre><span></span>$ paf manage.py
https://p.afpy.org/g3LE | URL | size | filename |
|-------------------------|--------|-----------|
| https://p.afpy.org/g3LE | 251 | manage.py |
</pre></div> </pre></div>
On peut le voir dans un navigateur (avec de la coloration syntaxique) : plusieurs fichiers :
<a href="https://p.afpy.org/g3LE">https://p.afpy.org/g3LE</a>), ou on peut le télécharger "brut" :
<div class="highlight"><pre><span></span>$ paf *.py
| URL | size | filename |
|-------------------------|--------|-----------------------|
| https://p.afpy.org/bvRV | 188 | admin.py |
| https://p.afpy.org/5uei | 296 | context_processors.py |
| https://p.afpy.org/Xg5a | 1419 | models.py |
| https://p.afpy.org/GkGS | 309 | urls.py |
| https://p.afpy.org/LVXL | 2730 | views.py |
</pre></div>
et même de lire sur <tt>stdin</tt> :
<div class="highlight"><pre><span></span>$ cal | paf
| URL | size | filename |
|-------------------------|--------|-------------|
| https://p.afpy.org/dNuo | 184 | request.txt |
</pre></div>
<h2>Accès aux collages</h2>
Chaque collage peut-être consulté dans un navigateur (où il est présenté avec de la coloration syntaxique :
<a href="https://p.afpy.org/g3LE">https://p.afpy.org/g3LE</a>), ou
être consulté en ligne de commande (où il est délivré brut) :
<div class="highlight"><pre><span></span>$ curl https://p.afpy.org/g3LE <div class="highlight"><pre><span></span>$ curl https://p.afpy.org/g3LE
#!/usr/bin/env python #!/usr/bin/env python

View File

@ -1,5 +1,5 @@
import json
from functools import lru_cache from functools import lru_cache
from mimetypes import common_types, types_map
import pygments import pygments
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
@ -8,38 +8,60 @@ from django.template import RequestContext, loader
from django.urls import reverse from django.urls import reverse
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from pygments.formatters import HtmlFormatter from pygments.formatters import HtmlFormatter
from pygments.lexers import get_lexer_by_name from pygments.lexers import get_lexer_by_name, get_lexer_for_filename
from tabulate import tabulate
from paste.forms import PasteForm
from paste.models import Paste from paste.models import Paste
from webtools import settings from webtools import settings
NON_STANDARD_TYPES_INV = {value: key for key, value in common_types.items()}
STANDARD_TYPES_INV = {value: key for key, value in types_map.items()}
HARDCODED_TYPES_INV = {"text/plain": ".txt"}
TYPES_INV = NON_STANDARD_TYPES_INV | STANDARD_TYPES_INV | HARDCODED_TYPES_INV
def get_files(request):
"""Get one or multiple files from either a multipart/form-data or
raw request body with a Content-Type header."""
if request.FILES:
return request.FILES
content_type = request.headers.get("content-type", "")
ext = TYPES_INV.get(content_type, "")
return {"request" + ext: request}
@csrf_exempt @csrf_exempt
def index(request): def index(request):
"""Displays form.""" """Displays form."""
if request.method == "GET": if request.method == "GET":
return render(request, "paste/index.html") return render(request, "paste/index.html")
paste = Paste() if request.headers.get("Expect") == "100-continue":
paste.choose_slug() return HttpResponse("")
if not request.FILES: pastes = []
return HttpResponse(json.dumps({"error": "Please provide a file."}, indent=4)) for filename, the_file in get_files(request).items():
(filename, the_file), *_ = request.FILES.items() paste = Paste(
form = PasteForm( filename=filename.split("/")[-1].split("\\")[-1],
{ content=the_file.read().decode("UTF-8"),
"filename": filename.split("/")[-1].split("\\")[-1], )
"content": the_file.read().decode(), paste.choose_slug()
}, paste.compute_size()
instance=paste, paste.save()
) pastes.append(
if not form.is_valid(): (
return HttpResponse(json.dumps(form.errors, indent=4)) request.build_absolute_uri(
form.save() reverse("short_paste", kwargs={"slug": paste.slug})
location = request.build_absolute_uri( ),
reverse("short_paste", kwargs={"slug": paste.slug}) paste.size,
) paste.filename,
return HttpResponseRedirect( )
location, content=location + "\n", content_type="text/plain" )
if not pastes:
return HttpResponse("error: Please provide a file.")
return HttpResponse(
tabulate(pastes, headers=("URL", "size", "filename"), tablefmt="github") + "\n",
content_type="text/plain",
) )
@ -58,7 +80,7 @@ def show(request, slug):
@lru_cache(1024) @lru_cache(1024)
def pygmentize(filename, filecontents): def pygmentize(filename, filecontents):
try: try:
lexer = get_lexer_by_name(filename.split(".")[-1]) lexer = get_lexer_for_filename(filename)
except pygments.util.ClassNotFound: except pygments.util.ClassNotFound:
lexer = get_lexer_by_name(settings.PASTE["default_language"]) lexer = get_lexer_by_name(settings.PASTE["default_language"])
formatter = HtmlFormatter(style="emacs") formatter = HtmlFormatter(style="emacs")

View File

@ -1,4 +1,5 @@
Django Django
Pygments Pygments
shortuuid shortuuid
tabulate
django_compressor django_compressor

View File

@ -24,3 +24,5 @@ shortuuid==1.0.11
# via -r requirements.in # via -r requirements.in
sqlparse==0.4.4 sqlparse==0.4.4
# via django # via django
tabulate==0.9.0
# via -r requirements.in