Django DRY Feeds - django

I'm using the Django Feeds Framework and it's really nice, very intuitive and easy to use. But, I think there is a problem when creating links to feeds in HTML.
For example:
<link rel="alternate" type="application/rss+xml" title="{{ feed_title }}" href="{{ url_of_feed }}" />
Link's HREF attribute can be easily found out, just use reverse()
But, what about the TITLE attribute? Where the template engine should look for this? Even more, what if the feed is build up dinamically and the title depends on parameters (like this)?
I can't come up with a solution that "seems" DRY to me... All that I can come up with is using context processors o template tags, but it gets messy when the context procesor/template tag has to find parameters to construct the Feed class, and writing this I realize I don't even know how to create a Feed instance myself within the view.
If I put all this logic in the view, it would not be just one view. Also, the value for TITLE would be in the view AND in the feed.

Just a guess (as I have not used feeds yet in my django app), but you could add a special template_context for your feed with your feed object and use it in your base.html.

I'm not fully satisfied with this solution, it may break feeds using Request and depends on a magic method. There it goes:
#coding:utf-8
# Author: Armando Pérez Marqués <mandx#rbol.org>
# Purpose: Django TemplateTag to output feed links in templates in a DRY way
# Created: 05/07/2010
import re
from django import template
from django.conf import settings
from django.contrib.syndication.views import Feed
from django.core.urlresolvers import reverse, resolve, NoReverseMatch
from django.template import Node
from django.template import TemplateSyntaxError
from django.utils.encoding import smart_str
from django.utils.html import escape as html_escape
from django.utils.safestring import mark_safe
register = template.Library()
kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
class FeedInfoNode(Node):
def __init__(self, view_name, args, kwargs, asvar):
self.view_name = view_name
self.args = args
self.kwargs = kwargs
self.asvar = asvar
def render(self, context):
args = [arg.resolve(context) for arg in self.args]
kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
for k, v in self.kwargs.items()])
# Try to look up the URL twice: once given the view name, and again
# relative to what we guess is the "main" app. If they both fail,
# re-raise the NoReverseMatch unless we're using the
# {% feed_info ... as var %} construct in which cause return nothing.
url = ''
try:
url = reverse(self.view_name, args=args, kwargs=kwargs, current_app=context.current_app)
except NoReverseMatch, e:
if settings.SETTINGS_MODULE:
project_name = settings.SETTINGS_MODULE.split('.')[0]
try:
url = reverse(project_name + '.' + self.view_name,
args=args, kwargs=kwargs, current_app=context.current_app)
except NoReverseMatch:
if self.asvar is None:
# Re-raise the original exception, not the one with
# the path relative to the project. This makes a
# better error message.
raise e
else:
if self.asvar is None:
raise e
if 'request' in context:
request = context['request']
else:
request = None
feed_instance, feed_args, feed_kwargs = resolve(url)
if not isinstance(feed_instance, Feed):
raise NoReverseMatch, \
'feed_info can only reverse class-based feeds'
feed_obj = feed_instance.get_object(request, *feed_args, **feed_kwargs)
feed_data = {
'url': url,
'obj': feed_instance,
'args': feed_args,
'kwargs': feed_kwargs,
#'title': html_escape(feed_instance.__get_dynamic_attr('title', obj)),
'title': html_escape(
feed_instance._Feed__get_dynamic_attr('title', feed_obj)
),
'type': feed_instance.feed_type.mime_type,
}
if self.asvar:
context[self.asvar] = feed_data
return ''
else:
return mark_safe(
'<link rel="alternate" type="%(type)s" title="%(title)s" href="%(url)s" />' \
% feed_data
)
def feed_info(parser, token):
"""
Returns an mapping containing populated info about the reversed feed
Works exactly as the url tag, but the mapping is not returned, instead
a variable is always set in the context.
This is a way to define links that aren't tied to a particular URL
configuration::
{% feed_info path.to.some_feed_view_class arg1 arg2 as feed_info_var %}
or
{% feed_info path.to.some_feed_view_class name1=value1 name2=value2 as feed_info_var %}
"""
bits = token.split_contents()
if len(bits) < 2:
raise TemplateSyntaxError("'%s' takes at least one argument"
" (path to a feed view)" % bits[0])
viewname = bits[1]
args = []
kwargs = {}
asvar = None
bits = bits[2:]
if len(bits) >= 2 and bits[-2] == 'as':
asvar = bits[-1]
bits = bits[:-2]
# Backwards compatibility: check for the old comma separated format
# {% url urlname arg1,arg2 %}
# Initial check - that the first space separated bit has a comma in it
if bits and ',' in bits[0]:
check_old_format = True
# In order to *really* be old format, there must be a comma
# in *every* space separated bit, except the last.
for bit in bits[1:-1]:
if ',' not in bit:
# No comma in this bit. Either the comma we found
# in bit 1 was a false positive (e.g., comma in a string),
# or there is a syntax problem with missing commas
check_old_format = False
break
else:
# No comma found - must be new format.
check_old_format = False
if check_old_format:
# Confirm that this is old format by trying to parse the first
# argument. An exception will be raised if the comma is
# unexpected (i.e. outside of a static string).
match = kwarg_re.match(bits[0])
if match:
value = match.groups()[1]
try:
parser.compile_filter(value)
except TemplateSyntaxError:
bits = ''.join(bits).split(',')
# Now all the bits are parsed into new format,
# process them as template vars
if len(bits):
for bit in bits:
match = kwarg_re.match(bit)
if not match:
raise TemplateSyntaxError("Malformed arguments to url tag")
name, value = match.groups()
if name:
kwargs[name] = parser.compile_filter(value)
else:
args.append(parser.compile_filter(value))
return FeedInfoNode(viewname, args, kwargs, asvar)
feed_info = register.tag(feed_info)
I'm starting with the code of the {% url %} template tag, and then, after obtaining the feed's URL, use resolve() to get the Feed subclass instance, and then get the needed attributes.
Caveats
Requires Django 1.2 Class Feeds, don't know exactly how to do this with the old way of feeds.
If the feed class uses the request object, the request context processor must be configured, since None is passed if it isn't present in the context.
There's an oddity with Feed.__get_dynamic_attr(). The Feed subclass instance doesn't have this method; instead, it appears with another name. Don't know how to figure the name out at runtime...

Related

How to perform a query by using URL with question mark in Django?

It seems like the original URL querying function has been removed from Django 3.1. Does anyone know how to do it with a new package?
The url.py:
urlpatterns = [
re_path(r'^portfolio/(?P<title>[\w-]+)/$' , BlogApp_View.displayPortfolio, name='displayPortfolio'),
path('portfolio/', BlogApp_View.selectPortfolio, name='selectPortfolio'),]
The view.py
def displayPortfolio(request):
title = request.GET.get('title')
portfolio = Article.objects.filter(articleType__name__contains = "Portfolio", title=title)
print(title)
DICT = {}
return render(request, 'Article/', DICT)
The problem is now if I visit http://127.0.0.1:8000/Blog/portfolio/?title=A_UAV_Positioning_Approach_Using_LoRa/, it will skip the re_path shows in url.py.
Instead, it goes to the path one.
I have tried str:title method but that is actually not what I want. I prefer using the question mark pattern to finish the query.
The part after the questionmark is the querystring [wiki] and is not part of the path. This thus means that regardless what patterns you write, you can not distinguish on this, since the path patterns, regardless whether it is a path or re_path, are never matched against a URL with a query string.
You thus should write a single view, and inspect the request.GET query dict (which is a dictionary-like representation of the query string and see if it contains a value for title.
Your urlpatterns thus look like:
urlpatterns = [
path('portfolio/', BlogApp_View.selectPortfolio, name='selectPortfolio'),
]
and in the view, you can see if it contains a title:
def selectPortfolio(request):
if 'title' in request.GET:
# contains a ?title=…
title = request.GET.get('title')
portfolio = Article.objects.filter(
articleType__name__contains='Portfolio',
title=title
)
data = {'portfolio': portfolio}
return render(request, 'some_template.html', data)
else:
# contains no ?title=…
# …
return …

Probably a circular import in django app?

There is a filter that adds to form django_crispy_form functionality. All arguments passing as string divided by ",".
# -*- coding: utf-8 -*-
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from django.core.urlresolvers import reverse
from django.template import Library
register = Library()
#args=[url_name, submit_button_text, optional_<pk>]
#register.filter
def with_submit(form, args):
sargs = args.split(',')
action, name = sargs[:2]
if len(sargs) > 2:
args = sargs[2:]
else:
args = None
helper = FormHelper()
helper.form_method = 'POST'
if args:
#there exception throwed every time if len of args > 2
helper.form_action = reverse(action, int(args[0]))
else:
helper.form_action = reverse(action)
helper.add_input(Submit(action, name, css_class='btn btn-primary'))
form.helper = helper
return form
When I use this filter without third optional argument - it works fine, but when I add third <pk> argument - it crashes and says:
The included URLconf '1' does not appear to have any patterns in it. If you see valid patterns in the file then the issue is probably caused by a circular import.
And I don't know where I should find circular import.
The signature of the reverse method is:
reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)
So, your second argument of int(args[0]) is being passed in as urlconf, which is leading to the exception. You need to use
reverse(action, args=int(args[0]))
Whenever you write template tags or filters, you have to be very careful that you code is as simple and as robust as possible. Custom tags and filters are the most difficult parts of a django application to debug.
Consider this line:
action, name = sargs[:2]
This line will raise ValueError if there aren't exactly two items in sargs[:2]. You should do a check first before executing this statement.

How to avoid Django URLField adding the trailing slash?

Django URLField likes to add a trailing slash (/) at the end of the user input, forcing all URLs to be stored with the extra character, this is wrong. How can I stop this behavior and save URLs as submitted by users?
Check to_python of URLField at https://github.com/django/django/blob/master/django/forms/fields.py.
You can see it has a line url_fields[2] = '/' almost at the end of method to_python. It appends a trailing slash / at the end of url. You can see the logic for doing this as a comment before this line.
This slash is necessary in case some query params are given.
If you want to avoid this behaviour, write you own field which extends from URLField and override to_python in your custom class.
I've been struggling with this as well, because it's causing a problem for certain urls. For example, http://www.nasa.gov/mission_pages/kepler/news/kepler-62-kepler-69.html/ fails, but it works without the slash.
To expand on akshar's answer, the method to do this is explained here. For example, defining this in my models.py file and setting url = NoSlashURLField() rather than models.URLField() in my model removes the slash:
try:
from urllib.parse import urlsplit, urlunsplit
except ImportError: # Python 2
from urlparse import urlsplit, urlunsplit
class NoSlashURLField(models.URLField):
description = "Remove the goddamn slash"
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
super(NoSlashURLField, self).__init__(*args, **kwargs)
def to_python(self, value):
def split_url(url):
"""
Returns a list of url parts via ``urlparse.urlsplit`` (or raises a
``ValidationError`` exception for certain).
"""
try:
return list(urlsplit(url))
except ValueError:
# urlparse.urlsplit can raise a ValueError with some
# misformatted URLs.
raise ValidationError(self.error_messages['invalid'])
value = super(NoSlashURLField, self).to_python(value)
if value:
url_fields = split_url(value)
if not url_fields[0]:
# If no URL scheme given, assume http://
url_fields[0] = 'http'
if not url_fields[1]:
# Assume that if no domain is provided, that the path segment
# contains the domain.
url_fields[1] = url_fields[2]
url_fields[2] = ''
# Rebuild the url_fields list, since the domain segment may now
# contain the path too.
url_fields = split_url(urlunsplit(url_fields))
# if not url_fields[2]:
# # the path portion may need to be added before query params
# url_fields[2] = '/'
value = urlunsplit(url_fields)
return value
For those using the usual Django admin forms for their site, and also using South for DB migrations, you may want to use the following method instead of stonefury's. His method changes the model field, which confuses South unless you add some special code. The below method changes only the admin code, allowing South to remain blissfully unaware.
Define this class somewhere in your app:
class NoSlashURLFormField(forms.URLField):
def to_python(self, value):
def split_url(url):
"""
Returns a list of url parts via ``urlparse.urlsplit`` (or raises a
``ValidationError`` exception for certain).
"""
try:
return list(urlsplit(url))
except ValueError:
# urlparse.urlsplit can raise a ValueError with some
# misformatted URLs.
raise ValidationError(self.error_messages['invalid'])
if value:
url_fields = split_url(value)
if not url_fields[0]:
# If no URL scheme given, assume http://
url_fields[0] = 'http'
if not url_fields[1]:
# Assume that if no domain is provided, that the path segment
# contains the domain.
url_fields[1] = url_fields[2]
url_fields[2] = ''
# Rebuild the url_fields list, since the domain segment may now
# contain the path too.
url_fields = split_url(urlunsplit(url_fields))
value = urlunsplit(url_fields)
return value
Then edit your admin.py file as follows:
from your_app.path.to.noslash import NoSlashURLFormField
from django.contrib.admin.widgets import AdminURLFieldWidget
class MyModelAdmin(admin.ModelAdmin):
...
formfield_overrides = {
models.URLField: {
'form_class': NoSlashURLFormField,
# Need to specify the AdminURLFieldWidget here because it would
# otherwise get defaulted back to URLInput.
'widget': AdminURLFieldWidget,
}
}

Django: custom template tag errors in for loop

I'm trying to get a custom template tag to work but am having some difficulties when I apply the tag within a for loop with multiple items in the thing being iterated across.
I'd like to have the tag look as below and only display the text "WORKED" if my permissions function evaluates to True.
Template code
{% load permission_tags %}
{% for group in groups %}
<div>{% permission request.user can_edit_group on group %}WORKED{% endpermission %}</div>
{% endfor %}
The tag basically takes in a user instance, a permissions string (i.e. "can_edit_group"), an "on" keyword (just to make the syntax nice) and an object instance to check permissions on.
The permissions framework I have going here I think is working ok and is not really part of my question. The difficulty I am having is
"Caught AttributeError while rendering: 'User' object has no attribute 'resolve'"
templatetags/permission_tags.py in render, line 23 (I've marked line 23 below)
template error when the for loop contains more than one group. The tag works nicely with just one group, but bombs out if I add more than one.
Template tag called permissions in templatetags/permission_tags.py
from django import template
register = template.Library()
def permission(parser, token):
try:
tag_name, username, permission, onkeyword, object = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError("%r tag requires exactly 4 "
"arguments" % token.contents.split()[0])
nodelist = parser.parse(('endpermission',))
parser.delete_first_token()
return PermissionNode(nodelist, username, permission, object)
class PermissionNode(template.Node):
def __init__(self, nodelist, user, permission, object):
self.nodelist = nodelist
self.user = template.Variable(user)
self.permission = permission
self.object = template.Variable(object)
def render(self, context):
self.user = self.user.resolve(context) # <---- Line 23
self.object = self.object.resolve(context)
# My custom permissions code. I don't think it's
# causing the error I am experiencing
permissions_obj = self.object.permissions(self.user)
content = self.nodelist.render(context)
# My custom permissions code. I don't think it's causing
# the error.
if hasattr(permissions_obj, self.permission):
perm_func = getattr(permissions_obj, self.permission)
if perm_func():
return content
return ""
register.tag('permission', permission)
Do you have any idea why this template tag generates an error when I have more than one item in my for loop but succeeds when I have just one?
I don't quite yet understand all of the inner-workings of this template tags syntax, so I have a feeling I have made a logic error somewhere. Any advice is much appreciated.
Thank you,
Joe
When you change back self.user to a User instance here:
self.user = self.user.resolve(context)
it works the first time, but the next time, because self.user is no longer a template.Variable instance, you get the exception: 'User' object has no attribute 'resolve'"
One solution is to save user & object instance in local variables:
def render(self, context):
user_inst = self.user.resolve(context)
object_inst = self.object.resolve(context)
permissions_obj = object_inst.permissions(user_inst)
content = self.nodelist.render(context)
if hasattr(permissions_obj, self.permission):
perm_func = getattr(permissions_obj, self.permission)
if perm_func():
return content
return ""

Django inclusion tag with configurable template

I've created an inclusion tag, however I'd like to be able to make the template optionally configurable. There doesn't seem to be support for this out of the box, so I'd like to see how people did this - maybe a method search the templates directory first for a specific template name and then falling back to the default template.
#register.inclusion_tag('foo.html', takes_context=True)
I use simple_tag when i need to do that:
from django.template import Library, loader, Context
#register.simple_tag(takes_context=True)
def my_tag(context, template_name):
var1 = ...
t = loader.get_template(template_name)
return t.render(Context({
'var1': var1,
...
}))
This post saved my life: http://djangosnippets.org/snippets/1329/
The key is to add to a "dummy template":
{% extends template %}
The inclusion_tag decorator is just a shortcut - it's meant as a simple way of rendering a specific template with a specific context. As soon as you want to move outside of that, it can no longer help you. But that just means you'll have to write the tag the long way, as explained in the documentation, and pass the template you want as a parameter.
I had to do something like this for a project and since we needed more than one of this kind of inclusion tag I made a decorator based on django inclusion_tag decorator. This is the code:
# -*- coding: utf-8 -*-
from django import template
from inspect import getargspec
from django.template.context import Context
from django.template import Node, generic_tag_compiler, Variable
from django.utils.functional import curry
def inclusion_tag(register, context_class=Context, takes_context=False):
def dec(func):
params, xx, xxx, defaults = getargspec(func)
if takes_context:
if params[0] == 'context':
params = params[1:]
else:
raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
class InclusionNode(Node):
def __init__(self, vars_to_resolve):
self.vars_to_resolve = map(Variable, vars_to_resolve)
def render(self, context):
resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
if takes_context:
args = [context] + resolved_vars
else:
args = resolved_vars
file_name, extra_context = func(*args)
from django.template.loader import get_template, select_template
if not isinstance(file_name, basestring) and is_iterable(file_name):
t = select_template(file_name)
else:
t = get_template(file_name)
self.nodelist = t.nodelist
new_context = context_class(extra_context, autoescape=context.autoescape)
# Copy across the CSRF token, if present, because inclusion
# tags are often used for forms, and we need instructions
# for using CSRF protection to be as simple as possible.
csrf_token = context.get('csrf_token', None)
if csrf_token is not None:
new_context['csrf_token'] = csrf_token
return self.nodelist.render(new_context)
compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
compile_func.__doc__ = func.__doc__
register.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
return func
return dec
You have to return a tuple with the template (or template list) and the context dict. Note that you have to pass the register (Library instance) in the decorator call:
from somewhere import inclusion_tag
#inclusion_tag(register)
def display_formset(formset):
template_name = FORMSET_TEMPLATES.get(formset.model,
'includes/inline_formset.html')
return (template_name, {'formset': formset})
Hope this helps
A solution could be a regular inclusion_tag which pass dynamic template name to context.
Like this :
# templatetags/tags.py
#register.inclusion_tag('include_tag.html', takes_context=True)
def tag_manager(context):
context.update({
'dynamic_template': resolve_template(context),
})
return context
And the template:
<!-- include_tag.html -->
{% include dynamic_template %}
The tricks here is, when I call {% tag_manager %}, it includes include_tag.html which in turn includes the template returned by resolve_template() (not included for brevity).
Hope this helps...