From 5a16ec150c5f524543a7bff26a8dc61d0cbb517f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Luis=20Boya=20Garc=C3=ADa?= Date: Mon, 14 Jan 2013 00:24:33 +0100 Subject: [PATCH] First commit --- .gitignore | 5 ++ LICENSE.md | 7 +++ README.md | 4 ++ __init__.py | 0 admin.py | 7 +++ forms.py | 14 +++++ management/__init__.py | 0 management/commands/__init__.py | 0 management/commands/_private.py | 0 management/commands/deletemeal.py | 18 ++++++ management/commands/wash.py | 22 +++++++ models.py | 40 +++++++++++++ sendfile.py | 24 ++++++++ static/lasana/sass/stylesheet.sass | 64 +++++++++++++++++++++ templates/lasana/base.html | 21 +++++++ templates/lasana/copyright_footer_note.html | 4 ++ templates/lasana/meal_create_success.html | 11 ++++ templates/lasana/meal_form.html | 14 +++++ tests.py | 16 ++++++ urls.py | 8 +++ views.py | 58 +++++++++++++++++++ 21 files changed, 337 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 __init__.py create mode 100644 admin.py create mode 100644 forms.py create mode 100644 management/__init__.py create mode 100644 management/commands/__init__.py create mode 100644 management/commands/_private.py create mode 100644 management/commands/deletemeal.py create mode 100644 management/commands/wash.py create mode 100644 models.py create mode 100644 sendfile.py create mode 100644 static/lasana/sass/stylesheet.sass create mode 100644 templates/lasana/base.html create mode 100644 templates/lasana/copyright_footer_note.html create mode 100644 templates/lasana/meal_create_success.html create mode 100644 templates/lasana/meal_form.html create mode 100644 tests.py create mode 100644 urls.py create mode 100644 views.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c429f0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.swp +*.pyc +static/lasana/css +uploads/* +dbfile diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..bb1a12d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,7 @@ +Copyright (c) 2012 ntrrgc + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1497627 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +Lasaña, a temporary file hosting service +======================================== + +This is a WIP, but feel free to dig in source. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/admin.py b/admin.py new file mode 100644 index 0000000..f6e00f1 --- /dev/null +++ b/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from . models import Meal + +class MealAdmin(admin.ModelAdmin): + pass + +admin.site.register(Meal, MealAdmin) diff --git a/forms.py b/forms.py new file mode 100644 index 0000000..f95d1a4 --- /dev/null +++ b/forms.py @@ -0,0 +1,14 @@ +from django.forms import Form, FileField, IntegerField, ChoiceField + +from . models import Meal + +class MealCreateForm(Form): + file = FileField() + expires_in = ChoiceField(choices=( + (1, "1 minute"), + (30, "30 minutes"), + (60, "1 hour"), + (180, "3 hours"), + (60*24, "1 day"), + (60*24*7, "1 week"), + ), initial=30) diff --git a/management/__init__.py b/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/management/commands/__init__.py b/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/management/commands/_private.py b/management/commands/_private.py new file mode 100644 index 0000000..e69de29 diff --git a/management/commands/deletemeal.py b/management/commands/deletemeal.py new file mode 100644 index 0000000..1e1a846 --- /dev/null +++ b/management/commands/deletemeal.py @@ -0,0 +1,18 @@ +from django.core.management.base import BaseCommand, CommandError + +from lasana.models import Meal + +class Command(BaseCommand): + args = '' + help = 'Deletes the meal with the specified id' + + def handle(self, *args, **options): + for meal_id in args: + try: + meal = Meal.objects.get(id=int(meal_id)) + except Meal.DoesNotExist: + raise CommandError('Meal "%s" does not exist' % meal_id) + + meal.delete() + + self.stdout.write('Successfully deleted meal "%s"\n' % meal_id) diff --git a/management/commands/wash.py b/management/commands/wash.py new file mode 100644 index 0000000..a8d6b6e --- /dev/null +++ b/management/commands/wash.py @@ -0,0 +1,22 @@ +from django.core.management.base import BaseCommand, CommandError + +from lasana.models import Meal + +from django.utils import timezone + +class Command(BaseCommand): + args = '' + help = 'Deletes expired meals' + + def handle(self, *args, **options): + now = timezone.now() + old_meals = Meal.objects.all().order_by("expiration_time") + + for meal in old_meals: + if meal.is_expired(now): + meal_id = meal.id + + meal.delete() + self.stdout.write('Thrown away expired meal "%d"\n' % meal_id) + else: + break diff --git a/models.py b/models.py new file mode 100644 index 0000000..a986063 --- /dev/null +++ b/models.py @@ -0,0 +1,40 @@ +from django.db.models import Model, CharField, FileField, DateTimeField + +from django.conf import settings + +from django.utils import timezone + +import string +import random + +class Meal(Model): + id_length = 4 + id = CharField(max_length=id_length, db_index=True, primary_key=True) + file = FileField(upload_to=settings.LASANA_UPLOAD_ROOT) + expiration_time = DateTimeField(db_index=True) + + def generate_auto_id(self): + #Theoretically, we can have up to 46656 meals, but having 10k would be enough to worry + if Meal.objects.count() > 10000: + raise "Too much meals" + + while True: + random_string = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(4)) + if len(Meal.objects.filter(id=random_string)) == 0: + self.id = random_string + return self.id + + def is_expired(self, now=None): + if not now: + now = timezone.now() + return now > self.expiration_time + + class Meta: + ordering = ['expiration_time'] + + def __unicode__(self): + now = timezone.now() + if now > self.expiration_time: + return "%s, expired for %s" % (self.file.url, now - self.expiration_time) + else: + return "%s, expires in %s" % (self.file.url, self.expiration_time - now) diff --git a/sendfile.py b/sendfile.py new file mode 100644 index 0000000..202568d --- /dev/null +++ b/sendfile.py @@ -0,0 +1,24 @@ +from django.http import HttpResponse, Http404 +from django.conf import settings +import os + +from django.conf import settings + +#For no-XSendfile approach +from django.core.servers.basehttp import FileWrapper + +def send(request, file): + if not file: + raise Http404 + + if settings.LASANA_USE_X_SENDFILE: + response = HttpResponse() + response['Content-Disposition'] = 'filename=%s' % os.path.basename(file.name) + response['X-Sendfile'] = file.path + return response + else: + response = HttpResponse(FileWrapper(file)) + response['Content-Disposition'] = 'filename=%s' % os.path.basename(file.name) + del response['content-type'] #let the web server guess + response['Content-Length'] = file.size + return response diff --git a/static/lasana/sass/stylesheet.sass b/static/lasana/sass/stylesheet.sass new file mode 100644 index 0000000..3250168 --- /dev/null +++ b/static/lasana/sass/stylesheet.sass @@ -0,0 +1,64 @@ +@import url(http://fonts.googleapis.com/css?family=Donegal+One) + +$body_font: 'Donegal One', serif +$content_width: 600px +$content_border_color: #ff912f + +body + background: #fff8da + font-family: $body_font + font-size: 18px + +h1, #miau + text-align: center + +#content + border: 5px dotted $content_border_color + width: $content_width + margin-left: auto + margin-right: auto + padding: 30px + +form + width: 100% + display: table + border-collapse: separate + border-spacing: 2px + border-top: 1px solid lightgray + border-left: 1px solid lightgray + border-right: 1px solid black + border-bottom: 1px solid black + +form > .field + display: table-row + +form > .field > .cell + display: table-cell + border-top: 1px solid black + border-left: 1px solid black + border-right: 1px solid lightgray + border-bottom: 1px solid lightgray + padding: 10px 10px + text-align: left + +#lasagna_url_field + box-sizing: border-box + width: 100% + border: 1px solid $content_border_color + padding: 3px + font-family: $body_font + font-size: 36px + +#copyright_footer + padding-top: 50px + width: $content_width + margin: 0 auto + +hr + width: $content_width + border: 0 + border-top: 2px solid $content_border_color + +#copyright_footer_note + font-size: 70% + text-align: center diff --git a/templates/lasana/base.html b/templates/lasana/base.html new file mode 100644 index 0000000..2ab1951 --- /dev/null +++ b/templates/lasana/base.html @@ -0,0 +1,21 @@ +{% load staticfiles %} + + + + {% block title %}Lasaña{% endblock %} + + + +

Lasaña

+
+ {% block content %} + {% endblock %} +
+ + + diff --git a/templates/lasana/copyright_footer_note.html b/templates/lasana/copyright_footer_note.html new file mode 100644 index 0000000..18eb40f --- /dev/null +++ b/templates/lasana/copyright_footer_note.html @@ -0,0 +1,4 @@ +© John Titor 2036 + + Redefine 'templates/lasana/copyright_footer_note.html' to edit this note. + diff --git a/templates/lasana/meal_create_success.html b/templates/lasana/meal_create_success.html new file mode 100644 index 0000000..8f5cde4 --- /dev/null +++ b/templates/lasana/meal_create_success.html @@ -0,0 +1,11 @@ +{% extends "lasana/base.html" %} + +{% block content %} +

Share this lasagna:

+ + +

Store more

+{% endblock %} diff --git a/templates/lasana/meal_form.html b/templates/lasana/meal_form.html new file mode 100644 index 0000000..feb5d36 --- /dev/null +++ b/templates/lasana/meal_form.html @@ -0,0 +1,14 @@ +{% extends "lasana/base.html" %} + +{% block content %} +
+ {% for field in form %} +
+
{{ field.label_tag }}
+
{{ field }}{{ field.errors }}
+
+ {% endfor %} + {% csrf_token %} + +
+{% endblock %} diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..501deb7 --- /dev/null +++ b/tests.py @@ -0,0 +1,16 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +from django.test import TestCase + + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) diff --git a/urls.py b/urls.py new file mode 100644 index 0000000..d2f1b7b --- /dev/null +++ b/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls import patterns, include, url + +from . import views + +urlpatterns = patterns('', + url(r'^$', views.MealCreateView.as_view(), name='meal-create'), + url(r'^(?P[A-Z0-9]+)/$', views.MealServeView.as_view(), name='meal-serve'), +) diff --git a/views.py b/views.py new file mode 100644 index 0000000..a9bd868 --- /dev/null +++ b/views.py @@ -0,0 +1,58 @@ +from django.views.generic.edit import CreateView, FormView +from django.views.generic.base import View, TemplateResponse + +from . models import Meal +from . forms import MealCreateForm +from . sendfile import send + +from django.shortcuts import render +from django.http import HttpResponse, HttpResponseRedirect + +from django.core.urlresolvers import reverse, reverse_lazy + +from datetime import timedelta +from django.utils import timezone + +class MealCreateView(FormView): + template_name = "lasana/meal_form.html" + form_class = MealCreateForm + + def form_valid(self, form): + expires_in = int(form.cleaned_data['expires_in']) + file = form.cleaned_data['file'] + + expiration_time = timezone.now() + timedelta(minutes=expires_in) + + meal = Meal(file=file, expiration_time=expiration_time) + meal.generate_auto_id() + meal.save() + + meal_serve_url = reverse('meal-serve', kwargs={'meal_id': meal.id}) + meal_serve_absolute_url = self.request.build_absolute_uri(meal_serve_url) + + return TemplateResponse(self.request, + "lasana/meal_create_success.html", + context={'meal': meal, + 'meal_serve_absolute_url': meal_serve_absolute_url}) + + +class MealServeView(View): + def get(self, request, *args, **kwargs): + #if there is no such meal, redirect to main page + meal_iter = Meal.objects.filter(id=kwargs['meal_id']) + if len(meal_iter) != 1: + return self.no_meal() + + meal = meal_iter[0] + + #if meal is there, but has expired, throw it away and redirect to main page too + if meal.is_expired(): + meal.delete() + return self.no_meal() + else: + #else, serve the meal + file = meal_iter[0].file + return send(request, file) + + def no_meal(self): + return HttpResponseRedirect(reverse('meal-create'))