DRF does not reverse Django url pattern: NoReverseMatch - python

There is a complex app (not possible to just paste the code). Going to try to explain.
Django
There is a urls.py file from the native Django app. The urlpatterns defines and register its urls. The ^foo/ defines a group of related urls and the foonamepsace.
urlpatterns = patterns('',
...
url(r'^foo/', include('foo.urls', namespace='foonamespace')),
...
Now there is a method generate_report which does some logic inside and then uses render_to_string to return the HTML:
def generate_report(..):
...
return render_to_string('foo/foo_report.html', args)
Everything works inside the app, the url get reversed successfully.
Django Rest Framework (DRF)
Now there is a DRF implementation and one of its resources is supposed to return a report in a binary format.
class PDFReportViewSet(APIView):
renderer_classes = (BinaryFileRenderer, )
def get(..):
...
pdf = generate_report() # <-- fails with NoReverseMatch
...
return response
Problem
The ViewSet calls the generate_report, however one gets an error when trying to parse the HTML:
NoReverseMatch: foonamespace' is not a registered namespace
Question
Any clues why DRF cannot reverse the namespcae/url from the the core of Django app? How to make sure DRF can reverse a namespace from the core urls.py urlpattern?
Added
After investigation, inside the foo_report.html any usage of the url, for example {% url 'foonamespace:123' %} or {% url 'barnamespace:123' %} produces the error - only if ran from the DRF (running the same page using native Django works fine).
URLS
foo.urls.py
from django.conf.urls import patterns, url
from foo.views import (FooListView, FooDetailView...)
urlpatterns = patterns('',
url(r'^$', FooListView.as_view(), name='foo_list'),
url(r'^(?P<pk>\d+)/$', FooDetailView.as_view(), name='foo_details'),
Important note. The app is served at some.domain.com/, while the REST is served from some.domain.com/rest. So may be this way /rest just don't include anything because it is a parent of the root (which includes the foo.urls.py)

I was managed to resolve my issue with the help from #dirkgroten. It was difficult to see the problem without looking at the source code.
Solution
Updated the routers.py file:
urlpatterns = router.urls
urlpatterns += patterns('',
url(r'^foo/', include('foo.urls', namespace='foonamespace')),
)
Explanation
Basically, the app was serve from the root url / while the rest was served from /rest. The DRF router simply didn't include any of the root routes. Adding them manually like it is shown in solution resolved the problem and made foonamespace visible for all DRF elements.

Related

How to serve django and gatsby on single vhost?

I'm trying to deploy gatsby based frontend with django based backend under a single domain. It will rely on Apache and mod_wsgi. In a perfect world it should work as following:
https://my-domain.com/ - gatsby frontend
https://my-domain.com/admin - django
https://my-domain.com/api - django
I can see two possibilities:
Django is aware of frontend. Serve everything via Django, setup / as a STATIC_URL.
Django is not aware of frontend. Serve /api and /admin via django. / is handled by a webserver.
I feel more comfortable with the second approach, however I do not know how to configure VirtualHost for such a scenario. The firstch approach looks like an ugly hack.
How should I proceed with that?
After compiling your gatsby project, it should be served by django as a static page.
First: The gatsby dist should be in your static_private path.
Second: In your django project, you will define a URL for / that will call an index view let's say.
Finally: in your view you should render index.html of your gatsby dist.
urls.py:
from django.contrib import admin
from django.urls import path, re_path, include
from . import views
urlpatterns = [
path('admin/', admin.site.urls),
path('apis/', include('apps.urls')),
path('/', views.index),
]
views.py:
from django.shortcuts import render
def index(request):
return render(request, 'index.html')
Note that if you are handling routing in your frontend your url pattern for the index view should be like this : re_path('^.*$', views.index)
If you are hosting your django app on heroku you will need the whitenoise middleware and set it up in your settings.py :
MIDDLEWARE = [
...
'whitenoise.middleware.WhiteNoiseMiddleware',
...
]
A doc is available here: https://devcenter.heroku.com/articles/django-assets#whitenoise

Django Tutorial "Write Views That Actually Do Something"

I've been working on the Django tutorial. I'm on the part where it is "Write Views That Actually Do something." (Part 3)
I'm trying to use the index.html template that it gives you, but I keep getting a 404 error that says
Request Method: GET
Request URL: http://127.0.0.1:8000/polls/index.html
Using the URLconf defined in mysite.urls, Django tried these URL patterns, in this order:
^polls/ ^$ [name='index']
^polls/ ^(?P<question_id>\d+)/$ [name='detail']
^polls/ ^(?P<question_id>\d+)/results/$ [name='results']
^polls/ ^(?P<question_id>\d+)/vote/$ [name='vote']
^admin/
The current URL, polls/index.html, didn't match any of these.
I don't know if one of the regex are wrong? I've been messing around with it for a while now and I have had no luck getting it to work.
I can go to /polls just fine. But /polls/index.html does not work.
Any help would be appreciated.
The version of Django that I'm using is 1.7.4
Django view functions or classes use the template you define, so that you do not have to specify it in the URL. The urls.py file matches your defined regex to send requests to views.
If you truly wanted to use that URL, you would have to define ^polls/index.html$ in your urls.py and direct it to your view.
From what you're asking it sounds like you essentially want to output a static html file on a URL defined in your urlpatterns in urls.py.
I strongly suggest you take a look at Class Based Views.
https://docs.djangoproject.com/en/1.7/topics/class-based-views/#simple-usage-in-your-urlconf
The quickest way to go from what you've got, to rendering polls/index.html would be something like;
# some_app/urls.py
from django.conf.urls import patterns
from django.views.generic import TemplateView
urlpatterns = patterns('',
(r'^polls/index.html', TemplateView.as_view(template_name="index.html")),
)
But I'm sure you'll want to pass things to the template so class based views will be what you need. So the alternative to the above with added context would be;
# some_app/views.py
from django.views.generic import TemplateView
class Index(TemplateView):
template_name = "index.html"
def get_context_data(self, **kwargs):
context = super(Index, self).get_context_data(**kwargs)
context['foo'] = 'bar'
return context
Then obviously adding {{ foo }} to your index.html would output bar to the user. And you'd update your urls.py to;
# some_app/urls.py
from django.conf.urls import patterns
from .views import Index
urlpatterns = patterns(
'',
(r'^polls/index.html', Index.as_view()),
)

NoReverseMatch at /rest-auth/password/reset/

I have a django application with an angular front-end. When from the front-end I try to send a request for passwordReset, I get the following error:
Reverse for 'password_reset_confirm' with arguments '()' and keyword
arguments '{u'uidb64': 'MTE', u'token': u'3z4-eadc7ab3866d7d9436cb'}'
not found. 0 pattern(s) tried: []
Its a POST request going to http://127.0.0.1:8080/rest-auth/password/reset/
Following is what my urls.py looks like:
from django.conf.urls import patterns, include, url
from django.contrib import admin
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
url(r'^rest-auth/', include('rest_auth.urls')),
url(r'^rest-auth/registration/', include('rest_auth.registration.urls')),
url(r'^account/', include('allauth.urls'))
)
I also was having this problem, and found this github issue it said we need to add
url(r'^', include('django.contrib.auth.urls')),
on the urlpatterns.
As stated there it says that The PasswordReset view depends on django.contrib.auth.views.password_reset_confirm view.
As Roar Skullestad pointed out, the problem is with the default email template, which tries to resolve URL by reversing viewname "password_reset_confirm", which is undefined.
It is enough to register a viewname "password_reset_confirm" with a custom route and then default email template rendering will work fine.
You can register viewname with custom route by adding a path to urls.py:
urlpatterns = [
...,
path('password-reset/<uidb64>/<token>/', empty_view, name='password_reset_confirm'),
]
password-reset - custom route that password reset confirmation view. If you have SPA (Angular) - it will be the URL of your SPA view (such as the route to Angular component) that will handle the password reset.
This is the URL that will be resolved and embedded in the email. For this example it will be something like:
http://my-spa.com/app-name/password-reset/Nw/51v-490d4b372ec930e49049/
empty_view - in case of SPA (Angular), you don't really need server-side implementation, because the front end will actually handle this route. I used this implementation of a view, but it can be anything else:
from django.http import HttpResponse
def empty_view(request):
return HttpResponse('')
And since I'm using Angular, this is the route for my Angular component:
{
path: 'password-reset/:uid/:token',
component: PasswordRecoveryComponent
}
For me the problem was this line in site-packages/django/contrib/admin/templates/registration/password_reset_email.html:
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
From what I understand the problem is caused by reverse lookup not working for this line in contrib/auth/urls.py:
url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
'django.contrib.auth.views.password_reset_confirm',
name='password_reset_confirm'),
My (at least temporarily) solution was to override the template and hardcode the reverse lookup part of the url for the link in the email.
The path to the new template is specified in settings.py:
TEMPLATE_DIRS =(
"/absolute/path/to/my/templates/directory",
)
Since I am using angular front end, I also changed the link so that it triggers password reset confirmation via the angular client:
{{ protocol }}://{{ domain }}/#/passwordResetConfirm/{{ uid }}/{{ token }}
#AbimaelCarrasquillo's solutions works but you probably do not want to expose those endpoints as #dpstart mentioned in the comments.
I solved this by overriding the PasswordResetSerializer of rest-auth and simply replacing the reset form:
password_reset_form_class = PasswordResetForm
from the internal django.contrib.auth.forms.PasswordResetForm to allauth.account.forms.ResetPasswordForm
Make sure to add the following to your settings:
REST_AUTH_SERIALIZERS = {
'PASSWORD_RESET_SERIALIZER':'path.to.PasswordResetSerializer'
}
For those who are still struggling with this issue, I found that the reverse look up internal view looks for reverse lookup within the core project urls and not within any application. It could work within application with some tweak, but I am not sure. But it works creating the reset urls directly on core project urls.py
{
path(r'password_reset/', PasswordResetView.as_view(template_name='password_reset_form.html'), name='password_reset'),
path(r'password_reset_done/', PasswordResetDoneView.as_view(template_name='password_reset_done.html'), name='password_reset_done'),
path(r'password_reset_confirm/<uidb64>/<token>/', PasswordResetConfirmView.as_view(template_name='password_reset_confirm.html'), name='password_reset_confirm'),
path(r'password_reset_complete/', PasswordResetCompleteView.as_view(template_name='password_reset_complete.html'), name='password_reset_complete'),
}
Well I was also facing this problem. Every time I entered my email and pressed the button it took me to NoReverseMatch url, and I am using:
re_path(r'^password-reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
TemplateView.as_view(template_name="password_reset_confirm.html"),name='password_reset_confirm')
(url is not used to match regex in django anymore re_path is used)
My solution is to change the regex a little bit because the "csrf token" is longer than 20 words – that's why I was getting an error and you should try the same.
re_path(r'^password-reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,40})/$',
TemplateView.as_view(template_name="password_reset_confirm.html"),
name='password_reset_confirm')
You can either use + (in regex it means one or more) or {1,40} (in regex it means match from 1 to 40)
Add this to your project url.py file
url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
url('', include('social.apps.django_app.urls', namespace='social')),
Check out the FAQ: It explains this error and how to fix it. It references you to the demo program which contains:
# this url is used to generate email content
url(r'^password-reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
TemplateView.as_view(template_name="password_reset_confirm.html"),
name='password_reset_confirm'),
My solution was to override the email template that calls the reverse of "password_reset_confirm". Make sure email template sends a URL to your frontend app with the UID and Token in the URL (instead of trying to reverse "password_reset_confirm").
Your frontend's route should take the URL, parse it and then with the updated user's password and send it back as an API call to your backend to confirm.
I resolved this issue by moving these:
path('reset_password/', auth_views.PasswordResetView.as_view(), name='password_reset'),
path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
path('reset_password_sent/', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'),
path('reset_password_complete/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
from accounts/urls.py to yourProjectName/urls.py.
I guess that path('', include("accounts.urls")), was causing the problem.
I have a headless backend, so adding or renaming URLs only for this was not a good option.
The issue is on the email template, you can override it. Just take care that you have the correct path on the settings.
In my case I have all this logic inside users app. So I have something like this.
users/templates/registration/password_reset_email.html
Inside this template I have a new custom message without the reverse URL call.
If you need more than just override the template, or maybe you need to send additional data to the template. You must override the serializer also. To do that you have to create your new serializer
from dj_rest_auth.serializers import PasswordResetSerializer as RestPasswordResetSerializer
class PasswordResetSerializer(RestPasswordResetSerializer):
def get_email_options(self):
return {
'html_email_template_name': 'registration/password_reset_email_html.html', # if you want to use an HTML template you can declare here
'extra_email_context': {'custom_key': 'custom value for my template',}
}
and add to settings.
REST_AUTH_SERIALIZERS = {
'PASSWORD_RESET_SERIALIZER':'path.to.PasswordResetSerializer'
}
On the serializer you can add custom validation if needed also.
In your views.py, if you have set a token, pass it along with the path similar to:
path('resetpassword_validate/<uidb64>/<token>/', views.resetpassword_validate, name='resetpassword_validate'),

Django admin site with url parameters

I have a Django project with several applications, and I want to add the Django admin site for one of these.
The problem I have is that the main urls.py file has
url(r'^tools/(\w+)/', include('tools.myapp.urls')),
and in my myapp.urls I have added
url(r'^admin/', include(admin.site.urls)),
The problem is that the parent part of the url matching uses a parameter, which is usually passed in the template (that's the application name) like so
{% url "my_view_function" request.info.appname %}
But the default Django template obviously don't include that extra parameter, when calling
{% url 'admin:logout' %}
thus leading to a NoReverseMatch exception.
How can I have the admin site working?
If you just want to have admin run under any tools/whatever/admin URL, you can add this line before the tools.myapp.urls import in your main urls.py:
url(r'^tools/\w+/admin/', include(admin.site.urls)),

Customized views with django-registration

I need to make a very simple modification -- require that certain views only show up when a user is not authenticated -- to django-registration default views. For example, if I am logged in, I don't want users to be able to visit the /register page again.
So, I think the idea here is that I want to subclass the register view from django-registration. This is just where I'm not sure how to proceed. Is this the right direction? Should I test the user's authentication status here? Tips and advice welcomed!
Edit
I think this is the right track here: Django: Redirect logged in users from login page
Edit 2
Solution:
Create another app, for example, custom_registration, and write a view like this (mine uses a custom form as well):
from registration.views import register
from custom_registration.forms import EduRegistrationForm
def register_test(request, success_url=None,
form_class=EduRegistrationForm, profile_callback=None,
template_name='registration/registration_form.html',
extra_context=None):
if request.user.is_authenticated():
return HttpResponseRedirect('/')
else:
return register(request, success_url, form_class, profile_callback, template_name, extra_context)
I had to use the same function parameters, but otherwise just include the test, and if we pass it, continue to the main function.
Don't forget to put this in your URLConf either (again, this includes some stuff about my custom form as well):
top-level URLConf
(r'^accounts/', include('custom_registration.urls')),
(r'^accounts/', include('registration.urls')),
custom_registration.views
from django.conf.urls.defaults import *
from custom_registration.views import register_test
from custom_registration.forms import EduRegistrationForm
urlpatterns = patterns('',
url(r'^register/$', register_test, {'form_class': EduRegistrationForm}, name='registration.views.register'),
)
As far as I remember django-registration is using function-based views, so you can not really subclass them. The approach I usually follow is "overwriting" the original views (without modifying the django-registration app of course). This works like this:
Create another app (you could call it custom_registration or whatever you want)
This app need to contain another urls.py and in your case another views.py
Copy the original register view code to your new views.py and modify it, add a pattern to your urls.py to point to this view (use the same url pattern as in django-registration for this view)
Put an include to your projects urls.py of your new app urls.py before your are including the original django-registration app. This could look like this for example:
urlpatterns = patterns('',
...
url(r'^accounts/', include('custom_registration.urls')),
url(r'^accounts/', include('registration.backends.default.urls')),
...
)
This simply works since the first matching url pattern for /accounts/register will point to your new app, so it will never try to call the one from the original app.

Categories

Resources