diff options
Diffstat (limited to 'news')
-rw-r--r-- | news/admin.py | 9 | ||||
-rw-r--r-- | news/migrations/0003_new_date_columns_precision.py | 4 | ||||
-rw-r--r-- | news/migrations/0005_add_slugs.py | 4 | ||||
-rw-r--r-- | news/migrations/0011_auto__add_field_news_safe_mode.py | 68 | ||||
-rw-r--r-- | news/migrations/0012_mark_old_news_safe_exempt.py | 73 | ||||
-rw-r--r-- | news/models.py | 22 | ||||
-rw-r--r-- | news/urls.py | 25 | ||||
-rw-r--r-- | news/views.py | 129 |
8 files changed, 238 insertions, 96 deletions
diff --git a/news/admin.py b/news/admin.py index 1b7de1d8..562c16d4 100644 --- a/news/admin.py +++ b/news/admin.py @@ -2,9 +2,14 @@ from django.contrib import admin from .models import News + class NewsAdmin(admin.ModelAdmin): - list_display = ('title', 'author', 'postdate', 'last_modified') - list_filter = ('postdate', 'author') + list_display = ('title', 'author', 'postdate', 'last_modified', 'safe_mode') + list_filter = ('postdate', 'author', 'safe_mode') search_fields = ('title', 'content') + date_hierarchy = 'postdate' + admin.site.register(News, NewsAdmin) + +# vim: set ts=4 sw=4 et: diff --git a/news/migrations/0003_new_date_columns_precision.py b/news/migrations/0003_new_date_columns_precision.py index 21b64443..1c97f488 100644 --- a/news/migrations/0003_new_date_columns_precision.py +++ b/news/migrations/0003_new_date_columns_precision.py @@ -1,14 +1,14 @@ # encoding: utf-8 -import datetime from south.db import db from south.v2 import SchemaMigration from django.db import models +from django.utils.timezone import now class Migration(SchemaMigration): def forwards(self, orm): # Adding field 'News.last_modified' - db.add_column('news', 'last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, default=datetime.datetime.now(), db_index=True, blank=True), keep_default=False) + db.add_column('news', 'last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, default=now(), db_index=True, blank=True), keep_default=False) # Changing field 'News.postdate' db.alter_column('news', 'postdate', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True)) # Adding index on 'News', fields ['postdate'] diff --git a/news/migrations/0005_add_slugs.py b/news/migrations/0005_add_slugs.py index 2a3b6174..96bd5213 100644 --- a/news/migrations/0005_add_slugs.py +++ b/news/migrations/0005_add_slugs.py @@ -11,7 +11,7 @@ class Migration(DataMigration): def forwards(self, orm): existing = list(orm.News.objects.values_list( 'slug', flat=True).distinct()) - for item in orm.News.objects.all(): + for item in orm.News.objects.defer('content').filter(slug=None): suffixed = slug = slugify(item.title) suffix = 1 while suffixed in existing: @@ -24,7 +24,7 @@ class Migration(DataMigration): item.save() def backwards(self, orm): - orm.News.obects.all.update(slug=None) + orm.News.objects.all.update(slug=None) models = { 'auth.group': { diff --git a/news/migrations/0011_auto__add_field_news_safe_mode.py b/news/migrations/0011_auto__add_field_news_safe_mode.py new file mode 100644 index 00000000..565c7adb --- /dev/null +++ b/news/migrations/0011_auto__add_field_news_safe_mode.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.add_column('news', 'safe_mode', + self.gf('django.db.models.fields.BooleanField')(default=True), + keep_default=True) + + def backwards(self, orm): + db.delete_column('news', 'safe_mode') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'news.news': { + 'Meta': {'ordering': "('-postdate',)", 'object_name': 'News', 'db_table': "'news'"}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'news_author'", 'on_delete': 'models.PROTECT', 'to': "orm['auth.User']"}), + 'content': ('django.db.models.fields.TextField', [], {}), + 'guid': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'postdate': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'safe_mode': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['news'] diff --git a/news/migrations/0012_mark_old_news_safe_exempt.py b/news/migrations/0012_mark_old_news_safe_exempt.py new file mode 100644 index 00000000..b2661cd8 --- /dev/null +++ b/news/migrations/0012_mark_old_news_safe_exempt.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +import markdown + +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + md = markdown.Markdown(safe_mode=True, enable_attributes=False) + magic = md.html_replacement_text + items = orm.News.objects.all() + has_html = [item.pk for item in items if magic in md.convert(item.content)] + for pk in has_html: + orm.News.objects.filter(pk=pk).update(safe_mode=False) + + def backwards(self, orm): + orm.News.objects.all().update(safe_mode=True) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'news.news': { + 'Meta': {'ordering': "('-postdate',)", 'object_name': 'News', 'db_table': "'news'"}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'news_author'", 'on_delete': 'models.PROTECT', 'to': "orm['auth.User']"}), + 'content': ('django.db.models.fields.TextField', [], {}), + 'guid': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'postdate': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'safe_mode': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['news'] + symmetrical = True diff --git a/news/models.py b/news/models.py index 95026e1d..d51db7c7 100644 --- a/news/models.py +++ b/news/models.py @@ -1,8 +1,10 @@ +import markdown + from django.db import models from django.contrib.auth.models import User from django.contrib.sites.models import Site - -from main.utils import utc_now +from django.utils.safestring import mark_safe +from django.utils.timezone import now class News(models.Model): @@ -14,10 +16,15 @@ class News(models.Model): title = models.CharField(max_length=255) guid = models.CharField(max_length=255, editable=False) content = models.TextField() + safe_mode = models.BooleanField(default=True) def get_absolute_url(self): return '/news/%s/' % self.slug + def html(self): + return mark_safe(markdown.markdown( + self.content, safe_mode=self.safe_mode, enable_attributes=False)) + def __unicode__(self): return self.title @@ -25,17 +32,18 @@ class News(models.Model): db_table = 'news' verbose_name_plural = 'news' get_latest_by = 'postdate' - ordering = ['-postdate'] + ordering = ('-postdate',) + def set_news_fields(sender, **kwargs): news = kwargs['instance'] - now = utc_now() - news.last_modified = now + current_time = now() + news.last_modified = current_time if not news.postdate: - news.postdate = now + news.postdate = current_time # http://diveintomark.org/archives/2004/05/28/howto-atom-id news.guid = 'tag:%s,%s:%s' % (Site.objects.get_current(), - now.strftime('%Y-%m-%d'), news.get_absolute_url()) + current_time.strftime('%Y-%m-%d'), news.get_absolute_url()) # connect signals needed to keep cache in line with reality from main.utils import refresh_latest diff --git a/news/urls.py b/news/urls.py index 10020f31..0eec6d86 100644 --- a/news/urls.py +++ b/news/urls.py @@ -1,14 +1,25 @@ from django.conf.urls import patterns +from django.contrib.auth.decorators import permission_required +from .views import (NewsDetailView, NewsListView, + NewsCreateView, NewsEditView, NewsDeleteView) + urlpatterns = patterns('news.views', - (r'^$', 'news_list', {}, 'news-list'), - (r'^add/$', 'add'), - (r'^preview/$', 'preview'), + (r'^$', + NewsListView.as_view(), {}, 'news-list'), + + (r'^preview/$', 'preview'), # old news URLs, permanent redirect view so we don't break all links - (r'^(?P<object_id>\d+)/$', 'view_redirect'), - (r'^(?P<slug>[-\w]+)/$', 'view'), - (r'^(?P<slug>[-\w]+)/edit/$', 'edit'), - (r'^(?P<slug>[-\w]+)/delete/$', 'delete'), + (r'^(?P<object_id>\d+)/$', 'view_redirect'), + + (r'^add/$', + permission_required('news.add_news')(NewsCreateView.as_view())), + (r'^(?P<slug>[-\w]+)/$', + NewsDetailView.as_view()), + (r'^(?P<slug>[-\w]+)/edit/$', + permission_required('news.change_news')(NewsEditView.as_view())), + (r'^(?P<slug>[-\w]+)/delete/$', + permission_required('news.delete_news')(NewsDeleteView.as_view())), ) # vim: set ts=4 sw=4 et: diff --git a/news/views.py b/news/views.py index 7ac009ba..62d30fde 100644 --- a/news/views.py +++ b/news/views.py @@ -1,91 +1,68 @@ +import markdown + from django import forms -from django.contrib.auth.decorators import permission_required from django.http import HttpResponse from django.shortcuts import get_object_or_404, redirect -from django.template.defaultfilters import slugify -from django.views.decorators.cache import never_cache -from django.views.generic import list_detail, create_update -from django.views.generic.simple import direct_to_template - -import markdown +from django.views.decorators.http import require_POST +from django.views.generic import (DetailView, ListView, + CreateView, UpdateView, DeleteView) from .models import News +from main.utils import find_unique_slug + + +class NewsForm(forms.ModelForm): + class Meta: + model = News + exclude = ('id', 'slug', 'author', 'postdate', 'safe_mode') + + +class NewsDetailView(DetailView): + model = News + template_name = "news/view.html" + + +class NewsListView(ListView): + queryset = News.objects.all().select_related('author').defer('content') + template_name = "news/list.html" + paginate_by = 50 + + +class NewsCreateView(CreateView): + model = News + form_class = NewsForm + template_name = "news/add.html" + + def form_valid(self, form): + # special logic, we auto-fill the author and slug fields + newsitem = form.save(commit=False) + newsitem.author = self.request.user + newsitem.slug = find_unique_slug(News, newsitem.title) + newsitem.save() + return super(NewsCreateView, self).form_valid(form) + + +class NewsEditView(UpdateView): + model = News + form_class = NewsForm + template_name = "news/add.html" + + +class NewsDeleteView(DeleteView): + model = News + template_name = "news/delete.html" + success_url = "/news/" + def view_redirect(request, object_id): newsitem = get_object_or_404(News, pk=object_id) return redirect(newsitem, permanent=True) -def view(request, slug=None): - return list_detail.object_detail(request, News.objects.all(), - slug=slug, - template_name="news/view.html", - template_object_name='news') - -#TODO: May as well use a date-based list here sometime -def news_list(request): - return list_detail.object_list(request, - News.objects.all().select_related('author').defer('content'), - paginate_by=50, - template_name="news/list.html", - template_object_name="news") -class NewsForm(forms.ModelForm): - class Meta: - model = News - exclude = ('id', 'slug', 'author', 'postdate') - -def find_unique_slug(newsitem): - '''Attempt to find a unique slug for this news item.''' - existing = list(News.objects.values_list('slug', flat=True).distinct()) - - suffixed = slug = slugify(newsitem.title) - suffix = 0 - while suffixed in existing: - suffix += 1 - suffixed = "%s-%d" % (slug, suffix) - - return suffixed - -@permission_required('news.add_news') -@never_cache -def add(request): - if request.POST: - form = NewsForm(request.POST) - if form.is_valid(): - newsitem = form.save(commit=False) - newsitem.author = request.user - newsitem.slug = find_unique_slug(newsitem) - newsitem.save() - return redirect(newsitem) - else: - form = NewsForm() - return direct_to_template(request, 'news/add.html', { 'form': form }) - -@permission_required('news.delete_news') -@never_cache -def delete(request, slug): - return create_update.delete_object(request, - News, - slug=slug, - post_delete_redirect='/news/', - template_name='news/delete.html', - template_object_name='news') - -@permission_required('news.change_news') -@never_cache -def edit(request, slug): - return create_update.update_object(request, - slug=slug, - form_class=NewsForm, - template_name="news/add.html") - -@permission_required('news.change_news') -@never_cache +@require_POST def preview(request): - markup = '' - if request.POST: - data = request.POST.get('data', '') - markup = markdown.markdown(data) + data = request.POST.get('data', '') + markup = markdown.markdown(data, safe_mode=True, enable_attributes=False) return HttpResponse(markup) # vim: set ts=4 sw=4 et: |