I'm creating an administrative interface/dashboard for a Django 1.7 project. Instead of creating a new admin application from scratch, I'm trying to use Django's built-in admin site. I have "root" (superuser) and "reviewer" (non-superuser) staff accounts. Both accounts are in the default "admin" group. Both accounts can log in to the admin site but some pages are accessible by both the root and reviewer accounts while access to others is limited to the superuser. I've created the two following views:
# mysite/apps/admin/views.py
from django.shortcuts import render
from django.contrib.auth.decorators import user_passes_test
#user_passes_test(lambda u: u.is_staff)
def all_admins(request, template):
return render(request, template)
#user_passes_test(lambda u: u.is_superuser)
def superuser_only(request, template):
return render(request, template)
The problem I'm running into is that if I log is using the reviewer account and click the anchor for the "superuser_only" page, I get the following error:
Page not found (404)
Request Method: GET
Request URL: http://localhost:8001/accounts/login/?next=/admin/super/
Using the URLconf defined in conf.urls, Django tried these URL patterns, in this order:
^admin/
^admin/all_admins/$ [name='all-admins']
^admin/super/$ [name='superuser-only']
The current URL, accounts/login/, didn't match any of these.
Is there something else I need to do to implement this superuser-only view and template so that this error doesn't occur if the reviewer clicks the link?
I followed the Django docs instructions and created my own copies of Django's base.html, base_site.html, and index.html templates in my mysite/apps/admin/templates/admin directory and then added the "all_admins" and "superuser_only" anchors to the bottom of the index.html page.
Here are my files:
# part of mysite/mysite/settings.py
TEMPLATE_DIRS = (
os.path.join(BASE_DIR, 'apps/admin/templates'),
os.path.join(BASE_DIR, 'apps/admin/templates/admin'),)
# mysite/mysite/urls.py
from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
url(r'^admin/all_admins/$',
'apps.admin.views.all_admins',
{'template': 'all_admins.html'},
name='all-admins'),
url(r'^admin/super/$',
'apps.admin.views.superuser_only',
{'template': 'superuser_only.html'},
name='superuser-only'),
)
Then user doesn't pass the test django redirects it to the login page. Defult url for this page is /accounts/login/. Set the LOGIN_URL setting to the admin's login page:
LOGIN_URL = 'admin:login'
Or, if you have non-staff users on the site, set the auth urls in the urls.py:
url('^accounts/', include('django.contrib.auth.urls')),
But this will require you to create several templates (login, logout, change password etc.)
UPDATE: If you want to show a message in the login page then you can create the custom login template for admin. Add this line to your urls.py:
admin.site.login_template = 'my_login.html'
And then create my_login.html template which is extended from admin/login.html:
{% extends 'admin/login.html' %}
{% block content %}
{% if user.is_staff %}
<p class="errornote">You need to be a superuser to access that page.</p>
{% endif %}
{{ block.super }}
{% endblock %}
UPDATE 2: If you don't want to change site-wide LOGIN_URL or implement auth by including django.contrib.auth.urls in your urls.py then you can pass login_url parameter to the #user_passes_test decorator:
#user_passes_test(lambda u: u.is_superuser, login_url='/admin/login/')
def superuser_only(request, template):
return render(request, template)
Related
I want to set up the disconnection. I have a page, Page 1, that is only accessible to an authenticated user. When i disconnect I can still access page 1.
And i have anothers questions, How does Django know to execute the logout_user function ? (I have the same question for the login but as it works I didn't ask myself the question ^^).
And why do we indicate a redirection in the return when in the html we already indicate the redirection ?
appIdentification/views.py
from django.contrib.auth import authenticate, login, logout
def logout_user(request):
logout(request)
messages.success(request, ("You Were Logged Out!"))
return redirect('home')
appIdentification/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('/login', views.login_user, name="login"),
path('', views.logout_user, name="logout"),
]
mainApp/template/mainApp/page1.html
<body>
<h1> PAGE 1 </h1>
{% if user.is_authenticated %}
Logout
{% endif %}
</body>
mainApp/views.py
#login_required
def page1(request):
return render(request, 'mainApp/p1.html', {})
mainApp/urls.py
from django.urls import path, include
from . import views
path('mainApp', include('django.contrib.auth.urls')),
path('mainApp', include('appIdentification.urls')),
path('home', views.home, name="home"),
path('p1', views.page1, name="p1"),
Answer to your first problem
I have a page, Page 1, that is only accessible to an authenticated user. When i disconnect I can still access page 1.
because you've put wrong url for logout it shoud be
Logout
instead of
Logout
and for your second question
How does Django know to execute the logout_user function ?
Django use MVT pattern in this basically when you go to some url eg. /logout/ it calls view mapped to that url eg. logout_user() and if your view required any data from model then it gets from your model.
Answer to your last question
And why do we indicate a redirection in the return when in the html we already indicate the redirection ?
becouse it's a Post/Redirect/Get web development design pattern
When a web form is submitted to a server through an HTTP POST request, attempts to refresh the server response can cause the contents of the original POST to be resubmitted, possibly causing undesired results, such as a duplicate web purchase. Some browsers mitigate this risk by warning the user that they are about to re-issue a POST request.
I have multiple apps in one django project
/user
/manager
/business`
Each needs a separate set of login and registration. How do I use django.contrib.auth to satisfy this?
I have urlpatterns in main are:
urlpatterns = [
path('admin/', admin.site.urls),
path('user/', include('user.urls')),
path('user/', include('django.contrib.auth.urls')),
path('manager/', include('manager.urls')),
path('manager/', include('django.contrib.auth.urls')),
path('business/', include('business.urls')),
path('business/', include('django.contrib.auth.urls')),
Urlpatterns in the apps are like those:
urlpatterns = [
path('index', views.index, name='index'),
path('register', views.register, name='register'),
]
and I have different views for login and register, also have different templates in each app: /templates/register/register.html and /templates/register/login.html
However, login and register views seem to be shared between apps. Is there a way to separate them with ease?
First of all I would suggest you make use namespaces in your urls so you don't get any conflicts with the names in your urls. So add app_name to the urls.py of your apps, e.g.
#user urls.py
app_name='user'
urlpatterns = [
...
Then something like {% url 'user:register' %} will point to register view of your user app, {% url 'manager:register' %} will point to the register view of your manager app and so on.
If you have defined individual login views for each app the same goes for these views too, providing you have imported the correct views in you apps urls.py.
As you are also including the django.contrib.auth.urls in each of your apps there is of course a second 'login/' path coming from the default path in the auth.urls. However, as far as I know the resolution of urls is done top down by django, so if you keep the order of urls as in your post your custom 'login/' will be hit first and used. So no problem with that.
Where I see a problem is with your templates. If I understand it correctly all are located in a templates/register/ folder in your apps. The django template loader does not differentiate between a register.html template in your user app and a register.html template in your manager app. So it is likely that you do not get the correct template. What I would suggest is moving the templates to an app specific subfolder, e.g. /templates/user/register. Then you can fetch the correct templates in your views (e.g. 'user/register.html').
I understand that you have individual app specific views. Of course you can always subclass the views django provides and adapt them to your needs. For instance by overriding the form_valid() method of the LoginView. This SO post provides an example.
If on the other hand you want to use the default django login view just with a custom template you can pass the template as a kwarg to your view. E.g.:
# user urls.py
from django.urls import path
from django.contrib.auth.views import LoginView
app_name='user'
urlpatterns = [
path('login/', LoginView.as_view(template_name='user/login.html'), name='login'),
# more patterns
]
If you want to redirect to some custom site after successful login you can add an input called 'next' to your login form containing the url to where to redirect, e.g.
<!-- user/login.html -->
<form action="{% url 'user:login' %}" method="POST">
{% csrf_token %}
{{ form }}
<input type="hidden" name="next" value="/user/index/">
<button type="submit">Login</button>
</form>
In your apps you will propably be using the user_passes_test decorator or something similar to check whether the user is allowed to access the view in that app.
How I've Arranged Templates : I have placed my login.html in /templates/registration folder of Django. So, the Django takes necessary care of accounts/login ,accounts/logout url requests and renders as per request. And I haven't to code for the individual login and logout functions.
What I am trying to Achieve : I want to authenticate users at login request, when they requests the login page:
If user is anonymous user, I want to render the normal login page.
However, If the user is authenticated thats already logged in. I want to display an error and not the logged page.
I want to achieve this in the views.py and urls.py and not in the templates by:
{% if user.is_authenticated %}
{% if user.is_anonymous %}
Urls.py
from django.conf.urls import url
from django.contrib.auth import views as auth_views
from . import views
urlpatterns=[
url(r'^register/$', views.register, name='register'),
url(r'^logout/$', auth_views.logout, {'next_page' : 'Homepage'}, name='logout'),
]
You can wrap the view with your own, which either redirects or calls the original.
def wrapped_login(request):
if request.user.is_authenticated:
return redirect('whatever')
else:
return auth_views.login(request)
Provide LOGIN_REDIRECT_URL = '/' in settings.py. Then use the following url for login page:
urlpatterns = [
url(r'^login/',
auth_views.LoginView.as_view(redirect_authenticated_user=True),
name='login'),
]
This will redirect your user to the URL provided in settings file if they try to login even after being authenticated.
when I use django.contrib.auth.views.password_reset_confirm without arguments at all it works and I can render the template without any problem, when adding uidb36 and token arguments it fails.
Caught NoReverseMatch while rendering: Reverse for 'django.contrib.auth.views.password_reset_confirm' with arguments '()' and keyword arguments '{'uidb36': '111', 'token': '1111111111111'}' not found.
Most likely it is an issue with your urls.py. You need to setup the right pattern to grab the uidb36 and token values passed as URL parameters. If not, it will throw a similar error to what you see above.
Something like:
(r'^reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', 'django.contrib.auth.views.password_reset_confirm', {'template_name' : 'registration/password_reset.html', 'post_reset_redirect': '/logout/' })
registration/password_reset.html - is my custom template
logout - is my custom logout action
I had this issue in Django 1.3, and wasted a lot of time because the error can mask a number of underlying issues.
I needed to add this to the top of the reset email template:
{% load url from future %}
Also, the example in the Django docs didn't match the sample url:
{{ protocol}}://{{ domain }}{% url 'auth_password_reset_confirm' uidb36=uid token=token %}
So I had to change the auth_password_reset_confirm above to password_reset_confirm.
If you're using Django 1.6+ and run into something like this it could be that you need to update uidb36 to uidb64 in both your template and your urls.
Example url:
url(r'^password/reset/confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$',
auth_views.password_reset_confirm
and reset link in template:
{{ protocol}}://{{ domain }}{% url 'django.contrib.auth.views.password_reset_confirm' uidb64=uid token=token %}
For Django 1.8+ users, just copy this URL to your main urls.py file, so that it recognizes the URL name
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'),
And add this mentioned by: #Lunulata to your password_reset_email.html file:
{{ protocol}}://{{ domain }}{% url 'django.contrib.auth.views.password_reset_confirm' uidb64=uid token=token %}
Try adding following to your urls.py
(r'^reset/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', 'django.contrib.auth.views.password_reset_confirm'),
I found this to work, copied from the default url
url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
auth_views.password_reset_confirm, name='password_reset_confirm'),
Just add this line to your urls.py:
url('^', include('django.contrib.auth.urls')),
This enables the django reset_password workflow.
Then override your login.html to include the line:
<div class="password-reset-link">
href="{{ password_reset_url }}">{% trans 'Forgotten your password or username?' %}</a></div>
Now you should be able to use the builtin Django PasswordResetView included with Django as long as your email settings are set up.
if you are using app_name in every urls.py
suppose you have an app and in that app in urls.py you have mentioned app_name="accounts"
in order to retrieve the page you need to mention two things
template_name and success_url inside the PasswordResetView(template_name="accounts/password_reset.html" , success_url= reverse_lazy('accounts:password_reset_sent'))
dont forget to import reverse_lazy from django.urls inside urls.py
so your final code of accounts/urls.py should look like
My app name is landing instead of accounts
from django.urls import path
from . import views
from django.contrib.auth import views as auth_views
from django.urls import reverse_lazy
app_name='landing'
urlpatterns = [
path('',views.home,name="home"),
path('terms/',views.terms,name="terms"),
path('login/',views.loginUser,name="login"),
path('signup/',views.signupUser,name="signup"),
path('about/',views.about,name="about"),
path('logout/',views.logoutUser,name="logout"),
path('password_reset/',
auth_views.PasswordResetView.as_view(template_name='landing/password_reset.html',success_url=reverse_lazy('landing:password_reset_done')),
name="password_reset"),
path('password_reset_sent/',
auth_views.PasswordResetDoneView.as_view(template_name='landing/password_reset_sent.html'),
name="password_reset_done"),
path('reset/<uidb64>/<token>/',
auth_views.PasswordResetConfirmView.as_view(template_name='landing/password_reset_form.html',success_url=reverse_lazy('landing:password_reset_complete')),
name="password_reset_confirm"),
path('password_reset_complete/',
auth_views.PasswordResetCompleteView.as_view(template_name='landing/password_reset_done.html'),
name="password_reset_complete"),
]
you have to use app_name: before the name of url you have mentioned it is very important
I have looked a lot on google for answers of how to use the 'url' tag in templates only to find many responses saying 'You just insert it into your template and point it at the view you want the url for'. Well no joy for me :( I have tried every permutation possible and have resorted to posting here as a last resort.
So here it is. My urls.py looks like this:
from django.conf.urls.defaults import *
from login.views import *
from mainapp.views import *
import settings
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Example:
# (r'^weclaim/', include('weclaim.foo.urls')),
(r'^login/', login_view),
(r'^logout/', logout_view),
('^$', main_view),
# Uncomment the admin/doc line below and add 'django.contrib.admindocs'
# to INSTALLED_APPS to enable admin documentation:
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
(r'^admin/', include(admin.site.urls)),
#(r'^static/(?P<path>.*)$', 'django.views.static.serve',{'document_root': '/home/arthur/Software/django/weclaim/templates/static'}),
(r'^static/(?P<path>.*)$', 'django.views.static.serve',{'document_root': settings.MEDIA_ROOT}),
)
My 'views.py' in my 'login' directory looks like:
from django.shortcuts import render_to_response, redirect
from django.template import RequestContext
from django.contrib import auth
def login_view(request):
if request.method == 'POST':
uname = request.POST.get('username', '')
psword = request.POST.get('password', '')
user = auth.authenticate(username=uname, password=psword)
# if the user logs in and is active
if user is not None and user.is_active:
auth.login(request, user)
return render_to_response('main/main.html', {}, context_instance=RequestContext(request))
#return redirect(main_view)
else:
return render_to_response('loginpage.html', {'box_width': '402', 'login_failed': '1',}, context_instance=RequestContext(request))
else:
return render_to_response('loginpage.html', {'box_width': '400',}, context_instance=RequestContext(request))
def logout_view(request):
auth.logout(request)
return render_to_response('loginpage.html', {'box_width': '402', 'logged_out': '1',}, context_instance=RequestContext(request))
and finally the main.html to which the login_view points looks like:
<html>
<body>
test! logout
</body>
</html>
So why do I get 'NoReverseMatch' every time?
*(on a slightly different note I had to use 'context_instance=RequestContext(request)' at the end of all my render-to-response's because otherwise it would not recognise {{ MEDIA_URL }} in my templates and I couldn't reference any css or js files. I'm not to sure why this is. Doesn't seem right to me)*
The selected answer is out of date and no others worked for me (Django 1.6 and [apparantly] no registered namespace.)
For Django 1.5 and later (from the docs)
Warning
Don’t forget to put quotes around the function path or pattern name!
With a named URL you could do:
(r'^login/', login_view, name='login'),
...
logout
Just as easy if the view takes another parameter
def login(request, extra_param):
...
login
Instead of importing the logout_view function, you should provide a string in your urls.py file:
So not (r'^login/', login_view),
but (r'^login/', 'login.views.login_view'),
That is the standard way of doing things. Then you can access the URL in your templates using:
{% url login.views.login_view %}
Make sure (django 1.5 and beyond) that you put the url name in quotes, and if your url takes parameters they should be outside of the quotes (I spent hours figuring out this mistake!).
{% url 'namespace:view_name' arg1=value1 arg2=value2 as the_url %}
link_name
The url template tag will pass the parameter as a string and not as a function reference to reverse(). The simplest way to get this working is adding a name to the view:
url(r'^/logout/' , logout_view, name='logout_view')
I run into same problem.
What I found from documentation, we should use namedspace.
in your case {% url login:login_view %}
Judging from your example, shouldn't it be {% url myproject.login.views.login_view %} and end of story? (replace myproject with your actual project name)