Login

Template filter for internal links in TextFields

Author:
bobtiki
Posted:
November 8, 2014
Language:
Python
Version:
1.7
Score:
0 (after 0 ratings)

Allows for in internal link markup to be saved inside markdown-formatted Textfield entries.

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 own website, and a basic version was shared as the answer to this Stack Overflow question.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 11 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 7 months ago
  5. Help text hyperlinks by sa2812 1 year, 8 months ago

Comments

Please login first before commenting.