I created some users for my website in Django and i want each user to access only his own profile page .
The template for the user profile page is fetched through a CBV Detail View called UserDetailView attached to a URL containing the user's and the page is loading only after authentication .( user logged in). So far so good.
urls.py:
from django.conf.urls import url
from django.urls import path
from basicapp import views
from django.contrib.auth.decorators import login_required
app_name='basicapp'
urlpatterns = [
url(r'^$',views.index,name='index'),
url(r'^user_list/',views.UserView.as_view(),name='user_list'),
url(r'^course_list/',views.CourseView.as_view(),name='course_list'),
url(r'^user_detail/(?P<pk>[-\w]+)/$',views.UserDetailView.as_view(),name='user_detail'),
]
The problem is after I login and get the user detail page : If I manually change the <pk> in the URL I get other user profile pages loaded. I don't want that to happen .
For ex, the URL for the logged in user profile is :
http://127.0.0.1:8000/basicapp/user_detail/1/
With the user already logged in i manually change the URL to :
http://127.0.0.1:8000/basicapp/user_detail/2/
and it works. It should retrict me or show me an error message
I tried using LoginRequiredMixin
views.py:
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.decorators import method_decorator
class UserDetailView(LoginRequiredMixin,DetailView):
context_object_name='user_detail'
model=models.User
template_name='basicapp/user_detail.html'
raise_exception = True # Raise exception when no access instead of redirect
permission_denied_message = "This page dows not exist."
and I also tried usingmethod_decorator :
#method_decorator(login_required)
class UserDetailView(LoginRequiredMixin,DetailView):
context_object_name='user_detail'
model=models.User
template_name='basicapp/user_detail.html'
raise_exception = True # Raise exception when no access instead of redirect
permission_denied_message = "This page dows not exist."
but it doesn't seem to work . I restarted the server.
Any ideas what i am doing wrong?
The LoginRequiredMixin will ensure that you can only see the page if you are logged in, but that does not mean you have to be that user.
However if you can only see your own profile, it does not make much sense to add a primary key in the url anyway, you can just define the url as:
url(r'^user_detail/$', views.UserDetailView.as_view(), name='user_detail'),
In the view you then return the logged in user for the .get_object() method [Django-doc]:
class UserDetailView(LoginRequiredMixin,DetailView):
context_object_name='user_detail'
model=models.User
template_name='basicapp/user_detail.html'
def get_object(self, *args, **kwargs):
return self.request.user
Or you can restrict users by filtering the queryset:
path('^user_detail/<int:pk>/', views.UserDetailView.as_view(), name='user_detail'),
class UserDetailView(LoginRequiredMixin,DetailView):
context_object_name='user_detail'
model=models.User
template_name='basicapp/user_detail.html'
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
if not self.request.user.is_superuser:
qs = qs.filter(pk=self.request.user.pk)
return qs
Related
I have this url pattern:
path("user/<int:pk>", MyAccountView.as_view(), name='my_account'),
And this view:
class MyAccountView(DetailView):
model = CustomUser
When the user is logged Django redirect to that URL.
The problem is that any user can access other users.
For example, if the logged user has pk 25, he can access the view of user with pk 26 by writing in the browser url box:
localhost:8000/user/26
I want that each user can access to his user page only, so if user with pk 25 try to access the url with pk 26, the access should be denied.
Can you point me in some direction of how this is done? The Django documentation is very confusing in this respect.
Thanks.
You need to override the get method of DetailView
from django.core.exceptions import PermissionDenied
from django.contrib.auth.mixins import LoginRequiredMixin
class MyAccountView(LoginRequiredMixin, DetailView):
model = CustomUser
def get(self, request, pk):
if request.user.pk != pk:
raise PermissionDenied()
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
Easy !
First change the view path from user/<int:pk>/ to user/
Link the view to the current user, DetailView won't work because it heavily relies on either pk or slug and we won't be using none of them, so you'll have to write a new view. (Example using FBV because i do not use CBV)
# views.py
from django.contrib.auth.decorators import login_required
# redirects to login page if the user is not authenticated
#login_required(login_url='/example url you want redirect/')
def get_user_profile(request):
context = dict(user=request.user)
return render(request, "template.html", context)
And that's it, any user visiting /user/ will only see their account/profile.
I am using Django 3.2 and django-allauth 0.44
I have set my LOGIN_REDIRECT_URL in settings.py as follows:
LOGIN_REDIRECT_URL = 'profile-page'
in urls.py, I have the following route defined:
path('accounts/profile/slug:username', AccountProfileView.as_view(), name='profile-page'),
When I log in, (unsurprisingly), I get the following error message:
NoReverseMatch at /accounts/login/
Reverse for 'profile-page' with no arguments not found. 1 pattern(s) tried: ['accounts/profile/(?P[-a-zA-Z0-9_]+)$']
How do I pass (or specify) a parameter of the logged in user's username to the route?
If your view needs to perform redirects which are not very simple, you need to override the get_success_url method, considering that you use django-allauth, you will need to override allauth.account.views.LoginView and also write your own url pattern for it so that your overriden view is used. First override the view:
from django.urls import reverse
from allauth.account.views import LoginView as AllauthLoginView
from allauth.account.utils import get_next_redirect_url
class LoginView(AllauthLoginView):
def form_valid(self, form):
self.user = form.user # Get the forms user
return super().form_valid(form)
def get_success_url(self):
ret = (
get_next_redirect_url(self.request, self.redirect_field_name)
or reverse('profile-page', kwargs={'username': self.user.username})
)
return ret
Next wherever you define the urls for allauth, just add your own url before it:
from path_to.view_above import LoginView # Change this import properly
urlpatterns = [
...
path('accounts/login/', LoginView.as_view(), name="account_login"),
path('accounts/', include('allauth.urls')),
...
]
Another alternative solution using allauth is to use a custom ACCOUNT_ADAPTER and override it's get_login_redirect_url because LoginView will internally call it in case there is no next parameter. To do this, first inherit from allauth.account.adapter.DefaultAccountAdapter:
from django.urls import reverse
from allauth.account.adapter import DefaultAccountAdapter
class MyAccountAdapter(DefaultAccountAdapter):
def get_login_redirect_url(self, request):
return reverse('profile-page', kwargs={'username': request.user.username})
Next in settings.py set ACCOUNT_ADAPTER:
ACCOUNT_ADAPTER = "path.to.MyAccountAdapter"
The LOGIN_REDIRECT_URL should point to the page on which user lands after he successfully logs into your website, unless he was redirected to the login page by trying to visit any of the pages that require the authorization.
If you want to redirect user to a specific page, in this example to his own profile page, you can create an intermediate URL that will redirect the user to his own profile page, when visited. It can be accomplished like so:
Using class-based views:
class CurrentUserProfileRedirectView(LoginRequiredMixin, RedirectView):
def get_redirect_url(self, *args, **kwargs):
return reverse('profile-page', kwargs={'username': request.user.username})
or using function-based views:
#login_required
def current_user_profile(request):
return redirect('profile-page', username=request.user.username)
Next, register this redirect view as a regular view, not requiring any parameters, and set the LOGIN_REDIRECT_URL to this view's name.
My django-admin page is located at
http://127.0.0.1:8000/admin/
Suppose there are 3 users in my website.
Superuser
Staff
End user
If anyone of these three users tries to access this link http://127.0.0.1:8000/admin/, 1st and 2nd can access it.
And End User is not able to access it. Till this, it is fine.
What I want is to do is to show Error 404 to End User. He/She should never know that this link is for admin page. Moreover, if anyone is not logged in, they should also see same 404 Page.
I have referred this link but it takes me to another default page.
PFA for the same
PS: I am using Signin With Google, so it should not redirect me there.
I am trying this since 3 continous days, so any help is highly appreciated. Thanks in advance.
Django-Version: 3. 0. 5
You first need to make a custom decorator that would give a 404 if the user is not a staff:
from django.http import Http404
from functools import wraps
def staff_required(func):
#wraps(func)
def wrapper(request, *args, **kwargs):
if request.user.is_staff:
return func(request, *args, **kwargs)
raise Http404()
return wrapper
Next we will use this decorator as described in your linked question,:
from django.contrib import admin
admin.site.login = staff_required(admin.site.login)
urlpatterns = [
path('admin/', admin.site.urls),
]
Edit: Above method was a bit hacky it would show the login pages url to the user even if it gives a 404 error. It would be better to make a custom admin site class and use it. The admin site has a method admin_view which decorates the admin views which we shall override. We shall do this in the file admin.py in the projects main app (let's say the project is named myproject)
from functools import update_wrapper
from django.contrib import admin
from django.http import (
Http404, HttpResponseRedirect,
)
from django.urls import reverse
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
class MyAdminSite(admin.AdminSite):
def admin_view(self, view, cacheable=False):
def inner(request, *args, **kwargs):
if not self.has_permission(request):
if request.path == reverse('admin:logout', current_app=self.name):
index_path = reverse('admin:index', current_app=self.name)
return HttpResponseRedirect(index_path)
raise Http404()
return view(request, *args, **kwargs)
if not cacheable:
inner = never_cache(inner)
# We add csrf_protect here so this function can be used as a utility
# function for any view, without having to repeat 'csrf_protect'.
if not getattr(view, 'csrf_exempt', False):
inner = csrf_protect(inner)
return update_wrapper(inner, view)
Now we need to replace the default admin site with our custom admin site. To do this we will follow Overriding the default admin site [Django docs]:
In the projects main app's apps.py file (assuming project to be named myproject):
from django.contrib.admin.apps import AdminConfig
class MyAdminConfig(AdminConfig):
default_site = 'myproject.admin.MyAdminSite'
Now in settings.py we will replace 'django.contrib.admin' with our own config class:
INSTALLED_APPS = [
...
'myproject.apps.MyAdminConfig', # replaces 'django.contrib.admin'
...
]
A simpler way is to create your own middleware that whenever the path starts with /admin/ and the user is logged in but doesn't have is_staff set to True (both staff and superusers have it), then raise the 404.
from django.http import Http404
class NoDjangoAdminForEndUserMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.path.startswith("/admin/"):
if request.user.is_authenticated and not request.user.is_staff:
raise Http404()
response = self.get_response(request)
return response
Then, to activate it, add it to the MIDDLEWARE list in your Django settings.
You'll still see the URL with http://127.0.0.1:8000/admin/, despite seeing a 404 page. If you want to redirect the user to a different page, instead of raising the Http404(), then return an HttpResponseRedirect().
I am new to django.In my project,I have many users,how to control the Url access by different user?
eg:
urlpatterns = [
url(r'man/dash/(?P<base>[0-9]{8})/$', man_dash ,name='man_dash')
]
The url can access By User A and B has no permisson.
I will appreciate it if someone can give me a instance...
In your views.py:
from django.contrib.auth.decorators import user_passes_test
def my_check(user):
return user.username == 'A'
#user_passes_test(my_check)
def man_dash(request):
This shouldn't be done on URL level, but view level. In the view that the URL redirects to, you can check if the user has the permissions he/she needs and then either return a site or return a 404 page or 403 forbidden page.
example:
from django.views.generic import TemplateView
from django.http import HttpResponseNotAllowed
class MyView(TemplateView):
template_name = 'sth.html'
def get(self, request, **kwargs):
if request.user.is_superuser():
return super().get(request, **kwargs)
return HttpResponseNotAllowed()
I am trying to use Django with allauth trying to create a login page.
I am able to login and redirect the user to LOGIN_REDIRECT_URL (= "/users/{id}/mypage") page successfully. I have a separate view for /users/{id}/mypage called the UserHomePageView as defined in views.py:
from django.views.generic import TemplateView
class UserHomePageView(TemplateView):
template_name = "user_homepage.html"
I want a security measure to prevent anyone (basically non-loggedin-users) to navigating to "/users/{id}/mypage" (e.g /users/5/mypage). e.i If unauthenticated user tries to navigate to /users/5/mypage, he/she should get redirected to signin page/homepage.
Basically, how do i present a unauthenticated user to view pages that are meant for authenticated users (I don't want to use template tags users.authenitcated, but wish to override the TemplateView)
Another solution which is generally used is to decorate the dispatch method with the login_required decorator:
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
class UserHomePageView(TemplateView):
template_name = "user_homepage.html"
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(UserHomePageView, self).dispatch(*args, **kwargs)
This has the advantage that it works with all methods (get, post, put, etc.), and that using the login_required decorator is more DRY than explicitly checking request.user.is_authenticated and redirecting.
I found the answer...
By overriding get() and using self.request.user.is_authenticated()
class UserHomePageView(TemplateView):
template_name = "user_homepage.html"
redirect_field_name = "next"
def get(self, request, *args, **kwargs):
'''
Overriding TemplateView.get() in order to
prevent unauthorized user to view UserHomePageView
'''
# Redirect to Homepage if user is not signed in
if not self.request.user.is_authenticated():
return redirect(self.get_redirect_url())
# Process normally if User is signed in
context = self.get_context_data(**kwargs)
return self.render_to_response(context)