From 2f1663157ec13b0a6680ccfd8a01eebf8c092a24 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Tue, 28 Jan 2014 17:03:17 +0000 Subject: [PATCH] Move verdantembeds to the django-wagtail package --- .../wagtailadmin/pages/_editor_js.html | 2 +- wagtail/wagtailcore/rich_text.py | 2 +- wagtail/wagtailembeds/__init__.py | 2 + wagtail/wagtailembeds/embeds.py | 40 +++++++++++++ wagtail/wagtailembeds/format.py | 26 +++++++++ wagtail/wagtailembeds/forms.py | 15 +++++ .../wagtailembeds/migrations/0001_initial.py | 58 +++++++++++++++++++ wagtail/wagtailembeds/migrations/__init__.py | 0 wagtail/wagtailembeds/models.py | 27 +++++++++ .../hallo-plugins/hallo-verdantembeds.coffee | 36 ++++++++++++ .../wagtailembeds/chooser/chooser.html | 19 ++++++ .../wagtailembeds/chooser/chooser.js | 19 ++++++ .../wagtailembeds/chooser/embed_chosen.js | 4 ++ .../wagtailembeds/templatetags/__init__.py | 0 .../templatetags/embed_filters.py | 21 +++++++ wagtail/wagtailembeds/tests.py | 9 +++ wagtail/wagtailembeds/urls.py | 7 +++ wagtail/wagtailembeds/views/__init__.py | 0 wagtail/wagtailembeds/views/chooser.py | 37 ++++++++++++ 19 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 wagtail/wagtailembeds/__init__.py create mode 100644 wagtail/wagtailembeds/embeds.py create mode 100644 wagtail/wagtailembeds/format.py create mode 100644 wagtail/wagtailembeds/forms.py create mode 100644 wagtail/wagtailembeds/migrations/0001_initial.py create mode 100644 wagtail/wagtailembeds/migrations/__init__.py create mode 100644 wagtail/wagtailembeds/models.py create mode 100644 wagtail/wagtailembeds/static/wagtailembeds/js/hallo-plugins/hallo-verdantembeds.coffee create mode 100644 wagtail/wagtailembeds/templates/wagtailembeds/chooser/chooser.html create mode 100644 wagtail/wagtailembeds/templates/wagtailembeds/chooser/chooser.js create mode 100644 wagtail/wagtailembeds/templates/wagtailembeds/chooser/embed_chosen.js create mode 100644 wagtail/wagtailembeds/templatetags/__init__.py create mode 100644 wagtail/wagtailembeds/templatetags/embed_filters.py create mode 100644 wagtail/wagtailembeds/tests.py create mode 100644 wagtail/wagtailembeds/urls.py create mode 100644 wagtail/wagtailembeds/views/__init__.py create mode 100644 wagtail/wagtailembeds/views/chooser.py diff --git a/wagtail/wagtailadmin/templates/wagtailadmin/pages/_editor_js.html b/wagtail/wagtailadmin/templates/wagtailadmin/pages/_editor_js.html index cd4752e261..15bb265894 100644 --- a/wagtail/wagtailadmin/templates/wagtailadmin/pages/_editor_js.html +++ b/wagtail/wagtailadmin/templates/wagtailadmin/pages/_editor_js.html @@ -14,7 +14,7 @@ - + diff --git a/wagtail/wagtailcore/rich_text.py b/wagtail/wagtailcore/rich_text.py index 94f7b3cb1f..61a42a0f54 100644 --- a/wagtail/wagtailcore/rich_text.py +++ b/wagtail/wagtailcore/rich_text.py @@ -83,7 +83,7 @@ class MediaEmbedHandler(object): Given a dict of attributes from the tag, return the real HTML representation. """ - from verdantembeds import format + from wagtail.wagtailembeds import format if for_editor: return format.embed_to_editor_html(attrs['url']) else: diff --git a/wagtail/wagtailembeds/__init__.py b/wagtail/wagtailembeds/__init__.py new file mode 100644 index 0000000000..b75cbc491d --- /dev/null +++ b/wagtail/wagtailembeds/__init__.py @@ -0,0 +1,2 @@ +from .models import Embed +from .embeds import get_embed diff --git a/wagtail/wagtailembeds/embeds.py b/wagtail/wagtailembeds/embeds.py new file mode 100644 index 0000000000..c1b3b5b98c --- /dev/null +++ b/wagtail/wagtailembeds/embeds.py @@ -0,0 +1,40 @@ +from datetime import datetime +from django.conf import settings +from embedly import Embedly +from .models import Embed + + +def get_embed(url, max_width=None): + # Check database + try: + return Embed.objects.get(url=url, max_width=max_width) + except Embed.DoesNotExist: + pass + + # Call embedly API + client = Embedly(key=settings.EMBEDLY_KEY) + if max_width is not None: + oembed = client.oembed(url, maxwidth=max_width, better=False) + else: + oembed = client.oembed(url, better=False) + + # Check for error + if oembed.error: + return None + + # Save result to database + row, created = Embed.objects.get_or_create(url=url, max_width=max_width, + defaults={'type': oembed.type, 'title': oembed.title, 'thumbnail_url': oembed.thumbnail_url, 'width': oembed.width, 'height': oembed.height}) + + if oembed.type == 'photo': + html = '' % (oembed.url, ) + else: + html = oembed.html + + if html: + row.html = html + row.last_updated = datetime.now() + row.save() + + # Return new embed + return row \ No newline at end of file diff --git a/wagtail/wagtailembeds/format.py b/wagtail/wagtailembeds/format.py new file mode 100644 index 0000000000..cd629bb6ba --- /dev/null +++ b/wagtail/wagtailembeds/format.py @@ -0,0 +1,26 @@ +from __future__ import division # Use true division +from .embeds import get_embed +from django.utils.html import escape + + +def embed_to_frontend_html(url): + embed = get_embed(url) + if embed is not None: + # Work out ratio + if embed.width and embed.height: + ratio = str(embed.height / embed.width * 100) + "%" + else: + ratio = "0" + + # Build html + return '
%s
' % (ratio, embed.html) + else: + return '' + + +def embed_to_editor_html(url): + # Check that the embed exists + embed = get_embed(url) + if embed is None: + return '' + return '

%s

%s

' % (url, escape(embed.title), url, embed.thumbnail_url) \ No newline at end of file diff --git a/wagtail/wagtailembeds/forms.py b/wagtail/wagtailembeds/forms.py new file mode 100644 index 0000000000..64d289be2e --- /dev/null +++ b/wagtail/wagtailembeds/forms.py @@ -0,0 +1,15 @@ +from django import forms +from django.core.validators import URLValidator +from django.core.exceptions import ValidationError + + +def validate_url(url): + validator = URLValidator() + try: + validator(url) + except ValidationError: + raise ValidationError("Please enter a valid URL") + + +class EmbedForm(forms.Form): + url = forms.CharField(label="URL", validators=[validate_url]) \ No newline at end of file diff --git a/wagtail/wagtailembeds/migrations/0001_initial.py b/wagtail/wagtailembeds/migrations/0001_initial.py new file mode 100644 index 0000000000..48fa506e0e --- /dev/null +++ b/wagtail/wagtailembeds/migrations/0001_initial.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + depends_on = ( + ("wagtailcore", "0002_initial_data"), + ) + + def forwards(self, orm): + # Adding model 'Embed' + db.create_table(u'wagtailembeds_embed', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('url', self.gf('django.db.models.fields.URLField')(max_length=200)), + ('max_width', self.gf('django.db.models.fields.SmallIntegerField')(null=True, blank=True)), + ('type', self.gf('django.db.models.fields.CharField')(max_length=10)), + ('html', self.gf('django.db.models.fields.TextField')(blank=True)), + ('title', self.gf('django.db.models.fields.TextField')(blank=True)), + ('thumbnail_url', self.gf('django.db.models.fields.URLField')(max_length=200, null=True, blank=True)), + ('width', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), + ('height', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), + ('last_updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), + )) + db.send_create_signal(u'wagtailembeds', ['Embed']) + + # Adding unique constraint on 'Embed', fields ['url', 'max_width'] + db.create_unique(u'wagtailembeds_embed', ['url', 'max_width']) + + + def backwards(self, orm): + # Removing unique constraint on 'Embed', fields ['url', 'max_width'] + db.delete_unique(u'wagtailembeds_embed', ['url', 'max_width']) + + # Deleting model 'Embed' + db.delete_table(u'wagtailembeds_embed') + + + models = { + u'wagtailembeds.embed': { + 'Meta': {'unique_together': "(('url', 'max_width'),)", 'object_name': 'Embed'}, + 'height': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'html': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'max_width': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'thumbnail_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '10'}), + 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'width': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['wagtailembeds'] \ No newline at end of file diff --git a/wagtail/wagtailembeds/migrations/__init__.py b/wagtail/wagtailembeds/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/wagtail/wagtailembeds/models.py b/wagtail/wagtailembeds/models.py new file mode 100644 index 0000000000..c8f515dd06 --- /dev/null +++ b/wagtail/wagtailembeds/models.py @@ -0,0 +1,27 @@ +from django.db import models + + +EMBED_TYPES = ( + ('video', 'Video'), + ('photo', 'Photo'), + ('link', 'Link'), + ('rich', 'Rich'), +) + + +class Embed(models.Model): + url = models.URLField() + max_width = models.SmallIntegerField(null=True, blank=True) + type = models.CharField(max_length=10, choices=EMBED_TYPES) + html = models.TextField(blank=True) + title = models.TextField(blank=True) + thumbnail_url = models.URLField(null=True, blank=True) + width = models.IntegerField(null=True, blank=True) + height = models.IntegerField(null=True, blank=True) + last_updated = models.DateTimeField(auto_now=True) + + class Meta: + unique_together = ('url', 'max_width') + + def __unicode__(self): + return self.url \ No newline at end of file diff --git a/wagtail/wagtailembeds/static/wagtailembeds/js/hallo-plugins/hallo-verdantembeds.coffee b/wagtail/wagtailembeds/static/wagtailembeds/js/hallo-plugins/hallo-verdantembeds.coffee new file mode 100644 index 0000000000..841be3a74e --- /dev/null +++ b/wagtail/wagtailembeds/static/wagtailembeds/js/hallo-plugins/hallo-verdantembeds.coffee @@ -0,0 +1,36 @@ +# plugin for hallo.js to allow inserting embeds + +(($) -> + $.widget "IKS.halloverdantembeds", + options: + uuid: '' + editable: null + + populateToolbar: (toolbar) -> + widget = this + + # Create an element for holding the button + button = $('') + button.hallobutton + uuid: @options.uuid + editable: @options.editable + label: 'Embed' + icon: 'icon-media' + command: null + + # Append the button to toolbar + toolbar.append button + + button.on "click", (event) -> + lastSelection = widget.options.editable.getSelection() + insertionPoint = $(lastSelection.endContainer).parentsUntil('.richtext').last() + ModalWorkflow + url: '/admin/embeds/chooser/' # TODO: don't hard-code this, as it may be changed in urls.py + responses: + embedChosen: (embedData) -> + elem = $(embedData).get(0) + lastSelection.insertNode(elem) + if elem.getAttribute('contenteditable') == 'false' + insertRichTextDeleteControl(elem) + widget.options.editable.element.trigger('change') +)(jQuery) diff --git a/wagtail/wagtailembeds/templates/wagtailembeds/chooser/chooser.html b/wagtail/wagtailembeds/templates/wagtailembeds/chooser/chooser.html new file mode 100644 index 0000000000..d993291f3c --- /dev/null +++ b/wagtail/wagtailembeds/templates/wagtailembeds/chooser/chooser.html @@ -0,0 +1,19 @@ +{% load image_tags ellipsistrim%} + +
+

Insert embed

+
+ +
+
+
+ {% csrf_token %} +
    + {% for field in form %} + {% include "wagtailadmin/shared/field_as_li.html" with field=field %} + {% endfor %} +
  • +
+
+
+
\ No newline at end of file diff --git a/wagtail/wagtailembeds/templates/wagtailembeds/chooser/chooser.js b/wagtail/wagtailembeds/templates/wagtailembeds/chooser/chooser.js new file mode 100644 index 0000000000..dcdbb6771f --- /dev/null +++ b/wagtail/wagtailembeds/templates/wagtailembeds/chooser/chooser.js @@ -0,0 +1,19 @@ +function(modal) { + $('form.embed-form', modal.body).submit(function() { + var formdata = new FormData(this); + + $.ajax({ + url: this.action, + data: formdata, + processData: false, + contentType: false, + type: 'POST', + dataType: 'text', + success: function(response){ + modal.loadResponseText(response); + } + }); + + return false; + }); +} diff --git a/wagtail/wagtailembeds/templates/wagtailembeds/chooser/embed_chosen.js b/wagtail/wagtailembeds/templates/wagtailembeds/chooser/embed_chosen.js new file mode 100644 index 0000000000..d32fce5630 --- /dev/null +++ b/wagtail/wagtailembeds/templates/wagtailembeds/chooser/embed_chosen.js @@ -0,0 +1,4 @@ +function(modal) { + modal.respond('embedChosen', '{{ embed_html|safe }}'); + modal.close(); +} \ No newline at end of file diff --git a/wagtail/wagtailembeds/templatetags/__init__.py b/wagtail/wagtailembeds/templatetags/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/wagtail/wagtailembeds/templatetags/embed_filters.py b/wagtail/wagtailembeds/templatetags/embed_filters.py new file mode 100644 index 0000000000..d44f34dad4 --- /dev/null +++ b/wagtail/wagtailembeds/templatetags/embed_filters.py @@ -0,0 +1,21 @@ +import re +from django import template +from django.utils.safestring import mark_safe +from wagtail.wagtailembeds.embeds import get_embed + + +register = template.Library() + + +@register.filter +def embedly(url, max_width=None): + embed = get_embed(url, max_width=max_width) + if embed is not None: + return mark_safe(embed.html) + else: + return '' + + +@register.filter +def embed(url): + return embedly(url) \ No newline at end of file diff --git a/wagtail/wagtailembeds/tests.py b/wagtail/wagtailembeds/tests.py new file mode 100644 index 0000000000..94319d83b2 --- /dev/null +++ b/wagtail/wagtailembeds/tests.py @@ -0,0 +1,9 @@ +from .embeds import get_embed +from django.test import TestCase + + +class TestEmbeds(TestCase): + def test_get_embed(self): + # This test will fail if the video is removed or the title is changed + embed = get_embed('http://www.youtube.com/watch?v=S3xAeTmsJfg') + self.assertEqual(embed.title, 'Animation: Ferret dance (A series of tubes)') \ No newline at end of file diff --git a/wagtail/wagtailembeds/urls.py b/wagtail/wagtailembeds/urls.py new file mode 100644 index 0000000000..490ba1d6b1 --- /dev/null +++ b/wagtail/wagtailembeds/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import patterns, url + + +urlpatterns = patterns('wagtail.wagtailembeds.views', + url(r'^chooser/$', 'chooser.chooser', name='wagtailembeds_chooser'), + url(r'^chooser/upload/$', 'chooser.chooser_upload', name='wagtailembeds_chooser_upload'), +) diff --git a/wagtail/wagtailembeds/views/__init__.py b/wagtail/wagtailembeds/views/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/wagtail/wagtailembeds/views/chooser.py b/wagtail/wagtailembeds/views/chooser.py new file mode 100644 index 0000000000..a1cae3fe66 --- /dev/null +++ b/wagtail/wagtailembeds/views/chooser.py @@ -0,0 +1,37 @@ +from wagtail.wagtailadmin.modal_workflow import render_modal_workflow +from wagtail.wagtailembeds.forms import EmbedForm +from wagtail.wagtailembeds.format import embed_to_editor_html +from django.forms.util import ErrorList + + +def chooser(request): + form = EmbedForm() + + return render_modal_workflow(request, 'wagtailembeds/chooser/chooser.html', 'wagtailembeds/chooser/chooser.js',{ + 'form': form, + }) + + +def chooser_upload(request): + if request.POST: + form = EmbedForm(request.POST, request.FILES) + + if form.is_valid(): + embed_html = embed_to_editor_html(form.cleaned_data['url']) + if embed_html != "": + return render_modal_workflow( + request, None, 'wagtailembeds/chooser/embed_chosen.js', + {'embed_html': embed_html} + ) + else: + errors = form._errors.setdefault('url', ErrorList()) + errors.append('This URL is not recognised') + return render_modal_workflow(request, 'wagtailembeds/chooser/chooser.html', 'wagtailembeds/chooser/chooser.js',{ + 'form': form, + }) + else: + form = EmbedForm() + + return render_modal_workflow(request, 'wagtailembeds/chooser/chooser.html', 'wagtailembeds/chooser/chooser.js',{ + 'form': form, + }) \ No newline at end of file