From 34181e03f749a51dc4e627fd648edf61674be89f Mon Sep 17 00:00:00 2001 From: Martin Mahner Date: Sun, 8 Jun 2014 20:15:59 +0200 Subject: [PATCH] Added a API value to set the expire time of snippets. Closes issue #64. --- docs/api.rst | 21 ++++++++++++--- dpaste/forms.py | 52 ++++++++++++++++++++---------------- dpaste/tests/test_api.py | 51 +++++++++++++++++++++++++++++++++++ dpaste/tests/test_snippet.py | 6 +---- dpaste/views.py | 16 +++++++++-- 5 files changed, 113 insertions(+), 33 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index a33a20f..4e4ef34 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -15,18 +15,18 @@ Available POST data for an API call: ``content`` (required) ~~~~~~~~~~~~~~~~~~~~~~ -Required. The UTF-8 encoded string you want to paste. +The UTF-8 encoded string you want to paste. ``lexer`` (optional) ~~~~~~~~~~~~~~~~~~~~ -Optional. Can also be set via GET. The lexer string key used for highlighting. +Can also be set via GET. The lexer string key used for highlighting. See `lexer list`_ for a full list of choices. Default: ``python``. ``format`` (optional) ~~~~~~~~~~~~~~~~~~~~~ -Optional. Can also be set via GET. The format of the API response. Choices are: +Can also be set via GET. The format of the API response. Choices are: * ``default`` — Returns a full qualified URL wrapped in quotes. Example:: @@ -47,6 +47,21 @@ Optional. Can also be set via GET. The format of the API response. Choices are: "conent": "The text body of the snippet." } + +``expires`` (optional) +~~~~~~~~~~~~~~~~~~~~~~ + +Can also be set via GET. A keyword to indicate the lifetime of a +snippetn in seconds. The values are predefined by the server. Calling this with +an invalid value returns a HTTP 400 BadRequest together with a list of valid +values. Default: ``2592000``. In the default configuration valid values are: + +* onetime +* never +* 3600 +* 604800 +* 2592000 + .. hint:: You need to adjust the setting ``DPASTE_BASE_URL`` which is used to generate the full qualified URL in the API response. See :doc:`settings`. diff --git a/dpaste/forms.py b/dpaste/forms.py index 1379622..e248dcc 100644 --- a/dpaste/forms.py +++ b/dpaste/forms.py @@ -17,6 +17,20 @@ EXPIRE_CHOICES = getattr(settings, 'DPASTE_EXPIRE_CHOICES', ( EXPIRE_DEFAULT = getattr(settings, 'DPASTE_EXPIRE_DEFAULT', EXPIRE_CHOICES[3][0]) MAX_CONTENT_LENGTH = getattr(settings, 'DPASTE_MAX_CONTENT_LENGTH', 250*1024*1024) +def get_expire_values(expires): + if expires == u'never': + expire_type = Snippet.EXPIRE_KEEP + expires = None + elif expires == u'onetime': + expire_type = Snippet.EXPIRE_ONETIME + expires = None + else: + expire_type = Snippet.EXPIRE_TIME + expires = expires and expires or EXPIRE_DEFAULT + expires = datetime.datetime.now() + datetime.timedelta(seconds=int(expires)) + return expires, expire_type + + class SnippetForm(forms.ModelForm): content = forms.CharField( label=_('Content'), @@ -71,43 +85,35 @@ class SnippetForm(forms.ModelForm): raise forms.ValidationError(_('Plesae fill out this field.')) return content + def clean_expires(self): + """ + Extract the 'expire_type' from the choice of expire choices. + """ + expires = self.cleaned_data['expires'] + expires, expire_type = get_expire_values(expires) + self.cleaned_data['expire_type'] = expire_type + return expires + def clean(self): - # The `title` field is a hidden honeypot field. If its filled, - # this is likely spam. + """ + The `title` field is a hidden honeypot field. If its filled, + this is likely spam. + """ if self.cleaned_data.get('title'): raise forms.ValidationError('This snippet was identified as Spam.') return self.cleaned_data - def clean_expires(self): - expires = self.cleaned_data['expires'] - - if expires == u'never': - self.cleaned_data['expire_type'] = Snippet.EXPIRE_KEEP - return None - - if expires == u'onetime': - self.cleaned_data['expire_type'] = Snippet.EXPIRE_ONETIME - return None - - self.cleaned_data['expire_type'] = Snippet.EXPIRE_TIME - return expires - def save(self, parent=None, *args, **kwargs): MAX_SNIPPETS_PER_USER = getattr(settings, 'DPASTE_MAX_SNIPPETS_PER_USER', 10) # Set parent snippet - if parent: - self.instance.parent = parent + self.instance.parent = parent # Add expire datestamp. None indicates 'keep forever', use the default # null state of the db column for that. + self.instance.expires = self.cleaned_data['expires'] self.instance.expire_type = self.cleaned_data['expire_type'] - expires = self.cleaned_data['expires'] - if expires: - self.instance.expires = datetime.datetime.now() + \ - datetime.timedelta(seconds=int(expires)) - # Save snippet in the db super(SnippetForm, self).save(*args, **kwargs) diff --git a/dpaste/tests/test_api.py b/dpaste/tests/test_api.py index 3525596..2a2da5c 100644 --- a/dpaste/tests/test_api.py +++ b/dpaste/tests/test_api.py @@ -3,6 +3,7 @@ from django.core.urlresolvers import reverse from django.test.client import Client from django.test import TestCase +from django.test.utils import override_settings from ..models import Snippet @@ -133,3 +134,53 @@ class SnippetAPITestCase(TestCase): response = self.client.post(self.api_url, data) self.assertEqual(response.status_code, 400) self.assertEqual(Snippet.objects.count(), 0) + + def test_expire_choices_none_given(self): + # No expire choice given will set a default expiration of one month + response = self.client.post(self.api_url, { + 'content': u"Hello Wörld.\n\tGood Bye"}) + self.assertEqual(response.status_code, 200) + self.assertEqual(Snippet.objects.count(), 1) + self.assertTrue(Snippet.objects.all()[0].expires) + + def test_expire_choices_invalid_given(self): + # A expire choice that does not exist returns a BadRequest + response = self.client.post(self.api_url, { + 'content': u"Hello Wörld.\n\tGood Bye", 'expires': 'foobar'}) + self.assertEqual(response.status_code, 400) + self.assertEqual(Snippet.objects.count(), 0) + + def test_valid_expiration_choices(self): + """ + Test all the different expiration choices. We dont actually test + the deletion, since thats handled in the `test_snippet` section. + """ + response = self.client.post(self.api_url, { + 'content': u"Hello Wörld.\n\tGood Bye", 'expires': 'onetime'}) + self.assertEqual(response.status_code, 200) + self.assertEqual(Snippet.objects.count(), 1) + self.assertEqual(Snippet.objects.all()[0].expire_type, Snippet.EXPIRE_ONETIME) + + response = self.client.post(self.api_url, { + 'content': u"Hello Wörld.\n\tGood Bye", 'expires': 'never'}) + self.assertEqual(response.status_code, 200) + self.assertEqual(Snippet.objects.count(), 2) + self.assertEqual(Snippet.objects.all()[0].expire_type, Snippet.EXPIRE_KEEP) + + response = self.client.post(self.api_url, { + 'content': u"Hello Wörld.\n\tGood Bye", 'expires': 3600}) + self.assertEqual(response.status_code, 200) + self.assertEqual(Snippet.objects.count(), 3) + self.assertTrue(Snippet.objects.all()[0].expires) + + response = self.client.post(self.api_url, { + 'content': u"Hello Wörld.\n\tGood Bye", 'expires': 3600 * 24 * 7}) + self.assertEqual(response.status_code, 200) + self.assertEqual(Snippet.objects.count(), 4) + self.assertTrue(Snippet.objects.all()[0].expires) + + response = self.client.post(self.api_url, { + 'content': u"Hello Wörld.\n\tGood Bye", 'expires': 3600 * 24 * 30}) + self.assertEqual(response.status_code, 200) + self.assertEqual(Snippet.objects.count(), 5) + self.assertTrue(Snippet.objects.all()[0].expires) diff --git a/dpaste/tests/test_snippet.py b/dpaste/tests/test_snippet.py index 88c2445..98af952 100644 --- a/dpaste/tests/test_snippet.py +++ b/dpaste/tests/test_snippet.py @@ -340,14 +340,10 @@ class SnippetTestCase(TestCase): Snippets without an expiration date wont get deleted automatically. """ data = self.valid_form_data() + data['expires'] = 'never' self.client.post(self.new_url, data, follow=True) self.assertEqual(Snippet.objects.count(), 1) - - s = Snippet.objects.all()[0] - s.expires = None - s.save() - management.call_command('cleanup_snippets') self.assertEqual(Snippet.objects.count(), 1) diff --git a/dpaste/views.py b/dpaste/views.py index 11d05ea..3183540 100644 --- a/dpaste/views.py +++ b/dpaste/views.py @@ -16,7 +16,7 @@ from django.views.defaults import (page_not_found as django_page_not_found, server_error as django_server_error) from django.views.decorators.csrf import csrf_exempt -from dpaste.forms import SnippetForm +from dpaste.forms import SnippetForm, get_expire_values, EXPIRE_CHOICES from dpaste.models import Snippet, ONETIME_LIMIT from dpaste.highlight import LEXER_WORDWRAP, LEXER_LIST from dpaste.highlight import LEXER_DEFAULT, LEXER_KEYS @@ -285,6 +285,7 @@ FORMAT_MAPPING = { def snippet_api(request): content = request.POST.get('content', '').strip() lexer = request.REQUEST.get('lexer', LEXER_DEFAULT).strip() + expires = request.REQUEST.get('expires', '').strip() format = request.REQUEST.get('format', 'default').strip() if not content: @@ -294,10 +295,21 @@ def snippet_api(request): return HttpResponseBadRequest('Invalid lexer given. Valid lexers are: %s' % ', '.join(LEXER_KEYS)) + if expires: + expire_options = [str(i) for i in dict(EXPIRE_CHOICES).keys()] + if not expires in expire_options: + return HttpResponseBadRequest('Invalid expire choice "{}" given. ' + 'Valid values are: {}'.format(expires, ', '.join(expire_options))) + expires, expire_type = get_expire_values(expires) + else: + expires = datetime.datetime.now() + datetime.timedelta(seconds=60 * 60 * 24 * 30) + expire_type = Snippet.EXPIRE_TIME + s = Snippet.objects.create( content=content, lexer=lexer, - expires=datetime.datetime.now()+datetime.timedelta(seconds=60*60*24*30) + expires=expires, + expire_type=expire_type, ) s.save()