I'm trying to crate Django web project , and there are some User backend application where every URL ( view ) only for authenticated users.
I can write a condition in every view for example:
User/urls.py
urlpatterns = patterns('',
url(r'^settings/', 'User.views.user_settings'),
url(r'^profile/', 'User.views.profile'),
#Other User Admin panel Urls
)
User/views.py
def user_settings(request):
if request.user is None:
return redirect('/login')
#Some other code
def profile(request):
if request.user is None:
return redirect('/login')
#Some other code
As you can see it's not well coded but it works fine.
The only thing that I want to know , is it possible to add some condition for urls.py file all urls for not adding same code lines in every view function.
For example Symfony , Laravel and Yii frameworks have something like that what I want to do.
Is it possible do this in Django ? :)
Edited Here
With #login_required I need to add it for every view, I need something for all urls for example:
In Symfony framework I can write
{ path: ^/myPage, roles: AUTHENTICATED } and every url like this /myPage/someUrl will be asked for authentication.
I believe that Django have something like that :)
Thanks.
Well if you use a class based view, it makes easier for you to add #login_required. For example you can create a class based view here:
class BaseView(TemplateView):
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(BaseView, self).dispatch(*args, **kwargs)
Now override it each time you want to create a new view. For example:
class SettingsView(BaseView):
def get(request):
return (...)
It will check each time at url dispatch if the user is logged in.
Also, if you use a class based view, you can override get() to check if the user is authenticated.
class BaseView(TemplateView):
template_name= None
role= None
def get(self, request, *args, **kwargs):
if request.user is not None and role is "AUTHENTICATE":
return super(BaseView, self).get(request, *args, **kwargs)
else:
raise Exception('user is not logged in')
urls.py:
url(r'^settings/', BaseView.as_view(template_name='/sometemplate', role="AUTHENTICATE")
You can use the #login_required decorator:
https://docs.djangoproject.com/en/1.5/topics/auth/default/#the-login-required-decorator
From the docs:
login_required() does the following:
If the user isn’t logged in, redirect to settings.LOGIN_URL, passing the current absolute path in the query string. Example: /accounts/login/?next=/polls/3/.
If the user is logged in, execute the view normally. The view code is free to assume the user is logged in.
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
...
You could use this : https://docs.djangoproject.com/en/1.6/topics/auth/default/#the-login-required-decorator
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
...
Related
I want to know the major difference between self.request.user (when using generic view View) and request.user (when using a user defined view), at times when I use django's generic View (django.views.generic import View) which has access to the django User model, I'd interact with the authenticated user as request.user and self.request.user without having any problem.
For instance, in a django views.py:
from django.contrib.auth import get_user_model
User = get_user_model()
class GetUserView(View):
def get (self, request, *args, **kwargs):
user_qs = User.objects.filter(username=request.user.username)
#Do some other stuffs here
class AnotherGetUserView(View):
def get(self, request, *args, **kwargs):
user_qs = User.objects.filter(username=self.request.user.username)
#Do some other stuffs here
I realize that the two queryset works fine but I can't still figure out the best to use.
There is no difference. Before triggering the get method, it will run the .setup(…) method [Django-doc] which will set self.request = request as well as self.args and self.kwargs to the positional and named URL parameters respectively. Unless you override this, self.request will thus always refer to the same object as request, and the documentation explicitly says that you should not override the .setup(…) method without making a call to the super method which will thus set self.request.
It however does not make much sense to do User.objects.filter(username=request.user.username): you already have the user object: that is request.user, so here you will only make extra database requests. user_qs is simply a queryset with one element: request.user. You can thus for example use request.user.email to obtain the email address of the logged in user.
You might want to use the LoginRequiredMixin [Django-doc] to ensure that the view can only be called if the user has been logged in, so:
from django.contrib.auth.mixins import LoginRequiredMixin
class GetUserView(LoginRequiredMixin, View):
def get (self, request, *args, **kwargs):
# …
I am using Flask-Login for the login handling. In addition I wrote myself a decorator to keep views visible just for role=admin.
def admin_required(f):
#wraps(f)
def wrap(*args, **kwargs):
if current_user.role.name == "admin":
return f(*args, **kwargs)
else:
flash("You need to be an admin to view this page.")
return redirect(url_for('home'))
return wrap
I can use that decorator for my routes. But I don't know how to use this decorator for my flask-admin panel.
The Flask-SQLAlchemy Models were added to the flask-admin view like:
from flask_admin.contrib.sqla import ModelView
from flask_admin import Admin
admin = Admin(app, name='admin', template_mode='bootstrap3')
admin.add_view(ModelView(User, db.session))
admin.add_view(ModelView(Role, db.session))
But I want if someone who is not an admin hits: 127.0.0.1:8000/admin do not get access and gets redirected to /home.
But I have no route for my /admin and when I implement one I do not know what html I should return?
Thank you
Use the built-in method is_accessible, by deafult, it returns True, which means that admin panel will be accessible by all, you can change it by overriding it like this:
class MyAdminViews(ModelView):
def is_accessible(self):
user = User.query.filter_by(role=current_user.role).first()
res = user.role == "admin"
return res
admin.add_view((MyAdminViews(User, db.session)))
admin.add_view((MyAdminViews(Post, db.session)))
Now, these admin views will be accessible if and only if the user possesses an admin role. Also, you might want to add a clause to make it accessible only when someone is logged in, else the browser will throw an internal server error. You can do it like this:
class MyAdminViews(ModelView):
def is_accessible(self):
if current_user.is_authenticated:
user = User.query.filter_by(role=current_user.role).first()
res = user.role == "admin"
return res
I don't know how can I prevent using Global variable to pass data to another function. It's too much hassle to create request.session on every page just to get the value set by another function. I know there's a way e.g using decorators etc. but I haven't found yet the best way on how to implement it. Can anyone help me? Below I been using Global variable in usertype request.session['type_user'] = usertype. Thanks for the help.
def example_login(request):
usertype = "Admin"
request.session['type_user'] = usertype
return redirect ('dashboard')
def dashboard(request):
value_usertype = {'usertype_var':request.session['type_user']}
return render(request,'dashboard.html', value_usertype)
def anotherview(request): # too hassle to create another request.session in every page
value_usertype = {'usertype_var':request.session['type_user']}
return render(request,'dashboard.html', value_usertype)
Function-based view
You can construct a decorator that will pass the usertype_var to the view function:
from functools import wraps
def with_usertype(f):
#wraps(f)
def wrapped(request, *args, **kwargs):
return f(request, *args, **kwargs, usertype=request.session.get('type_user'))
return wrapped
You can then use the decorator as:
#with_usertype
def dashboard(request, usertype):
return render(request,'dashboard.html', {'usertype_var': usertype})
#with_usertype
def anotherview(request, usertype):
return render(request,'dashboard.html', , {'usertype_var': usertype})
You can also restrict it with:
from functools import wraps
from django.core.exceptions import PermissionDenied
def restrict_usertype(*restrictions):
def mydecorator(f):
#wraps(f)
def wrapped(request, *args, **kwargs):
usertype = request.session.get('type_user')
if usertyp in restrictions:
return f(request, *args, **kwargs, usertype=usertype)
else:
raise PermissionDenied
return wrapped
return mydecorator
then you can restrict this with:
#restrict_usertype('Admin')
def dashboard(request, usertype):
return render(request,'dashboard.html', {'usertype_var': usertype})
#restrict_usertype('Admin', 'Other')
def anotherview(request, usertype):
return render(request,'dashboard.html', , {'usertype_var': usertype})
Class-based views
For class-based views, since django-2.2 you can make use of the setup(…) method [Django-doc]. This method normally sets the request, args and kwargs to the view object:
# since Django-2.2
class UsertypeMixin:
def setup(self, request, *args, **kwargs):
self.usertype = request.session.get('type_user')
return super().setup(*args, **kwargs)
def get_context_data(self, *args, **kwargs):
return super().get_context_data(
*args,
**kwargs,
usertype=self.usertype
)
Then the mixin can be used, for example in a ListView with:
class MyListView(UserTypeMixin, ListView):
def get_queryset(self, *args, **kwargs):
# use self.usertype
# …
return super().get_queryset(*args, **kwargs)
we can restrict this as well with:
# since Django-2.2
from django.core.exceptions import PermissionDenied
class UsertypeRestrictMixin(UsertypeMixin):
required_usertype = ()
def setup(self, request, *args, **kwargs):
result = super().setup(*args, **kwargs)
if self.usertype not in self.required_usertype:
raise PermissionDenied
return result
and then use this as:
class MyListView(UsertypeRestrictMixin, ListView):
required_usertype = ('Admin',)
Django's groups and permissions
However often a better modeling is using groups, since a user can often have multiple roles. It can be a commentator, administrator, author, etc. and often after a while users can have multiple roles. Django has a system of groups and permissions [Django-doc] for this in place.
The advantage of using permissions is that this is more fine-grained, so one can make permissions for each (type of) view, and furthermore it allows a person to be a member of multiple groups, and also thus obtain the permissions of all these groups. This makes it more flexible since sometimes you want to give a small set of users an extra permission.
I suggest you to rely on Django's auth system for both authentication (username + password = user1) and authorization (can user1 user this view?)
Using Django's auth system:
Login:
from django.contrib.auth import authenticate, login
def my_view(request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
# Redirect to a success page.
...
else:
# Return an 'invalid login' error message.
...
Logout:
from django.contrib.auth import logout
def logout_view(request):
logout(request)
# Redirect to a success page.
Limiting access for authenticated users
redirecting to login
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
...
redirecting to custom template
from django.contrib.auth.decorators import login_required
#login_required(redirect_field_name='my_redirect_field')
def my_view(request):
...
Limiting access for authorized users
based on roles (Django's recommended approach)
from django.contrib.auth.decorators import permission_required
#permission_required('your_app.admin_rigths')
def my_view(request):
...
based on groups (custom way)
from django.contrib.auth.decorators import user_passes_test
def is_admin(user):
if your_custom_verification_for_admin_rights:
return True
return Fa
#user_passes_test(email_check)
def my_view(request):
...
See more in https://docs.djangoproject.com/en/3.1/topics/auth/default/#auth-web-requests
Custom auth approach
You can also not make use of Django's approach, as I needed once. I did the following:
Create an authentication middleware
class CustomAuthMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# do your custom verification
user_type = get_user_type_function(request)
# add its result to the request object
request.__setattr__('type_user', user_type)
# go to normal request flow
return self.get_response(request)
Add it to settings
MIDDLEWARE = [
...django's middleware
'your_auth_app.path.to.middleware.CustomAuthMiddleware',
Create a decorator for authorization enforcement
from django.core.exceptions import PermissionDenied
def needs_user_type(user_type):
def auth_decorator(function):
def wrapper(request, *args, **kwargs):
authorized = False
if hatattr(request, 'type_user'):
request_user_type = request.__getattribute__('type_user')
authorized = request_user_type == user_type
if authorized:
return function(*args, **kwargs)
else:
return PermissionDenied()
return wrapper
return auth_decorator
Use the decorator in your views
#needs_user_type('Admin')
def dashboard(request):
# do stuff
#needs_user_type('Admin')
def anotherview(request):
# do stuff
If you want to access user_type in your views, see Django's Request Context
I believe this is a simple one, just can't spot the solution. I have a view that does a bit of work on the server then passes the user back to another view, typically the original calling view.
The way I'm rendering it now, the url isn't redirected, ie it's the url of the original receiving view. So in the case the user refreshes, they'll run that server code again.
class CountSomethingView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
# so some counting
view = MyDetailView.as_view()
return view(request, *args, **kwargs)
I strongly recommend not overriding get or post methods. Instead, override dispatch. So, to expand on Platinum Azure's answer:
class CountSomethingView(LoginRequiredMixin, RedirectView):
permanent = False
def get_redirect_url(self, **kwargs):
url = you_can_define_the_url_however_you_want(**kwargs)
return url
def dispatch(self, request, *args, **kwargs):
# do something
return super(CountSomethingView, self).dispatch(request, *args, **kwargs)
when a user does an action and I need to redirect him to the same page, first of all I use a templateView to display a simple "thanks" (for example) then provide a link to go back to the previous page with a simple {% url %}
for example :
from django.views.generic import CreateView, TemplateView
from django.http import HttpResponseRedirect
class UserServiceCreateView(CreateView):
form_class = UserServiceForm
template_name = "services/add_service.html"
def form_valid(self, form):
[...]
return HttpResponseRedirect('/service/add/thanks/')
class UserServiceAddedTemplateView(TemplateView):
template_name = "services/thanks_service.html"
def get_context_data(self, **kw):
context = super(UserServiceAddedTemplateView, self).\
get_context_data(**kw)
context['sentance'] = 'Your service has been successfully created'
return context
in the template thanks_service.html i use {% url %} to go back to the expected page
Hope this can help
Performing a redirect in a Django Class Based View is easy.
Simply do a return redirect('your url goes here').
However, I believe this isn't what you want to do.
I see you're using get().
Normally, when speaking about HTTP, a GET request is seldom followed by a redirect.
A POST request is usually followed by a redirect because when the user goes backwards you wouldn't want to submit the same data again.
So what do you want to do?
What I think you want to do is this:
def get(self, request, *args, **kwargs):
return render_to_response('your template', data)
or even better
def get(self, request, *args, **kwargs):
return render(request, self.template_name, data)
If you're creating or updating a model, consider inheriting from CreateView or UpdateView and specifying a success_url.
If you're really doing a redirect off of an HTTP GET action, you can inherit from RedirectView and override the get method (optionally also specifying permanent = False):
class CountSomethingView(LoginRequiredMixin, RedirectView):
permanent = False
def get(self, request, *args, **kwargs):
# do something
return super(CountSomethingView, self).get(self, request, *args, **kwargs)
Note that it's really bad practice to have a get action with side-effects (unless it's just populating a cache or modifying non-essential data). In most cases, you should consider using a form-based or model-form-based view, such as CreateView or UpdateView as suggested above.
I want to be able to set an option in the user's settings that forces them to change their password upon the next login to the admin interface. Is this possible? How would it go about being implemented? I'm using the default auth model right now but not opposed to modifying or changing it. Thanks for any help.
I'm actually in the process of doing this myself. You need three components: a user profile (if not already in use on your site), a middleware component, and a pre_save signal.
My code for this is in an app named 'accounts'.
# myproject/accounts/models.py
from django.db import models
from django.db.models import signals
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
force_password_change = models.BooleanField(default=False)
def create_user_profile_signal(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
def password_change_signal(sender, instance, **kwargs):
try:
user = User.objects.get(username=instance.username)
if not user.password == instance.password:
profile = user.get_profile()
profile.force_password_change = False
profile.save()
except User.DoesNotExist:
pass
signals.pre_save.connect(password_change_signal, sender=User, dispatch_uid='accounts.models')
signals.post_save.connect(create_user_profile_signal, sender=User, dispatch_uid='accounts.models')
First, we create a UserProfile with a foreign key to User. The force_password_change boolean will, as its name describes, be set to true for a user whenever you want to force them to change their password. You could do anything here though. In my organization, we also chose to implement a mandatory change every 90 days, so I also have a DateTimeField that stores the last time a user changed their password. You then set that in the pre_save signal, password_changed_signal.
Second, we have the create_user_profile_signal. This is mostly added just for completeness. If you're just now adding user profiles into your project, you'll need a post_save signal that will create a UserProfile every time a User is created. This accomplishes that task.
Third, we have the password_changed_signal. This is a pre_save signal because at this point in the process the actual row in the User table hasn't be updated. Therefore, we can access both the previous password and the new password about to be saved. If the two don't match, that means the user has changed their password, and we can then reset the force_password_change boolean. This would be the point, also where you would take care of any other things you've added such as setting the DateTimeField previously mentioned.
The last two lines attach the two functions to their appropriate signals.
If you haven't already, you will also need to add the following line to your project's settings.py (changing the app label and model name to match your setup):
AUTH_PROFILE_MODULE = 'accounts.UserProfile'
That covers the basics. Now we need a middleware component to check the status of our force_password_change flag (and any other necessary checks).
# myproject/accounts/middleware.py
from django.http import HttpResponseRedirect
import re
class PasswordChangeMiddleware:
def process_request(self, request):
if request.user.is_authenticated() and \
re.match(r'^/admin/?', request.path) and \
not re.match(r'^/admin/password_change/?', request.path):
profile = request.user.get_profile()
if profile.force_password_change:
return HttpResponseRedirect('/admin/password_change/')
This very simple middleware hooks into the process_request stage of the page loading process. It checks that 1) the user has already logged in, 2) they are trying to access some page in the admin, and 3) the page they are accessing is not the password change page itself (otherwise, you'd get an infinite loop of redirects). If all of these are true and the force_password_change flag has been set to True, then the user is redirected to the password change page. They will not be able to navigate anywhere else until they change their password (firing the pre_save signal discussed previously).
Finally, you just need to add this middleware to your project's settings.py (again, changing the import path as necessary):
MIDDLEWARE_CLASSES = (
# Other middleware here
'myproject.accounts.middleware.PasswordChangeMiddleware',
)
I have used Chris Pratt's solution, with a little change: instead of using a middleware, that'd be executed for every page with the consequent resource use, I figured I'd just intercept the login view.
In my urls.py I have added this to my urlpatterns:
url(r'^accounts/login/$', 'userbase.views.force_pwd_login'),
then I added the following to userbase.views:
def force_pwd_login(request, *args, **kwargs):
response = auth_views.login(request, *args, **kwargs)
if response.status_code == 302:
#We have a user
try:
if request.user.get_profile().force_password_change:
return redirect('django.contrib.auth.views.password_change')
except AttributeError: #No profile?
pass
return response
It seems to work flawlessly on Django 1.2, but I have no reason to believe 1.3+ should have problems with it.
This is the middleware I use with Django 1.11 :
# myproject/accounts/middleware.py
from django.http import HttpResponseRedirect
from django.urls import reverse
class PasswordChangeMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
next = reverse('client:password-update')
if request.user.is_authenticated() and request.path != next:
if request.user.account.force_password_change:
return HttpResponseRedirect(next)
return response
Still adding it to the settings middleware list :
MIDDLEWARE_CLASSES = (
# Other middleware here
'myproject.accounts.middleware.PasswordChangeMiddleware',
)
I spent 2 days on this issue recently, and a new solution came out.
Hopefully it's useful.
Just as above said, a new user model created.
newuser/models.py
class Users(AbstractUser):
default_pwd_updated = models.NullBooleanField(default=None, editable=False)
pwd_update_time = models.DateTimeField(editable=False, null=True, default=None) # reserved column to support further interval password (such as 60 days) update policy
def set_password(self, raw_password):
if self.default_pwd_updated is None:
self.default_pwd_updated = False
elif not self.default_pwd_updated:
self.default_pwd_updated = True
self.pwd_update_time = timezone.now()
else:
self.pwd_update_time = timezone.now()
super().set_password(raw_password)
Set this model as the AUTH_USER_MODEL.
[project]/settings.py
AUTH_USER_MODEL = 'newuser.Users'
Now you just need to customize LoginView and some methods in AdminSite.
[project]/admin.py
from django.contrib.admin import AdminSite
from django.contrib.auth.views import LoginView
from django.utils.translation import gettext as _, gettext_lazy
from django.urls import reverse
from django.views.decorators.cache import never_cache
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.http import HttpResponseRedirect
class NewLoginView(LoginView):
def get_redirect_url(self):
if self.request.method == "POST" and self.request.user.get_username()\
and not self.request.user.default_pwd_updated:
redirect_to = reverse("admin:password_change")
else:
redirect_to = self.request.POST.get(
self.redirect_field_name,
self.request.GET.get(self.redirect_field_name, '')
)
return redirect_to
class NewAdminSite(AdminSite):
site_header = site_title = gettext_lazy("Customized Admin Site")
def __init__(self, name="admin"):
super().__init__(name)
#never_cache
def login(self, request, extra_context=None):
"""
Display the login form for the given HttpRequest.
"""
if request.method == 'GET' and self.has_permission(request):
# Already logged-in, redirect to admin index
if request.user.get_username() and not request.user.default_pwd_updated:
# default password not changed, force to password_change view
path = reverse('admin:password_change', current_app=self.name)
else:
path = reverse('admin:index', current_app=self.name)
return HttpResponseRedirect(path)
from django.contrib.auth.views import LoginView
from django.contrib.admin.forms import AdminAuthenticationForm
context = {
**self.each_context(request),
'title': _('Log in'),
'app_path': request.get_full_path(),
'username': request.user.get_username(),
}
if (REDIRECT_FIELD_NAME not in request.GET and
REDIRECT_FIELD_NAME not in request.POST):
context[REDIRECT_FIELD_NAME] = reverse('admin:index', current_app=self.name)
context.update(extra_context or {})
defaults = {
'extra_context': context,
'authentication_form': self.login_form or AdminAuthenticationForm,
'template_name': self.login_template or 'admin/login.html',
}
request.current_app = self.name
return NewLoginView.as_view(**defaults)(request) # use NewLoginView
#never_cache
def index(self, request, extra_context=None):
if request.user.get_username() and not request.user.default_pwd_updated:
# if default password not updated, force to password_change page
context = self.each_context(request)
context.update(extra_context or {})
return self.password_change(request, context)
return super().index(request, extra_context)
admin_site = NewAdminSite(name="admin")
NOTE: if you intend to use custom template for changing default password, you could override each_context method and then determine which template should be used up to the flag force_pwd_change.
[project]/admin.py
def using_default_password(self, request):
if self.has_permission(request) and request.user.get_username() and not request.user.default_pwd_updated:
return True
return False
def each_context(self, request):
context = super().each_context(request)
context["force_pwd_change"] = self.using_default_password(request)
return context
From a thread on the Django Users mailing list:
This isn't ideal, but it should work
(or prompt someone to propose
something better).
Add a one-to-one table for the user,
with a field containing the initial
password (encrypted, of course, so it
looks like the password in the
auth_user table).
When the user logs in, have the login
page check to see if the passwords
match. If they do, redirect to the
password change page instead of the
normal redirect page.
Checkout this simple package based on session (Tested with django 1.8). https://github.com/abdullatheef/django_force_reset_password
Create custom view in myapp.views.py
class PassWordReset(admin.AdminSite):
def login(self, request, extra_context=None):
if request.method == 'POST':
response = super(PassWordReset, self).login(request, extra_context=extra_context)
if response.status_code == 302 and request.user.is_authenticated():
if not "fpr" in request.session or request.session['fpr']:
request.session['fpr'] = True
return HttpResponseRedirect("/admin/password_change/")
return response
return super(PassWordReset, self).login(request, extra_context=extra_context)
def password_change(self, request, extra_context=None):
if request.method == 'POST':
response = super(PassWordReset, self).password_change(request, extra_context=extra_context)
if response.status_code == 302 and request.user.is_authenticated():
request.session['fpr'] = False
return response
return super(PassWordReset, self).password_change(request, extra_context=extra_context)
pfr_login = PassWordReset().login
pfr_password_change = PassWordReset().admin_view(PassWordReset().password_change, cacheable=True)
Then in project/urls.py
from myapp.views import pfr_password_change, pfr_login
urlpatterns = [
......
url(r'^admin/login/$', pfr_login),
url(r'^admin/password_change/$', pfr_password_change),
url(r'^admin/', admin.site.urls),
....
]
Then add this middleware myapp/middleware.py
class FPRCheck(object):
def process_request(self, request):
if request.user.is_authenticated() \
and re.match(r'^/admin/?', request.path) \
and (not "fpr" in request.session or ("fpr" in request.session and request.session['fpr'])) \
and not re.match(r"/admin/password_change|/admin/logout", request.path):
return HttpResponseRedirect("/admin/password_change/")
Order of middleware
MIDDLEWARE_CLASSES = [
....
'myapp.middleware.FPRCheck'
]
Note
This will not need any extra model.
Also work with any Session Engine.
No db query inside middleware.