Allowing to paste multiple files.
This commit is contained in:
parent
8eefddfda9
commit
3266bd553c
|
@ -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
|
|
|
@ -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 » s’utilise 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">"</span><span class="nv">$1</span><span class="s2">=@</span><span class="nv">$1</span><span class="s2">"</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">"Content-Type: text/plain"</span>
|
||||||
|
<span class="k">else</span>
|
||||||
|
curl https://p.afpy.org/ <span class="s2">"</span><span class="si">${</span><span class="p">@/*/-F&=@&</span><span class="si">}</span><span class="s2">"</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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
Django
|
Django
|
||||||
Pygments
|
Pygments
|
||||||
shortuuid
|
shortuuid
|
||||||
|
tabulate
|
||||||
django_compressor
|
django_compressor
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue