Skip to content
This repository has been archived by the owner on Jan 9, 2018. It is now read-only.

Consolidated the changes from many of the forks #1

Open
wants to merge 66 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
5e24c32
Add OEmbedField.
DrMeers Oct 13, 2010
b181765
Add support for SoundCloud and official.fm
DrMeers Oct 13, 2010
fcbe4e2
repair usage of filter without args
yedpodtrzitko Nov 16, 2010
e986823
added method for fetching dict from provider without embedding
Feb 11, 2011
78911f3
Merge branch 'master' of github.com:jezdez/django-oembed
DrMeers Jun 10, 2011
a4f5cd4
Replace trailing \\S* components with whitelisted characters from RFC…
DrMeers Jun 10, 2011
73d2501
Apparently skipped a lot of trailing \\S patterns in previous commit.
DrMeers Jul 3, 2011
34c48cd
Merge remote-tracking branch 'tttallis/master' into HEAD
Aramgutang Aug 17, 2011
a5ce33d
Cleaned-up the new fetch_dict helper function and optimised a few DB …
Aramgutang Aug 18, 2011
4748922
A bit of PEP-8-ification and removed unused views.py.
Aramgutang Aug 18, 2011
95240e3
Merged in fork by yedpodtrzitko, and resolved conflict in core.py.
Aramgutang Aug 18, 2011
febaef0
No need to try simplejson first, then django.utils.simplejson, the la…
Aramgutang Aug 18, 2011
2bac165
Increased the DRYness of building the oEmbed URL, and switched to usi…
Aramgutang Aug 18, 2011
5ae922e
Better linebreaks in the comments.
Aramgutang Aug 18, 2011
fd2159b
Tweetphoto is no more. Removed from tests.
Aramgutang Aug 18, 2011
ece8771
Added everyone whose changes I merged in to CONTRIBUTORS.
Aramgutang Aug 18, 2011
b6244ab
Accept youtu.be URLs.
DrMeers Aug 24, 2011
d0cadbc
Store JSON in StoredOEmbed. *Now uses South*. And allows thumbnail_ur…
DrMeers Aug 24, 2011
ea8207c
Python 2.5-compatible parse_qs import.
DrMeers Aug 25, 2011
e23bce5
removed OohEmbed providers, as the service is now defunct
tttallis Feb 21, 2012
abc75f5
On line 81 there's a settings.DEBUG without the corresponding import …
Mar 31, 2012
2014fc1
Fixed vimeo regex
Mar 31, 2012
9362349
Needed for line 81
Mar 31, 2012
f005ed9
Access any property of a StoredOEmbed object in template
Mar 31, 2012
414efe5
Basic cleaning
Mar 31, 2012
295fa1e
Basic cleaning 2
Mar 31, 2012
96911ae
Default not video image
Mar 31, 2012
e3353fd
New blip.tv endpoint
Mar 31, 2012
1eb566f
Better the way it was
Mar 31, 2012
61202fa
As noted by @Aramgutang, it should be from django.conf import settings
Mar 31, 2012
95131ad
Fixing ValueError: Invalid \escape: line 136 column 45 (char 4283)
Apr 2, 2012
5d47370
Update oembed/fixtures/initial_data.json
Apr 9, 2012
597a2d3
Update oembed/fixtures/initial_data.json
Apr 9, 2012
fec30f3
passed matched url to template
Jun 13, 2012
2d0c9f4
new templates
Jun 13, 2012
2ab70e5
CSS class name fixed
Jun 13, 2012
3339794
urlunquote_plus
Jun 13, 2012
802353d
compatible with Django 1.3
Jun 13, 2012
c25a9e9
new argument template_dir='oembed' for oembed.core.replace(). Oembed …
Jun 15, 2012
84b2d21
Decoding HTML Entities to Text for wikipedia
Jun 15, 2012
2741c81
Merge commit '61202faab1de8f0eddcbe16e1d42fc7652772bf6'
Jul 22, 2012
d25c169
Prevent deprecation warnings on Django 1.5 when marking the template …
Aramgutang Jan 31, 2013
8874789
use timezone aware datetime when USE_TZ is True
wil Feb 19, 2013
a52822a
use Twitter-native OEmbed URL, recognise new Twitter URL formats
wil Feb 19, 2013
901c86f
vimeo regex: allow channel videos
wil Feb 20, 2013
436343d
one slash too many
wil Feb 20, 2013
27f8a6d
Update oembed filter registration to non-deprecated form.
DrMeers May 21, 2013
5b8c667
Merge branch 'master' of github.com:ixc/django-oembed
DrMeers Aug 2, 2013
1213437
Update to allow https links as import for oembed link will export // …
Nov 26, 2013
c86adcc
simplejson is deprecated
DrMeers Nov 27, 2013
5bc4b44
Added verbose names to fix admin formatting
Nov 29, 2013
c7f02c3
Merge branch 'master' of https://github.com/tttallis/django-oembed
Dec 4, 2013
987f22e
Merge branch 'master' of https://github.com/DrMeers/django-oembed
Dec 4, 2013
8bbcfe7
allow http(s)
DrMeers Jun 9, 2014
cd4ed97
Update endpoints to use HTTPS where possible.
DrMeers Sep 16, 2014
17edd18
support older Django versions without django.utils.timezone.now
DrMeers Sep 16, 2014
6a32a53
colon!
DrMeers Sep 16, 2014
7027ca8
Add `.idea` to `.gitignore`
Apr 2, 2015
f235ca5
Replace deprecated `django.utils.simplejson` with standard `json` lib
Apr 2, 2015
698bf65
Add support for Django 1.7
Apr 2, 2015
fd67428
Add support for https URLs for all providers
Apr 28, 2015
39202b0
Merge remote-tracking branch 'ISKME/master'
Aramgutang Jul 27, 2015
d55d3d5
Merge remote-tracking branch 'drmeers/master'
Aramgutang Jul 27, 2015
83b18ae
add missing migration
Apr 8, 2016
6b78f97
Django 1.11 updates.
mrmachine Jan 16, 2019
ef3c035
Merge django-1-11 into master
mrmachine Jan 16, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -7,3 +7,5 @@
build
dist
*.egg-info

/.idea/
6 changes: 5 additions & 1 deletion CONTRIBUTORS.txt
Expand Up @@ -4,4 +4,8 @@ Jannis Leidel <jannis@leidel.info>
Nathan Borror
Thejaswi Puthraya
Brian Rosner
Jonathan Stasiak
Jonathan Stasiak
Simon Meers
Thomas Ashelford
Jiří Suchan
Aram Dulyan
116 changes: 86 additions & 30 deletions oembed/core.py
@@ -1,21 +1,31 @@
import gzip
import logging
import os
import re
import urllib2
import gzip
import urlparse
try:
import json
except ImportError:
from django.utils import simplejson as json
try:
from urlparse import parse_qs
except ImportError:
# (copied to urlparse from cgi in 2.6)
from cgi import parse_qs
from heapq import heappush, heappop
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
try:
import simplejson
except ImportError:
from django.utils import simplejson

from django.conf import settings
from django.utils.http import urlencode
from django.utils.safestring import mark_safe
from oembed.models import ProviderRule, StoredOEmbed
from django.template.loader import render_to_string
import logging

from .models import ProviderRule, StoredOEmbed

logger = logging.getLogger("oembed core")

END_OVERRIDES = (')', ',', '.', '>', ']', ';')
Expand Down Expand Up @@ -64,28 +74,28 @@ def match_compare(x, y):
prev_end = 0
iter_dict = dict((r, r.finditer(text)) for r in regex_list)

# a heapq containing matches
# A heapq containing matches
matches = []

# bootstrap the search with the first hit for each iterator
# Bootstrap the search with the first hit for each iterator
for regex, iterator in iter_dict.items():
try:
match = iterator.next()
heappush(matches, (match.start(), match))
except StopIteration:
iter_dict.pop(regex)

# process matches, revisiting each iterator from which a match is used
# Process matches, revisiting each iterator from which a match is used
while matches:
# get the earliest match
# Get the earliest match
start, match = heappop(matches)
end = match.end()
if start > prev_end:
# yield the text from current location to start of match
# Yield the text from current location to start of match
yield (-1, text[prev_end:start])
# yield the match
# Yield the match
yield (regex_list.index(match.re), text[start:end])
# get the next match from the iterator for this match
# Get the next match from the iterator for this match
if match.re in iter_dict:
try:
newmatch = iter_dict[match.re].next()
Expand All @@ -94,25 +104,71 @@ def match_compare(x, y):
iter_dict.pop(match.re)
prev_end = end

# yield text from end of last match to end of text
# Yield text from end of last match to end of text
last_bit = text[prev_end:]
if len(last_bit) > 0:
yield (-1, last_bit)

def replace(text, max_width=MAX_WIDTH, max_height=MAX_HEIGHT):
def build_url(endpoint, url, max_width, max_height):
# Split up the URL and extract GET parameters as a dictionary
split_url = urlparse.urlsplit(endpoint)
params = parse_qs(split_url[3])
params.update({
'url': url,
'maxwidth': max_width,
'maxheight': max_height,
'format': FORMAT,
})
# Put the URL back together with the new params and return it
params = urlencode(params, doseq=1)
return urlparse.urlunsplit(split_url[:3] + (params,) + split_url[4:])

def fetch_dict(url, max_width=None, max_height=None):
"""
Returns the response from the oEmbed provider as a dictionary for the
give oEmbeddable URL.

Returns None if URL could not be matched to an oEmbed provider.

This does not take advantage of the StoredOEmbed cache, since the cached
objects don't contain all the information from the JSON. If you find
yourself using this function a lot, consider rolling you own caching.
"""
if not max_width:
max_width = MAX_WIDTH
if not max_height:
max_height = MAX_HEIGHT
rule = None
for provider in ProviderRule.objects.only('regex'):
if re.match(provider.regex, url):
rule = provider
break
if rule is not None:
oembedurl = build_url(rule.endpoint, url, max_width, max_height)
# Fetch the link and parse the JSON.
return json.loads(fetch(oembedurl))

def replace(text, max_width=None, max_height=None, template_dir='oembed'):
"""
Scans a block of text, replacing anything matched by a ``ProviderRule``
pattern with an OEmbed html snippet, if possible.

Templates should be stored at oembed/{format}.html, so for example:
Templates should be stored at {template_dir}/{format}.html, so for example:

oembed/video.html
or
oembed/inline/video.html

These templates are passed a context variable, ``response``, which is a
dictionary representation of the response.
"""
if not max_width:
max_width = MAX_WIDTH
if not max_height:
max_height = MAX_HEIGHT

rules = list(ProviderRule.objects.all())
patterns = [re.compile(r.regex, re.I) for r in rules] # Compiled patterns from the rules
patterns = [re.compile(r.regex, re.I | re.U) for r in rules] # Compiled patterns from the rules
parts = [] # The parts that we will assemble into the final return value.
indices = [] # List of indices of parts that need to be replaced with OEmbed stuff.
indices_rules = [] # List of indices into the rules in order for which index was gotten by.
Expand All @@ -138,9 +194,10 @@ def replace(text, max_width=MAX_WIDTH, max_height=MAX_HEIGHT):
if to_append:
parts.append(to_append)
index += 1
# Now we fetch a list of all stored patterns, and put it in a dictionary
# Now we fetch a list of all stored patterns, and put it in a dictionary
# mapping the URL to to the stored model instance.
for stored_embed in StoredOEmbed.objects.filter(match__in=urls, max_width=max_width, max_height = max_height):
for stored_embed in StoredOEmbed.objects.filter(
match__in=urls, max_width=max_width, max_height=max_height):
stored[stored_embed.match] = stored_embed
# Now we're going to do the actual replacement of URL to embed.
for i, id_to_replace in enumerate(indices):
Expand All @@ -152,24 +209,23 @@ def replace(text, max_width=MAX_WIDTH, max_height=MAX_HEIGHT):
parts[id_to_replace] = stored[part].html
except KeyError:
try:
# Build the URL based on the properties defined in the OEmbed spec.
sep = "?" in rule.endpoint and "&" or "?"
q = urlencode({"url": part,
"maxwidth": max_width,
"maxheight": max_height,
"format": FORMAT})
url = u"%s%s%s" % (rule.endpoint, sep, q)
url = build_url(rule.endpoint, part, max_width, max_height)
# Fetch the link and parse the JSON.
resp = simplejson.loads(fetch(url))
json_string = fetch(url)
resp = json.loads(json_string)
# Depending on the embed type, grab the associated template and
# pass it the parsed JSON response as context.
replacement = render_to_string('oembed/%s.html' % resp['type'], {'response': resp})
replacement = render_to_string(
os.path.join(template_dir, '{0}.html'.format(resp['type'])),
{'response': resp, 'match': part, }
)
if replacement:
stored_embed = StoredOEmbed.objects.create(
match = part,
max_width = max_width,
max_height = max_height,
html = replacement,
json = json_string,
)
stored[stored_embed.match] = stored_embed
parts[id_to_replace] = replacement
Expand All @@ -182,4 +238,4 @@ def replace(text, max_width=MAX_WIDTH, max_height=MAX_HEIGHT):
except urllib2.HTTPError:
parts[id_to_replace] = part
# Combine the list into one string and return it.
return mark_safe(u''.join(parts))
return mark_safe(u''.join(parts).replace('http://','//'))
31 changes: 31 additions & 0 deletions oembed/fields.py
@@ -0,0 +1,31 @@
import re

from django.core import exceptions
from django.db import models

from .models import ProviderRule

class OEmbedField(models.URLField):
"""
A URL pointing to an oEmbed provider.

See http://www.oembed.com/ for information on providers
"""

description = "A URL pointing to an oEmbed provider"

def validate(self, value, model_instance):
for pattern in ProviderRule.objects.values_list('regex', flat=True):
if re.match(pattern, value):
return
raise exceptions.ValidationError('Not a valid oEmbed link')


try:
from south.modelsinspector import add_introspection_rules
except ImportError:
pass # No south, nevermind
else:
# Tell south to treat OEmbedFields just like URLFields
rules = ['^%s\.OEmbedField' % (__name__.replace('.','\.'),)]
add_introspection_rules([], rules)