Django CMS 2.1.0 App Extension NoReverseMatch TemplateSyntaxError - python

I'm writing a custom app for Django CMS, but get the following error when trying to view a published entry in the admin:
TemplateSyntaxError at /admin/cmsplugin_publisher/entry/
Caught NoReverseMatch while rendering: Reverse for 'cmsplugin_publisher_entry_detail' with arguments '()' and keyword arguments '{'slug': u'test-german'}' not found.
I can get the app working if I give the app a URL in my main application urls.py, but that fixes the app to a required URL, I just want extend Django CMS so the app will come from whichever page it's added to.
models.py Absolute URL Pattern
#models.permalink
def get_absolute_url(self):
return ('cmsplugin_publisher_entry_detail', (), {
'slug': self.slug})
urls/entries.py
from django.conf.urls.defaults import *
from cmsplugin_publisher.models import Entry
from cmsplugin_publisher.settings import PAGINATION, ALLOW_EMPTY, ALLOW_FUTURE
entry_conf_list = {'queryset': Entry.published.all(), 'paginate_by': PAGINATION,}
entry_conf = {'queryset': Entry.published.all(),
'date_field': 'creation_date',
'allow_empty': ALLOW_EMPTY,
'allow_future': ALLOW_FUTURE,
}
entry_conf_detail = entry_conf.copy()
del entry_conf_detail['allow_empty']
del entry_conf_detail['allow_future']
del entry_conf_detail['date_field']
entry_conf_detail['queryset'] = Entry.objects.all()
urlpatterns = patterns('cmsplugin_publisher.views.entries',
url(r'^$', 'entry_index', entry_conf_list,
name='cmsplugin_publisher_entry_archive_index'),
url(r'^(?P<page>[0-9]+)/$', 'entry_index', entry_conf_list,
name='cmsplugin_publisher_entry_archive_index_paginated'),
)
urlpatterns += patterns('django.views.generic.list_detail',
url(r'^(?P<slug>[-\w]+)/$', 'object_detail', entry_conf_detail,
name='cmsplugin_publisher_entry_detail'),
)
views/entries.py
from django.views.generic.list_detail import object_list
from cmsplugin_publisher.models import Entry
from cmsplugin_publisher.views.decorators import update_queryset
entry_index = update_queryset(object_list, Entry.published.all)
views/decorators.py
def update_queryset(view, queryset, queryset_parameter='queryset'):
'''Decorator around views based on a queryset passed in parameter which will force the update despite cache
Related to issue http://code.djangoproject.com/ticket/8378'''
def wrap(*args, **kwargs):
'''Regenerate the queryset before passing it to the view.'''
kwargs[queryset_parameter] = queryset()
return view(*args, **kwargs)
return wrap
The app integration with Django CMS is explained here: http://github.com/divio/django-cms/blob/master/cms/docs/app_integration.txt
It looks like the issue might be that I'm not correctly returning the RequestContext as I'm using a mis of generic views and custom in the application.
The CMS App extension py file:
cms_app.py
from django.utils.translation import ugettext_lazy as _
from cms.app_base import CMSApp
from cms.apphook_pool import apphook_pool
from cmsplugin_publisher.settings import APP_MENUS
class PublisherApp(CMSApp):
name = _('Publisher App Hook')
urls = ['cmsplugin_publisher.urls']
apphook_pool.register(PublisherApp)
Any pointers appreciated, it's proving to be a tough nut to crack!

Looks like it's a bug in the URLconf parser in Django-CMS 2.1.0beta3, which is fixed in dev. The bug only occurs when including other URLconfs from within an app.

UPDATE:
OK, I think your error originates from get_absolute_url:
#models.permalink
def get_absolute_url(self):
return ('cmsplugin_publisher_entry_detail', (), {'slug': self.slug})
I suspect it's because this ultimately calls object_detail which expects a positional parameter queryset (see django/views/generic/list_detail.py). You could try changing this to something like:
return ('cmsplugin_publisher_entry_detail', [Entry.objects.all(),], {'slug': self.slug})

I would double-check that urls/entries.py is actually being imported somewhere otherwise it won't be able to get the reverse match.

Related

Django Admin Model on add fails to render related change link

Given following admin settings:
class BrokerLocationSetForm(forms.ModelForm):
class Meta:
model = BrokerLocationSet
fields = ('broker', 'program', 'label', 'locations')
widgets = {
'locations': autocomplete.ModelSelect2Multiple(url='admin-autocomplete-location', forward=('broker','program')),
}
class BrokerLocationSetAdmin(admin.ModelAdmin):
model = BrokerLocationSet
form = BrokerLocationSetForm
list_display=['broker', 'program', 'label']
admin.site.register(BrokerLocationSet, BrokerLocationSetAdmin)
When I try navigate to add view in admin for BrokerLocationSetForm it raises following error:
raise NoReverseMatch(msg) NoReverseMatch: Reverse for 'program_program_change' with arguments '(u'__fk__',)' not found. 1 pattern(s) tried: [u'admin/program/program/(?P<program_pk>\\d+)/change/$']
When I debug in shell:
reverse('admin:broker_broker_change', 'myapp.urls', args=(u'__fk__',))
it outputs:
u'/admin/broker/broker/fk/change/'
but for:
reverse('admin:program_program_change', 'myapp.urls', args=(u'__fk__',))
I get same error as above. After some debugging I sensed that somehow admin was passing a string instead of an int into reverse function while it expected an integer as below :
reverse('admin:program_program_change', 'myapp.urls', args=(u'1',))
u'/admin/program/program/1/change/'
Since django admin does this url reversing magic I am not sure where I should customize this to fix the bug. I have got this code base fairly new and to get sense completely.
How I can fix above bug by customizing admin model or form. I dont want to update 'admin:program_program_change' but probably provide an alternate route to same view! . Is it possible ? please advise !
I found a solution however, I am not sure if this is best one. Since ProgramAdmin expects a numeric parameter while popup link from BrokerLocationSetAdmin is expecting a route with a string parameter. e.g
reverse('admin:program_program_change', 'myapp.urls', args=(u'__fk__',))
Solution was to inject another admin route with same name to ProgramAdmin model by overriding its get_urls method as follow:
class ProgramAdmin(admin.ModelAdmin):
...
...
def get_urls(self):
from django.conf.urls import url
from functools import update_wrapper
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
wrapper.model_admin = self
return update_wrapper(wrapper, view)
urls = super(ProgramAdmin, self).get_urls()
info = self.model._meta.app_label, self.model._meta.model_name
alt_urls=[
url(r'^(?P<program_pk>\w+)/change/$', wrap(self.change_view), name='%s_%s_change' % info),
]
return urls+alt_urls
Now we have two routes with same name but different paths parameter e.g:
/admin/program/program/<program_pk>/change/ django.contrib.admin.options.change_view admin:program_program_change
admin/program/program/(?P\d+)/change/$
/admin/program/program/<program_pk>/change/ django.contrib.admin.options.change_view admin:program_program_change
admin/program/program/(?P\w+)/change/$
Depending on the context one of route will be used.

Django Haystack Custom Search Form

I've got a basic django-haystack SearchForm working OK, but now I'm trying to create a custom search form that includes a couple of extra fields to filter on.
I've followed the Haystack documentation on creating custom forms and views, but when I try to view the form I can only get the error:
ValueError at /search/calibration/
The view assetregister.views.calibration_search didn't return an HttpResponse object. It returned None instead.
Shouldn't basing this on SearchForm take care of returning a HttpResponse object?
forms.py
from django import forms
from haystack.forms import SearchForm
class CalibrationSearch(SearchForm):
calibration_due_before = forms.DateField(required=False)
calibration_due_after = forms.DateField(required=False)
def search(self):
#First we need to store SearchQuerySet recieved after / from any other processing that's going on
sqs = super(CalibrationSearch, self).search()
if not self.is_valid():
return self.no_query_found()
#check to see if any date filters used, if so apply filter
if self.cleaned_data['calibration_due_before']:
sqs = sqs.filter(calibration_date_next__lte=self.cleaned_data['calibration_due_before'])
if self.cleaned_data['calibration_due_after']:
sqs = sqs.filter(calibration_date_next__gte=self.cleaned_data['calibration_due_after'])
return sqs
views.py
from .forms import CalibrationSearch
from haystack.generic_views import SearchView
from haystack.query import SearchQuerySet
def calibration_search(SearchView):
template_name = 'search/search.html'
form_class = CalibrationSearch
queryset = SearchQuerySet().filter(requires_calibration=True)
def get_queryset(self):
queryset = super(calibration_search, self).get_queryset()
return queryset
urls.py
from django.conf.urls import include, url
from . import views
urlpatterns = [
....
url(r'^search/calibration/', views.calibration_search, name='calibration_search'),
....
]
Haystack's SearchView is a class based view, you have to call .as_view() class method when adding a urls entry.
url(r'^search/calibration/', views.calibration_search.as_view(), name='calibration_search'),
This helped me.
"removing the "page" prefix on the search.html template did the trick, and was a good temporary solution. However, it became a problem when it was time to paginate the results. So after looking around, the solution was to use the "page_obj" prefix instead of "page" and everything works as expected. It seems the issue is that the haystack-tutorial assumes the page object is called "page", while certain versions of django its called "page_obj"? I'm sure there is a better answer - I'm just reporting my limited findings."
See this: Django-Haystack returns no results in search form

Django django-cms reverse 'NoReverseMatch at..' multisite language

This seems to be a "classic" problem :
NoReverseMatch at /tr/showroom/
Reverse for 'project-list' with arguments '()' and keyword arguments '{}' not found. 0 pattern(s) tried: []
But the problem is, when I check the different topic on the internet and then my files, I don't get it.
So it's working perfectly on the native language. It happens when I try to change the language on this showroom page only.
Every page that have been copy with the commad cms copy works fine except this one.
Well, here is the model :
def get_slug(self):
return self.safe_translation_getter(
'slug',
language_code=get_language(),
any_language=False)
def get_absolute_url(self, current_app=None):
"""
Warning: Due to the use of django application instance namespace
(for handle multi instance of an application)we add the possibility
to use it with the reverse.
So if get_absolute_url is call without app instance it may failed (
for example in django_admin)
"""
lang = get_language()
if self.has_translation(lang):
kwargs = {'slug': self.get_slug()}
return reverse('djangocms_showroom:project-detail',
kwargs=kwargs,
current_app=current_app)
return reverse('djangocms_showroom:project-list', current_app=current_app)
def get_full_url(self, site_id=None):
return self._make_full_url(self.get_absolute_url(), site_id)
def _make_full_url(self, url, site_id=None):
if site_id is None:
site = Site.objects.get_current()
else:
site = Site.objects.get(pk=site_id)
return get_full_url(url, site)
The url :
from django.conf.urls import patterns, url
from .views import (ProjectListView, ProjectDetailView)
urlpatterns = patterns(
'',
url(r'^$', ProjectListView.as_view(), name='project-list'),
url(r'^project/(?P<slug>\w[-\w]*)/$', ProjectDetailView.as_view(), name='project-detail'),
)
And the html error :
Error during template rendering
In template /var/www/webapps/blippar_website/app/blippar/templates/djangocms_showroom/project_list.html, error at line 14
Which is :
<form id="projects-filter" action="{% url 'djangocms_showroom:project-list' %}">
Well, I am not very good yet with django and django-cms, if someone has any clue, it would be marvellous !

How to specify a custom 404 view for Django using Class Based Views?

Using Django, you can override the default 404 page by doing this in the root urls.py:
handler404 = 'path.to.views.custom404'
How to do this when using Class based views? I can't figure it out and the documentation doesn't seem to say anything.
I've tried:
handler404 = 'path.to.view.Custom404.as_view'
Never mind, I forgot to try this:
from path.to.view import Custom404
handler404 = Custom404.as_view()
Seems so simple now, it probably doesn't merit a question on StackOverflow.
Managed to make it work by having the following code in my custom 404 CBV (found it on other StackOverflow post: Django handler500 as a Class Based View)
from django.views.generic import TemplateView
class NotFoundView(TemplateView):
template_name = "errors/404.html"
#classmethod
def get_rendered_view(cls):
as_view_fn = cls.as_view()
def view_fn(request):
response = as_view_fn(request)
# this is what was missing before
response.render()
return response
return view_fn
In my root URLConf file I have the following:
from apps.errors.views.notfound import NotFoundView
handler404 = NotFoundView.get_rendered_view()
In your main urls.py you can just add from app_name.views import Custom404
and then set handler404 = Custom404.as_view().
It should work

How to generate urls in django

In Django's template language, you can use {% url [viewname] [args] %} to generate a URL to a specific view with parameters. How can you programatically do the same in Python code?
What I need is to create a list of menu items where each item has name, URL, and an active flag (whether it's the current page or not). This is because it will be a lot cleaner to do this in Python than the template language.
If you need to use something similar to the {% url %} template tag in your code, Django provides the django.core.urlresolvers.reverse(). The reverse function has the following signature:
reverse(viewname, urlconf=None, args=None, kwargs=None)
https://docs.djangoproject.com/en/dev/ref/urlresolvers/
At the time of this edit the import is django.urls import reverse
I'm using two different approaches in my models.py. The first is the permalink decorator:
from django.db.models import permalink
def get_absolute_url(self):
"""Construct the absolute URL for this Item."""
return ('project.app.views.view_name', [str(self.id)])
get_absolute_url = permalink(get_absolute_url)
You can also call reverse directly:
from django.core.urlresolvers import reverse
def get_absolute_url(self):
"""Construct the absolute URL for this Item."""
return reverse('project.app.views.view_name', None, [str(self.id)])
For Python3 and Django 2:
from django.urls import reverse
url = reverse('my_app:endpoint', kwargs={'arg1': arg_1})
Be aware that using reverse() requires that your urlconf module is 100% error free and can be processed - iow no ViewDoesNotExist errors or so, or you get the dreaded NoReverseMatch exception (errors in templates usually fail silently resulting in None).

Categories

Resources