This commit is contained in:
Julien Palard 2020-05-31 15:33:59 +02:00
parent f1a87fb77b
commit a539d9096f
12 changed files with 219 additions and 130 deletions

View File

@ -2,6 +2,7 @@ from django.contrib import admin
from .models import Paste
@admin.register(Paste)
class PasteAdmin(admin.ModelAdmin):
list_display = ('paste_time', 'slug', 'title', 'viewcount')
list_display = ("paste_time", "slug", "title", "viewcount")

View File

@ -3,8 +3,9 @@ from django.conf import settings
def app_details(request):
"""Passes settings details to the templates."""
return {'APP_NAME': settings.APP_NAME,
'APP_VERSION': settings.APP_VERSION,
'DISPLAY_NAME': settings.DISPLAY_NAME,
'PASTE': settings.PASTE,
return {
"APP_NAME": settings.APP_NAME,
"APP_VERSION": settings.APP_VERSION,
"DISPLAY_NAME": settings.DISPLAY_NAME,
"PASTE": settings.PASTE,
}

View File

@ -6,23 +6,31 @@ from .models import Paste, Language, EXPIRE_CHOICES
class PasteForm(ModelForm):
"""Paste model form."""
content = CharField(max_length=settings.PASTE['max_characters'],
strip=False,
widget=forms.Textarea)
content = CharField(
max_length=settings.PASTE["max_characters"], strip=False, widget=forms.Textarea
)
class Meta:
model = Paste
fields = ['language', 'title', 'password', 'content', 'lifetime',
'lifecount', 'private']
fields = [
"language",
"title",
"password",
"content",
"lifetime",
"lifecount",
"private",
]
def save(self, commit=True):
"""Overwrites save method."""
paste = super(PasteForm, self).save(commit=False)
paste.compute_size()
if not self.cleaned_data['title']:
paste.title = 'no title'
if self.cleaned_data['password']:
paste.set_password(self.cleaned_data['password'])
if not self.cleaned_data["title"]:
paste.title = "no title"
if self.cleaned_data["password"]:
paste.set_password(self.cleaned_data["password"])
if commit:
paste.save()
return paste

View File

@ -1,16 +1,18 @@
from django.contrib.sessions.middleware import SessionMiddleware as DjangoSessionMiddleware
from django.contrib.sessions.middleware import (
SessionMiddleware as DjangoSessionMiddleware,
)
class SessionMiddleware(DjangoSessionMiddleware):
def process_request(self, request):
if 'admin' in request.path:
if "admin" in request.path:
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
request.session = self.SessionStore(session_key)
else:
request.session = {}
def process_response(self, request, response):
if 'admin' in request.path:
if "admin" in request.path:
return super().process_response(request, response)
else:
return response

View File

@ -10,37 +10,81 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='Language',
name="Language",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, unique=True)),
('slug', models.SlugField(max_length=200, unique=True)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=200, unique=True)),
("slug", models.SlugField(max_length=200, unique=True)),
],
),
migrations.CreateModel(
name='Paste',
name="Paste",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(editable=False, unique=True)),
('title', models.CharField(blank=True, max_length=200)),
('content', models.TextField(validators=[django.core.validators.MaxLengthValidator(100000)])),
('size', models.IntegerField(default=0, editable=False)),
('paste_time', models.DateTimeField(default=datetime.datetime.now, editable=False)),
('paste_ip', models.GenericIPAddressField(editable=False)),
('paste_agent', models.CharField(editable=False, max_length=200)),
('lifetime', models.IntegerField(choices=[(0, 'Never expire'), (5, '5 minutes'), (30, '30 minutes'), (60, '1 hour'), (1440, '1 day'), (10080, '1 week')], default=0)),
('lifecount', models.IntegerField(default=0)),
('viewcount', models.IntegerField(default=0, editable=False)),
('expired', models.BooleanField(default=False, editable=False)),
('private', models.BooleanField(default=False)),
('password', models.CharField(blank=True, max_length=128)),
('salt', models.CharField(blank=True, max_length=36)),
('language', models.ForeignKey(default=13, null=True, on_delete=django.db.models.deletion.SET_NULL, to='paste.Language')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("slug", models.SlugField(editable=False, unique=True)),
("title", models.CharField(blank=True, max_length=200)),
(
"content",
models.TextField(
validators=[django.core.validators.MaxLengthValidator(100000)]
),
),
("size", models.IntegerField(default=0, editable=False)),
(
"paste_time",
models.DateTimeField(default=datetime.datetime.now, editable=False),
),
("paste_ip", models.GenericIPAddressField(editable=False)),
("paste_agent", models.CharField(editable=False, max_length=200)),
(
"lifetime",
models.IntegerField(
choices=[
(0, "Never expire"),
(5, "5 minutes"),
(30, "30 minutes"),
(60, "1 hour"),
(1440, "1 day"),
(10080, "1 week"),
],
default=0,
),
),
("lifecount", models.IntegerField(default=0)),
("viewcount", models.IntegerField(default=0, editable=False)),
("expired", models.BooleanField(default=False, editable=False)),
("private", models.BooleanField(default=False)),
("password", models.CharField(blank=True, max_length=128)),
("salt", models.CharField(blank=True, max_length=36)),
(
"language",
models.ForeignKey(
default=13,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="paste.Language",
),
),
],
),
]

View File

@ -7,31 +7,40 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('paste', '0001_initial'),
("paste", "0001_initial"),
]
operations = [
migrations.RemoveField(
model_name='paste',
name='paste_agent',
),
migrations.RemoveField(
model_name='paste',
name='paste_ip',
migrations.RemoveField(model_name="paste", name="paste_agent",),
migrations.RemoveField(model_name="paste", name="paste_ip",),
migrations.AlterField(
model_name="paste",
name="language",
field=models.ForeignKey(
default=14,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="paste.Language",
),
),
migrations.AlterField(
model_name='paste',
name='language',
field=models.ForeignKey(default=14, null=True, on_delete=django.db.models.deletion.SET_NULL, to='paste.Language'),
),
migrations.AlterField(
model_name='paste',
name='lifecount',
model_name="paste",
name="lifecount",
field=models.IntegerField(blank=True, default=0),
),
migrations.AlterField(
model_name='paste',
name='lifetime',
field=models.IntegerField(choices=[(0, 'Never expire'), (60, '1 hour'), (1440, '1 day'), (10080, '1 week'), (302400, '1 month'), (3679200, '1 year')], default=3679200),
model_name="paste",
name="lifetime",
field=models.IntegerField(
choices=[
(0, "Never expire"),
(60, "1 hour"),
(1440, "1 day"),
(10080, "1 week"),
(302400, "1 month"),
(3679200, "1 year"),
],
default=3679200,
),
),
]

View File

@ -9,17 +9,18 @@ import uuid
EXPIRE_CHOICES = (
(0, _('Never expire')),
(60, _('1 hour')),
(60 * 24, _('1 day')),
(60 * 24 * 7, _('1 week')),
(60 * 24 * 7 * 30, _('1 month')),
(60 * 24 * 7 * 365, _('1 year')),
(0, _("Never expire")),
(60, _("1 hour")),
(60 * 24, _("1 day")),
(60 * 24 * 7, _("1 week")),
(60 * 24 * 7 * 30, _("1 month")),
(60 * 24 * 7 * 365, _("1 year")),
)
class Language(models.Model):
"""Language object."""
name = models.CharField(max_length=200, unique=True)
slug = models.SlugField(max_length=200, unique=True)
@ -28,7 +29,8 @@ class Language(models.Model):
language = cls.objects.filter(name__icontains=name).first()
if not language:
language = cls.objects.filter(
name__iexact=settings.PASTE['default_language']).first()
name__iexact=settings.PASTE["default_language"]
).first()
return language
def __unicode__(self):
@ -41,15 +43,20 @@ class Language(models.Model):
class Paste(models.Model):
"""Paste object."""
language = models.ForeignKey(Language, default=14, on_delete=models.SET_NULL, null=True)
language = models.ForeignKey(
Language, default=14, on_delete=models.SET_NULL, null=True
)
slug = models.SlugField(unique=True, editable=False)
title = models.CharField(max_length=200, blank=True)
content = models.TextField(validators=[MaxLengthValidator(
settings.PASTE['max_characters'])])
content = models.TextField(
validators=[MaxLengthValidator(settings.PASTE["max_characters"])]
)
size = models.IntegerField(default=0, editable=False)
paste_time = models.DateTimeField(default=datetime.now, editable=False)
lifetime = models.IntegerField(default=settings.PASTE['default_lifetime'],
choices=EXPIRE_CHOICES)
lifetime = models.IntegerField(
default=settings.PASTE["default_lifetime"], choices=EXPIRE_CHOICES
)
lifecount = models.IntegerField(default=0, blank=True)
viewcount = models.IntegerField(default=0, editable=False)
expired = models.BooleanField(default=False, editable=False)
@ -109,7 +116,7 @@ class Paste(models.Model):
"""Return hashed string."""
if not self.salt:
self.salt = str(uuid.uuid1())
return hashlib.sha512((raw+self.salt).encode()).hexdigest()
return hashlib.sha512((raw + self.salt).encode()).hexdigest()
def set_password(self, raw):
"""Define a hashed password."""
@ -126,6 +133,5 @@ class Paste(models.Model):
return self.slug
def __str__(self):
excerpt = repr(self.content[:100]) + (
'...' if len(self.content) > 100 else '')
excerpt = repr(self.content[:100]) + ("..." if len(self.content) > 100 else "")
return "{} - {} - {}".format(self.slug, self.title, excerpt)

View File

@ -9,27 +9,27 @@ from paste.tools import cache_exists, cache_fetch, cache_store
def render_pygments(request, paste, data):
"""Renders Pygments template."""
key = paste.slug+'_pygments.cache'
key = paste.slug + "_pygments.cache"
if cache_exists(key):
highlighted_content = cache_fetch(key)
else:
lexer = get_lexer_by_name(paste.language.slug)
formatter = HtmlFormatter(style='emacs')
formatter = HtmlFormatter(style="emacs")
highlighted_content = highlight(paste.content, lexer, formatter)
cache_store(key, highlighted_content)
data['paste'] = paste
data['highlighted'] = highlighted_content
rendered = loader.render_to_string('paste/show-pygments.html', data, request)
data["paste"] = paste
data["highlighted"] = highlighted_content
rendered = loader.render_to_string("paste/show-pygments.html", data, request)
return HttpResponse(rendered)
def render_form(request, paste, data):
"""Renders Form template."""
data['paste'] = paste
rendered = loader.render_to_string('paste/show-form.html', data, request)
data["paste"] = paste
rendered = loader.render_to_string("paste/show-form.html", data, request)
return HttpResponse(rendered)
def render_raw(request, paste, data):
"""Renders RAW content."""
return HttpResponse(paste.content, content_type='text/plain')
return HttpResponse(paste.content, content_type="text/plain")

View File

@ -2,12 +2,14 @@ from django import template
register = template.Library()
@register.filter(name='add_class')
@register.filter(name="add_class")
def add_class(value, arg):
value.field.widget.attrs.update({"class": arg})
return value
@register.filter(name='placeholder')
@register.filter(name="placeholder")
def placeholder(value, arg):
value.field.widget.attrs.update({"placeholder": arg})
return value

View File

@ -8,7 +8,7 @@ from .models import Paste
def random_id(model):
"""Returns a short uuid for the slug of the given model."""
uuid = random.choice('0123456789') + shortuuid.uuid()
uuid = random.choice("0123456789") + shortuuid.uuid()
for i in range(3, len(uuid)):
potential_uuid = uuid[:i]
if not model.objects.filter(slug=potential_uuid):
@ -28,11 +28,11 @@ def cache_exists(key):
def cache_store(key, value):
"""Store cache value for key."""
with open(cache_get_filepath(key), 'w') as cache_file:
with open(cache_get_filepath(key), "w") as cache_file:
cache_file.write(value)
def cache_fetch(key):
"""Fetch cache value for key."""
with open(cache_get_filepath(key), 'r') as cache_file:
with open(cache_get_filepath(key), "r") as cache_file:
return cache_file.read()

View File

@ -5,9 +5,13 @@ from webtools import settings
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^history$', views.history, name='history'),
url(r'^static/(?P<path>.*)', serve, {'document_root': settings.STATIC_ROOT}),
url(r'^paste/(?P<slug>[a-zA-Z0-9]+)/(?P<renderer>[a-z]+)?$', views.show, name='paste'),
url(r'^(?P<slug>[0-9][a-zA-Z0-9]+)$', views.show, name='short_paste'),
url(r"^$", views.index, name="index"),
url(r"^history$", views.history, name="history"),
url(r"^static/(?P<path>.*)", serve, {"document_root": settings.STATIC_ROOT}),
url(
r"^paste/(?P<slug>[a-zA-Z0-9]+)/(?P<renderer>[a-z]+)?$",
views.show,
name="paste",
),
url(r"^(?P<slug>[0-9][a-zA-Z0-9]+)$", views.show, name="short_paste"),
]

View File

@ -12,74 +12,86 @@ from webtools import settings
@csrf_exempt
def index(request):
"""Displays form."""
data = {'menu': 'index',
'max_characters': settings.PASTE['max_characters']}
if request.method == 'POST':
data = {"menu": "index", "max_characters": settings.PASTE["max_characters"]}
if request.method == "POST":
paste = Paste(slug=random_id(Paste))
if request.FILES:
for language_name, any_file in request.FILES.items():
break
language = Language.by_name(language_name)
form = PasteForm({'language': language.id,
'title': any_file.name,
'private': settings.PASTE['private_by_default'],
'lifetime': settings.PASTE['default_lifetime'],
'content': any_file.read().decode()
}, instance=paste)
form = PasteForm(
{
"language": language.id,
"title": any_file.name,
"private": settings.PASTE["private_by_default"],
"lifetime": settings.PASTE["default_lifetime"],
"content": any_file.read().decode(),
},
instance=paste,
)
else:
form = PasteForm(request.POST, instance=paste)
if not form.is_valid():
data['form'] = form
return render(request, 'paste/index.html', data)
form.save() # Some logic added to overrided method, see forms.py
data["form"] = form
return render(request, "paste/index.html", data)
form.save() # Some logic added to overrided method, see forms.py
location = request.build_absolute_uri(
reverse('short_paste', kwargs={'slug': paste.slug}))
return HttpResponseRedirect(location, content=location + "\n",
content_type='text/plain')
data['form'] = PasteForm(initial={
'private': settings.PASTE['private_by_default'],
'lifetime': settings.PASTE['default_lifetime'],
'language': Language.by_name(settings.PASTE['default_language']).id})
data['absolute_index_url'] = request.build_absolute_uri(reverse('index'))
return render(request, 'paste/index.html', data)
reverse("short_paste", kwargs={"slug": paste.slug})
)
return HttpResponseRedirect(
location, content=location + "\n", content_type="text/plain"
)
data["form"] = PasteForm(
initial={
"private": settings.PASTE["private_by_default"],
"lifetime": settings.PASTE["default_lifetime"],
"language": Language.by_name(settings.PASTE["default_language"]).id,
}
)
data["absolute_index_url"] = request.build_absolute_uri(reverse("index"))
return render(request, "paste/index.html", data)
def show(request, slug, renderer='pygments'):
def show(request, slug, renderer="pygments"):
"""Display paste."""
# Fetching object
paste = get_object_or_404(Paste, slug=slug)
data = {'title': paste.title,'slug': slug}
data = {"title": paste.title, "slug": slug}
# Handling expiration
if paste.is_expired():
return render(request, 'paste/expired.html')
return render(request, "paste/expired.html")
# Handling passwords
if paste.password:
if 'password' in request.POST:
password = request.POST['password']
elif 'password' in request.COOKIES:
password = request.COOKIES['password']
if "password" in request.POST:
password = request.POST["password"]
elif "password" in request.COOKIES:
password = request.COOKIES["password"]
else:
password = None
if not paste.pwd_match(password):
return render(request, 'paste/locked.html', data)
return render(request, "paste/locked.html", data)
# Before rendering actions
paste.incr_viewcount()
# Handling rendering modes
if not renderer or renderer not in settings.PASTE['enabled_renderers']:
renderer = settings.PASTE['default_renderer']
data['current_renderer'] = renderer
data['renderers'] = settings.PASTE['enabled_renderers']
render_method = getattr(renderers, 'render_%s' % renderer)
if not renderer or renderer not in settings.PASTE["enabled_renderers"]:
renderer = settings.PASTE["default_renderer"]
data["current_renderer"] = renderer
data["renderers"] = settings.PASTE["enabled_renderers"]
render_method = getattr(renderers, "render_%s" % renderer)
response = render_method(request, paste, data)
# Responding
if 'password' in request.POST:
response.set_cookie('password', request.POST['password'])
if "password" in request.POST:
response.set_cookie("password", request.POST["password"])
return response
def history(request):
"""Display last 25 public non-expired pastes."""
pastes = Paste.objects.filter(private=False, expired=False).order_by('-pk')[:25]
data = {'pastes': pastes, 'menu': 'history', 'default_renderer': settings.PASTE['default_renderer']}
return render(request, 'paste/history.html', data)
pastes = Paste.objects.filter(private=False, expired=False).order_by("-pk")[:25]
data = {
"pastes": pastes,
"menu": "history",
"default_renderer": settings.PASTE["default_renderer"],
}
return render(request, "paste/history.html", data)