mentions/internal links in Django - python

I have a bunch of models. All these models has a method get_absolute_url and a field text. I want to make internal links in the text field just like wikipedia does.
Wikipedia's internal links in pages only refer to other pages. I need to link to all my models.
I could make a pattern for internal links and replacing this pattern with a hardcoded url to an url but it's really not a good idea because the links can change. So it would be best if I could refer to get_absolute_url.
Another option would be to use a template tag to change a specific pattern to links.
How should it be done? Are there any open source projects in which this has already been done?

I wanted to answer this same problem just a few days ago, and I did it with a template filter. My links are relative URLs, not absolute, but you could tweak that pretty easily, and you could also tweak the regex pattern to match whatever link markup you prefer.
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.
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.)
As for the specific filter itself, I did it like this:
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 filters
for internal links in the format:
{{film:alien-1979}}
...where "film" is the designation for a link type (model),
and "alien-1979" is the slug for a given object
NOTE: Process BEFORE markdown, as it will resolve
to a markdown-formatted linked name:
[Alien](http://opticalpodcast.com/cinedex/film/alien-1979/)
:param value:
:return:
"""
try:
import re
pattern = '{{\S+:\S+}}'
p = re.compile(pattern)
#replace the captured pattern(s) with the markdown link
return p.sub(localurl, value)
except:
# If the link lookup fails, just display the original text
return value
def localurl(match):
string = match.group()
# Strip off the {{ and }}
string = string[2:-2]
# Separate the link type and the slug
link_type, link_slug = string.split(":")
link_view = ''
# figure out what view we need to display
# for the link type
if(link_type == 'film'):
link_view = 'film_detail'
elif(link_type == 'person'):
link_view = 'person_detail'
else:
raise Exception("Unknown link type.")
link_url = reverse(link_view, args=(link_slug,))
entity = get_object_or_404(Entity, slug=link_slug)
markdown_link = "[" + entity.name + "](" + link_url + ")"
return markdown_link

Related

Dynamic URL and Jinja templates

I've been trying to create user interface to filter out results from my database. Important thing is that I want the filters to be 'additive'. So if user selects one filter, page redirects and displays results. After that, user can select another filter and the results are narrowed down to both filters. This should continue for any number of filters.
This is how it looks now
#app.route('/')
def home():
kind = request.args.get('kind')
price = request.args.get('price')
category = request.args.get('category')
filters = {}
if price is not None: filters['params.price'] = {'$lt' : int(price) }
if kind is not None: filters['kind'] = kind
if category is not None: filters['category'] = category
posts = db.collection.find(filters)
return render_template('home.html', posts=posts)
and links for my hrefs using jinja2 templates look like
<li>Label</<li>
<li>Label</li>
<li>Label</li>
... many more similar links
Currently this works as override for the URL. If I click any of those links it just replaces the whole URL and uses the variable from the link.
first link: http://127.0.0.1/?kind=m
second link: http://127.0.0.1/?price=5000
third link: http://127.0.0.1/?category=p
What I'd like it to do is to append the query - If i click any of the links it remembers previous selected filters and 'adds' last clicked link. Below I show how I expect for it to work.
first link: http://127.0.0.1/?kind=m
second link: http://127.0.0.1/?kind=m?price=50000
second link: http://127.0.0.1/?kind=m?price=50000?category=p
You could pass all filter values (None initially) to the view, and add them as arguments to the url_for calls. Filters which are None will not be included in the links.

Django strip_tags template filter add space

I use djangos template filter striptags. Example:
>>> from django.utils.html import strip_tags
>>> strip_tags("<p>This is a paragraph.</p><p>This is another paragraph.</p>")
'This is a paragraph.This is another paragraph.'
What is the best way to add a space character between the paragraphs, so that I get this string instead:
'This is a paragraph. This is another paragraph.'
Edit:
One idea I have is to write a custom template filter that replaces all </p> tags with [space]</p> before the striptags filter is used. But is that a clean and robust solution?
yes, it seems like a clean solution to me. I had a similar issue when trying to create an excerpt for some articles in a WagTail (built on Django) application. I was using this syntax in my template..
{{ page.body|striptags|truncatewords:50 }}
.. and getting the same issue you described. Here is an implementation I came up - hopefully useful for other Django / WagTail developers as well.
from django import template
from django.utils.html import strip_spaces_between_tags, strip_tags
from django.utils.text import Truncator
register = template.Library()
#register.filter(name='excerpt')
def excerpt_with_ptag_spacing(value, arg):
try:
limit = int(arg)
except ValueError:
return 'Invalid literal for int().'
# remove spaces between tags
value = strip_spaces_between_tags(value)
# add space before each P end tag (</p>)
value = value.replace("</p>"," </p>")
# strip HTML tags
value = strip_tags(value)
# other usage: return Truncator(value).words(length, html=True, truncate=' see more')
return Truncator(value).words(limit)
and you use it like this..
{{ page.body|excerpt:50 }}

Dynamic Url's in Django

So I have a list of items and I want to have all of the items direct the user to a standard info page about that item when clicked. I don't full understand the dynamic url system in django. My html file for the content is called detail.html. The goal is to click a movie item, and be directed to a page title "blahblah.com/specific_movie_title"
In views.py I have:
def detail(reuqest, movie_title):
moviePage = Movie.objects.get(pk=movie_title)
return render_to_response('detail.html', {'detail':moviePage, RequestContext(request))
In the urls.py the line I have to correspond with this is:
url(r'^$', views.detail, name='detail')
Could I get help with correcting these two chunks of code?
In django, urls are matched by a regular expression pattern.
The general format of the url line is:
url(the_pattern, the_view_name_or_callable, **any_extra_arguments)
The key point is the_pattern, which is a regular expression and whatever is matched can be passed as an argument to the view function.
It is important that any parts of the pattern that are captured and passed on to the view function, actually match the function's signature (the def line, where you define the name and arguments). Otherwise, django will throw an error.
So now, with that out of the way - lets deal with the actual issue.
Your want a url like /the_great_gatsby to redirect to the page for The Great Gatsby.
The first step is to identify a pattern that matches the_great_gatsby. You can use [_\w]+ which means "one or more word characters or _", and plug it into the URL:
url(r'/[_\w]+$', views.detail, name='detail')
Next, you have to tell django how to capture that pattern and pass it to the view method as an argument.
Your view method is: def detail(request, movie_title). So django must pass whatever is after the / in the url (and matches the pattern) to the argument name movie_title, so what we want is this:
def detail(request, movie_title)
^^^^^^^^^^^
---------------|
|
vvvvvvv
url(r'/[_\w]+$', views.detail, name='detail')
We modify the regular expression to make sure that django captures whatever matches, and then assigns it a name. To do that, wrap the part of the regular expression in (?P<name_of_variable>expression) like this:
url(r'/(?P<movie_title>[_\w+])$', views.detail, name='detail')
Now, whatever is captured after / will be passed as the argument movie_title to the views.detail method.
Of course, you have to make sure your view function is doing the right thing with the captured string. In your case, your view method is searching by the primary key and will fail to produce any results (and will raise an exception) since there is no movie that will have the title as the primary key; but that's another problem.
you can configure your url and view like this
urls.py
url(r'^movie/(?P<movie_title>\w+)$', views.movie_detail, name='detail')
views.py
def movie_detail(request, movie_title):
movie_object = Movie.objects.get(pk=movie_title)
return render(request, 'moview-detail.html', {'movie_object':movie_object}
and in your html {% url 'app_name:detail' movie_title %}

Template tag for conditionally rendering or including another template if the path or paths exist

I am implementing a generic dropdown menu within a Django application. In most cases, the pages are generic and they have simple generic 'children' menus. However in some cases, I want to be able to include or render a special set of children, or custom content from a completely different template, based upon the page slug (page.slug).
I have considered implementing an inclusion tag but I am not sure about how to do this in a template tag. I know in views you can implement it like so:
blog_post = get_object_or_404(blog_posts, slug=slug)
...
templates = [u"blog/blog_post_detail_%s.html" % str(slug), template]
return render(request, templates, context)
My design pattern for the tag would be simple:
Look within pages/SLUG/dropdown.html
Try pages/dropdown_SLUG.html
Render nothing if none of those files exist.
How can I do this from a template tag? Is an inclusion tag the right way, or a render_tag?
This is the solution I came up with:
#register.render_tag
def page_menu_special(context, token):
page = None
template_name = None
menu_templates = []
parts = token.split_contents()[1:]
for part in parts:
part = Variable(part).resolve(context)
if isinstance(part, str):
template_name = part
elif isinstance(part, Page) or isinstance(part, CustomPage):
page = part
if page and page.slug:
page_template = str(page.slug) if page.slug != home_slug() else "index"
method_template = page.get_content_model().get_template_name()
menu_templates.extend([
u'pages/menus/dropdown_%s.html' % (page_template),
u'pages/menus/dropdown/%s.html' % (page_template),
])
if method_template:
menu_templates.append(method_template)
if template_name:
menu_templates.insert(0, template_name)
try:
t = select_template(menu_templates)
return t.render(Context(context))
except TemplateDoesNotExist:
return u''
It is rather specific to Mezzanine, but the logic can be reused:
Build paths to potential templates.
Pass the list to select_template
Catch exception if none exist, and return empty or suitable rendered content.
I'm still trying to figure out how to default to another template tag, from within this definition. So I can call page_menu_special and if this one fails, it reverts back to a call to page_menu with the same arguments and that saves us an if/else block.

Using variables in Django model content

i like to use the url template tag in my model's content.
example:
models content:
Car.description = 'this is a link to our main page: home'
in template.html:
<div>{{ Car.description }}</div>
result
<div>this is a link to our main page: home
is it possible, or do i have to write my own template tag?
thanks in advance
Roman
Assuming you have this:
car.description = 'this is a link to our main page: home'
You can do:
from django.template import Context, Template
from django.core.urlresolvers import reverse
class Car(models.Model):
def description_with_url(self):
return Template(self.description).render({'url': reverse('home')})
or use the same logic in custom template tag instead of method..
I can't figure out why you would need to do that. Assuming that I fully-understood your question, you are attempting to store something within a model's field that then behaves "dynamically" when rendered.
A model field's content that stores a URL should contain a URL and only a URL by utilizing the URLField.
Else, if you're dynamically building the URL from somewhere else, simply use template markup, i.e. the url template tag as it is meant to be used; it can also take parameters depending on the specific URL pattern. I.e. the url tag is meant to be used in the template's context.
Let me know if you update the question to describe what you are trying to achieve. But storing "behaviour" at data level is something to simply stay away from.

Categories

Resources