Commits

Gregory Petukhov committed b91df5d

Add markdown support

Comments (0)

Files changed (4)

 from article.models import Article, Category
 
 class ArticleAdmin(admin.ModelAdmin):
-    list_display = ['title', 'category', 'user', 'is_list_visible', 'created', 'live_url',
+    list_display = ['title', 'category', 'user', 'markup',
+                    'is_list_visible', 'created', 'live_url',
                     'image_thumbnail']
     list_filter = ['category']
     search_fields = ['title', 'teaser', 'content']

article/migrations/0009_auto__add_field_article_markup.py

+# -*- 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):
+        # Adding field 'Article.markup'
+        db.add_column('article_article', 'markup',
+                      self.gf('django.db.models.fields.CharField')(default='html', max_length=10),
+                      keep_default=False)
+
+
+    def backwards(self, orm):
+        # Deleting field 'Article.markup'
+        db.delete_column('article_article', 'markup')
+
+
+    models = {
+        'article.article': {
+            'Meta': {'object_name': 'Article'},
+            'alias': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '20', 'blank': 'True'}),
+            'category': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'articles'", 'to': "orm['article.Category']"}),
+            'comment_count': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
+            'content': ('ckeditor.fields.RichTextField', [], {}),
+            'content_html': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'details_display_image': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}),
+            'is_list_visible': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+            'is_starred': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'markup': ('django.db.models.fields.CharField', [], {'default': "'html'", 'max_length': '10'}),
+            'meta_description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'meta_keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'default': 'None', 'max_length': '255'}),
+            'teaser': ('ckeditor.fields.RichTextField', [], {'blank': 'True'}),
+            'teaser_html': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'article.category': {
+            'Meta': {'object_name': 'Category'},
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'meta_description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'meta_keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'meta_title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'slug': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50'})
+        },
+        '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'})
+        }
+    }
+
+    complete_apps = ['article']

article/models.py

 from django.db import models
 from django.core.urlresolvers import reverse
 
+MARKUP_CHOICES = (
+    ('html', 'HTML'),
+    ('markdown', 'Mardown'),
+)
+
 class Article(models.Model):
     created = models.DateTimeField(auto_now_add=True)
     title = models.CharField(max_length=255)
     slug = models.SlugField(max_length=255, blank=False, default=None)
     category = models.ForeignKey('article.Category', related_name='articles')
-    teaser = RichTextField(blank=True)
-    content = RichTextField()
+    markup = models.CharField(choices=MARKUP_CHOICES, max_length=10,
+                              default='html')
+    teaser = models.TextField(blank=True)
+    content = models.TextField()
     teaser_html = models.TextField(editable=True, blank=True)
     content_html = models.TextField(editable=True, blank=True)
     image = models.ImageField(blank=True,

article/signals.py

 from lxml.html import fromstring, tostring
+from markdown2 import markdown
 
 from django.db.models.signals import pre_save
 from django.dispatch import receiver
 from article.models import Article
 
 def normalize_html(html):
-    if not html.strip():
+    tree = fromstring(html)
+    # remove leading empty P tags
+    for elem in tree.xpath('./*'):
+        if elem.tag != 'p':
+            break
+        if len(elem) == 0 and elem.text.strip() == "":
+            elem.getparent().remove(elem)
+        else:
+            break
+    return tostring(tree, encoding='utf-8')
+
+
+def render_source(markup, source):
+    if not source.strip():
         return ''
     else:
-        tree = fromstring(html)
-        # remove leading empty P tags
-        for elem in tree.xpath('./*'):
-            if elem.tag != 'p':
-                break
-            if len(elem) == 0 and elem.text.strip() == "":
-                elem.getparent().remove(elem)
-            else:
-                break
-        return tostring(tree, encoding='utf-8')
+        if markup == 'html':
+            return normalize_html(source)
+        elif markup == 'markdown':
+            return markdown(source, extras=['fenced-code-blocks'])
+        else:
+            raise Exception('Unknown markup: %s' % markup)
 
 
 @receiver(pre_save, sender=Article)
         head, tail = instance.content.split('@CUT@', 1)
         instance.teaser_html = head.strip()
         instance.content_html = tail.strip()
+
+        instance.teaser_html = render_source(instance.markup, instance.teaser_html)
+        instance.content_html = render_source(instance.markup, instance.content_html)
     else:
         instance.content_html = instance.content
-        if normalize_html(instance.teaser).strip():
+        if render_source(instance.markup, instance.teaser).strip():
             instance.teaser_html = instance.teaser
         else:
             instance.teaser_html = instance.content
 
-    instance.teaser_html = normalize_html(instance.teaser_html)
-    instance.content_html = normalize_html(instance.content_html)
+        instance.teaser_html = render_source(instance.markup, instance.teaser_html)
+        instance.content_html = render_source(instance.markup, instance.content_html)