Created
November 9, 2014 00:41
-
-
Save swizzlevixen/595a73f339c0adbe8c4d to your computer and use it in GitHub Desktop.
Django Internal Links for TextFields
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django import template | |
from django.core.urlresolvers import reverse | |
from my.views import * | |
register = template.Library() | |
@register.filter | |
def internal_links(value): | |
""" | |
Takes a markdown textfield, and searches for internal links in the format: | |
{{film:alien-1979}} | |
...where "film" is the designation for a model, | |
and "alien-1979" is the slug for a given object | |
NOTE: Process BEFORE markdown | |
If it is inside a markdown link, | |
it will resolve with the link text as intended: | |
[the first Alien movie]({{film:alien-1979}}) | |
[the first Alien movie](/cinedex/film/alien-1979/) | |
If it is by itself, it will resolve to a linked name: | |
{{film:alien-1979}} | |
[Alien (1979)](/cinedex/film/alien-1979/) | |
:param value: | |
:return: | |
""" | |
try: | |
import re | |
# Pattern(s) inside a markdown link first | |
# e.g. [link text here]({{film:alien-1979}}) | |
pattern = '\[.+\]\({{\S+:\S+}}\)' | |
p = re.compile(pattern) | |
text_linked = p.sub(localurl_markdown, value) | |
# After we replace those, find pattern(s) by itself | |
# e.g. {{film:alien-1979}} | |
pattern = '{{\S+:\S+}}' | |
p = re.compile(pattern) | |
#replace the captured pattern(s) with the new markdown link | |
return p.sub(localurl, text_linked) | |
except: | |
# Link lookups fail individually, but just in case there's | |
# some massive failure, just display the original text | |
return value | |
def localurlpattern(string): | |
# Strip off the {{ and }} | |
string = string[2:-2] | |
# Separate the link type and the slug | |
link_type, link_slug = string.split(":") | |
# figure out what view we need to display for the link_type | |
# Dictionary contains lookup as - link_type: viewname | |
# "viewname can be a string containing the Python path to the view object, a URL pattern name, or the callable view object." | |
# see https://docs.djangoproject.com/en/dev/ref/urlresolvers/#django.core.urlresolvers.reverse | |
link_types_views = { | |
'film': 'film_detail', | |
'person': 'person_detail', | |
'company': 'company_detail', | |
'term': 'term_detail', | |
} | |
# TODO: Maybe add error handling for a bad link_type, and try to look it up anyway | |
link_url = reverse(link_types_views[link_type], args=(link_slug,)) | |
entity = get_object_or_404(Entity, slug=link_slug) | |
if link_type == 'film': | |
# If it's a film, the name should be in italics and include the year. | |
link_name = "*" + entity.name + "* (" + str(entity.release_date.year) + ")" | |
else: | |
link_name = entity.name | |
# Return name and link_url as part of a dictionary | |
link_dict = {'name': link_name, 'url': link_url} | |
return link_dict | |
def localurl(match): | |
string = match.group() | |
try: | |
link_dict = localurlpattern(string) | |
markdown_link = "[" + link_dict['name'] + "](" + link_dict['url'] + ")" | |
return markdown_link | |
except: | |
# The lookup has failed, so let's send back a notice that it's broken | |
print('Broken internal_links localurl to ' + string) | |
markdown_link = "[***[broken link to " + string[2:-2] + "]***](#" + string[2:-2] + ")" | |
return markdown_link | |
def localurl_markdown(match): | |
string = match.group() | |
markdown_link = "" | |
# Grab the link text and link pattern | |
p_obj = re.search(r'\[(.+)\]\(({{\S+:\S+}})\)', string) | |
try: | |
if p_obj: | |
link_dict = localurlpattern(p_obj.group(2)) | |
markdown_link = "[" + p_obj.group(1) + "](" + link_dict['url'] + ")" | |
return markdown_link | |
except: | |
# The lookup has failed, so let's send back a notice that it's broken | |
print('Broken internal_links localurl_markdown to ' + string) | |
if p_obj: | |
markdown_link = "[" + p_obj.group(1) + " ***[broken link to " + p_obj.group(2)[2:-2] + "]***](#" + p_obj.group(2)[2:-2] + ")" | |
return markdown_link | |
else: | |
return string |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Allows for an internal link markup to be saved inside markdown-formatted Textfield entries. The links can be used alone, or inside a markdown-formatted link:
{{film:alien-1979}}
{{person:douglas-trumbull}}
[The first Alien movie]({{film:alien-1979}})
...will become (at least on my website), respectively:
[*Alien* (1979)](/cinedex/film/alien-1979)
[Douglas Trumbull](/cinedex/person/douglas-trumbull)
[The first Alien movie](/cinedex/film/alien-1979)
Using the filter, the link is only looked up at display time, so if your view's URL has changed, that should automatically update with the
reverse()
lookup.You could tweak the regex pattern to match whatever link markup you prefer. I also use Markdown to process my description fields, so I make the link return a markdown-formatted link instead of HTML, but you could tweak that too. If you use Markdown, you'd want to put this filter first.
So to display a description TextField with internal links, in the template would be something like this:
{{ entity.description|internal_links|markdown }}
(See the Django docs on writing your own custom filters for more details on writing and registering filters.)
Written for my movie podcast website, The Optical, and a basic version was shared as the answer to this Stack Overflow question, and the canonical version appears on my blog.