Added option to keep snippets forever.

This commit is contained in:
Martin Mahner 2014-01-11 16:00:11 +01:00
parent 25a83cf0f8
commit 964e1b64c7
10 changed files with 110 additions and 12 deletions

View file

@ -1,6 +1,11 @@
Changelog Changelog
========= =========
2.4 (dev)
----------------
* Added an option to keep snippets forever
2.3 (2014-01-07) 2.3 (2014-01-07)
---------------- ----------------

View file

@ -9,7 +9,7 @@ possible to be installed into an existing Django project like a regular app.
Contents: Contents:
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 1
testing testing
integration integration

View file

@ -35,6 +35,9 @@ Finally just ``syncdb`` or if you use South, migrate::
manage.py migrate dpaste manage.py migrate dpaste
Purge expired snippets
======================
Do not forget to setup a cron job to purge expired snippets. You need to Do not forget to setup a cron job to purge expired snippets. You need to
run the management command ``cleanup_snippets``. A cron job I use looks like:: run the management command ``cleanup_snippets``. A cron job I use looks like::

View file

@ -55,6 +55,15 @@ behavior without touching the code:
(3600 * 24 * 30 * 12 * 100, ugettext(u'100 Years')), (3600 * 24 * 30 * 12 * 100, ugettext(u'100 Years')),
) )
You can keep snippets forever when you set the choice key to ``never``.
The management command will ignore these snippets::
ugettext = lambda s: s
DPASTE_EXPIRE_CHOICES = (
(3600, ugettext(u'In one hour')),
(u'never', ugettext(u'Never')),
)
``DPASTE_EXPIRE_DEFAULT`` ``DPASTE_EXPIRE_DEFAULT``
The key of the default value of ``DPASTE_EXPIRE_CHOICES``. Default: The key of the default value of ``DPASTE_EXPIRE_CHOICES``. Default:
``3600 * 24 * 30 * 12 * 100`` or simpler: ``DPASTE_EXPIRE_CHOICES[2][0]``. ``3600 * 24 * 30 * 12 * 100`` or simpler: ``DPASTE_EXPIRE_CHOICES[2][0]``.

View file

@ -61,7 +61,6 @@ class SnippetForm(forms.ModelForm):
if 'l' in request.GET and request.GET['l'] in LEXER_KEYS: if 'l' in request.GET and request.GET['l'] in LEXER_KEYS:
self.fields['lexer'].initial = request.GET['l'] self.fields['lexer'].initial = request.GET['l']
def clean_content(self): def clean_content(self):
content = self.cleaned_data.get('content', '') content = self.cleaned_data.get('content', '')
if content.strip() == '': if content.strip() == '':
@ -75,6 +74,12 @@ class SnippetForm(forms.ModelForm):
raise forms.ValidationError('This snippet was identified as Spam.') raise forms.ValidationError('This snippet was identified as Spam.')
return self.cleaned_data return self.cleaned_data
def clean_expire_options(self):
expires = self.cleaned_data['expire_options']
if expires == u'never':
return None
return expires
def save(self, parent=None, *args, **kwargs): def save(self, parent=None, *args, **kwargs):
MAX_SNIPPETS_PER_USER = getattr(settings, 'DPASTE_MAX_SNIPPETS_PER_USER', 15) MAX_SNIPPETS_PER_USER = getattr(settings, 'DPASTE_MAX_SNIPPETS_PER_USER', 15)
@ -82,9 +87,12 @@ class SnippetForm(forms.ModelForm):
if parent: if parent:
self.instance.parent = parent self.instance.parent = parent
# Add expire datestamp # Add expire datestamp. None indicates 'keep forever', use the default
self.instance.expires = datetime.datetime.now() + \ # null state of the db column for that.
datetime.timedelta(seconds=int(self.cleaned_data['expire_options'])) expires = self.cleaned_data['expire_options']
if expires:
self.instance.expires = datetime.datetime.now() + \
datetime.timedelta(seconds=int(expires))
# Save snippet in the db # Save snippet in the db
super(SnippetForm, self).save(*args, **kwargs) super(SnippetForm, self).save(*args, **kwargs)

View file

@ -12,7 +12,10 @@ class Command(LabelCommand):
help = "Purges snippets that are expired" help = "Purges snippets that are expired"
def handle(self, *args, **options): def handle(self, *args, **options):
deleteable_snippets = Snippet.objects.filter(expires__lte=datetime.datetime.now()) deleteable_snippets = Snippet.objects.filter(
expires__isnull=False,
expires__lte=datetime.datetime.now()
)
sys.stdout.write(u"%s snippets gets deleted:\n" % deleteable_snippets.count()) sys.stdout.write(u"%s snippets gets deleted:\n" % deleteable_snippets.count())
for d in deleteable_snippets: for d in deleteable_snippets:
sys.stdout.write(u"- %s (%s)\n" % (d.secret_id, d.expires)) sys.stdout.write(u"- %s (%s)\n" % (d.secret_id, d.expires))

View file

@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Changing field 'Snippet.expires'
db.alter_column('dpaste_snippet', 'expires', self.gf('django.db.models.fields.DateTimeField')(null=True))
# Changing field 'Snippet.secret_id'
db.alter_column('dpaste_snippet', 'secret_id', self.gf('django.db.models.fields.CharField')(max_length=255, null=True))
# Changing field 'Snippet.published'
db.alter_column('dpaste_snippet', 'published', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True))
def backwards(self, orm):
# Changing field 'Snippet.expires'
db.alter_column('dpaste_snippet', 'expires', self.gf('django.db.models.fields.DateTimeField')(default=None))
# Changing field 'Snippet.secret_id'
db.alter_column('dpaste_snippet', 'secret_id', self.gf('django.db.models.fields.CharField')(default='', max_length=255))
# Changing field 'Snippet.published'
db.alter_column('dpaste_snippet', 'published', self.gf('django.db.models.fields.DateTimeField')())
models = {
u'dpaste.snippet': {
'Meta': {'ordering': "('-published',)", 'object_name': 'Snippet'},
'content': ('django.db.models.fields.TextField', [], {}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
u'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'lexer': ('django.db.models.fields.CharField', [], {'default': "'python'", 'max_length': '30'}),
u'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': u"orm['dpaste.Snippet']"}),
'published': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
u'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'secret_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
u'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
}
}
complete_apps = ['dpaste']

View file

@ -18,11 +18,11 @@ def generate_secret_id(length=L, alphabet=T):
return ''.join([R.choice(alphabet) for i in range(length)]) return ''.join([R.choice(alphabet) for i in range(length)])
class Snippet(models.Model): class Snippet(models.Model):
secret_id = models.CharField(_(u'Secret ID'), max_length=255, blank=True) secret_id = models.CharField(_(u'Secret ID'), max_length=255, blank=True, null=True)
content = models.TextField(_(u'Content'), ) content = models.TextField(_(u'Content'))
lexer = models.CharField(_(u'Lexer'), max_length=30, default=LEXER_DEFAULT) lexer = models.CharField(_(u'Lexer'), max_length=30, default=LEXER_DEFAULT)
published = models.DateTimeField(_(u'Published'), blank=True) published = models.DateTimeField(_(u'Published'), auto_now_add=True)
expires = models.DateTimeField(_(u'Expires'), blank=True) expires = models.DateTimeField(_(u'Expires'), blank=True, null=True)
parent = models.ForeignKey('self', null=True, blank=True, related_name='children') parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
class Meta: class Meta:
@ -37,8 +37,7 @@ class Snippet(models.Model):
return self.is_root_node() and not self.get_children() return self.is_root_node() and not self.get_children()
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.pk: if not self.secret_id:
self.published = datetime.now()
self.secret_id = generate_secret_id() self.secret_id = generate_secret_id()
super(Snippet, self).save(*args, **kwargs) super(Snippet, self).save(*args, **kwargs)

View file

@ -310,6 +310,22 @@ class SnippetTestCase(TestCase):
management.call_command('cleanup_snippets') management.call_command('cleanup_snippets')
self.assertEqual(Snippet.objects.count(), 1) self.assertEqual(Snippet.objects.count(), 1)
def test_delete_management_snippet_that_never_expires_will_not_get_deleted(self):
"""
Snippets without an expiration date wont get deleted automatically.
"""
data = self.valid_form_data()
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)
def test_highlighting(self): def test_highlighting(self):
# You can pass any lexer to the pygmentize function and it will # You can pass any lexer to the pygmentize function and it will
# never fail loudly. # never fail loudly.

View file

@ -1,3 +1,9 @@
# -----------------------------------------------------------------------------
# These requirements are only required for local testing or development.
# To use dpaste it's enough to install the package, all, and only the
# necessary dependencies are installed automatically.
# -----------------------------------------------------------------------------
# Project dependencies # Project dependencies
django==1.6.1 django==1.6.1
django-mptt==0.6.0 django-mptt==0.6.0