I have setup a basic Django site and have added login to the site. Additionally, I have created a Student (Profile) model that expands upon the built in User one. It has a OneToOne relationship with the User Model.
However, I have yet not come right with forcing the user to automatically create a Profile the first time they log in. How would I make sure they are not able to progress through anything without creating this?
I have tried by defining the following in views:
def UserCheck(request):
current_user = request.user
# Check for or create a Student; set default account type here
try:
profile = Student.objects.get(user = request.user)
if profile == None:
return redirect('/student/profile/update')
return True
except:
return redirect('/student/profile/update')
And thereafter adding the following:
UserCheck(request)
at the top of each of my views. This however, never seems to redirect a user to create a profile.
Is there a best way to ensure that the User is forced to create a profile object above?
Looks like you're attempting to do something similar to Django's user_passes_test decorator (documentation). You can turn the function you have into this:
# Side note: Classes are CamelCase, not functions
def user_check(user):
# Simpler way of seeing if the profile exists
profile_exists = Student.objects.filter(user=user).exists()
if profile_exists:
# The user can continue
return True
else:
# If they don't, they need to be sent elsewhere
return False
Then, you can add a decorator to your views:
from django.contrib.auth.decorators import user_passes_test
# Login URL is where they will be sent if user_check returns False
#user_passes_test(user_check, login_url='/student/profile/update')
def some_view(request):
# Do stuff here
pass
Related
Hej!
I'm looking for a possibility to add entries by different users and groups.
In my app there are different users and different groups where one user can belong to multiple groups. The goal is that some of the groups have sensitive data so I only want members of this group to be able to read the entries but some data should be available in multiple groups.
It should also be possible to only see some information. (e.g. See the name but not the address)
For now I have the decorator #login_required for all my views and #permission_required for a persons_view. Therefore you have to be a registered user to see anything, which is great but not quite enough.
I also created groups (via the admin area) but can't filter the data for each model/view. The group either sees the data or they don't.
The registration of users is connceted to an already existing ldap system (where those groups are pre-defined. Would be great to use those!)
Is there a possibility to add entries only for a special group and view only some of the given information?
Thanks for the help!
If you want to restrict or specify a view for a group, you should use two mixins: UserPassesTestMixin and LoginRequiredMixin.
LoginRequiredMixin will just make sure the user in the request is logged in
UserPassesTestMixin will check the request from a boolean expression we define and if it returns True it will let the user in
We can set this up using a test_func method that will return either True or False
Here is an example that you can use in your view
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
class Index(LoginRequiredMixin, UserPassesTestMixin,View):
def get(self, request):
return render(request, 'nioulboy/index.html')
def test_func(self):
return self.request.user.groups.filter(name='Admin')
In the test_func method, we are returning true if the request.user has a group with the name Admin.
If it returns True, it will allow the user to view the index page if it returns False it will send back a 403 error.
for NOT class based views works this function to give permission to a whole view.
# views.py
from django.contrib.auth.decorators import login_required, user_passes_test
def user_check(user):
return user.groups.filter(name="Admin")
#user_passes_test(user_check)
#login_required
def view(request):
....
If I am logged in as user1 and I am accessing a ViewSet called RecipeSubmissionViewSet, the POST takes a recipe_id of the recipe the user wants to submit. How do I ensure that user1 does not submit user2's recipe, assuming the Recipe model has an owner field on it and I can just compare it to request.user? Should I use a permission class for this or is there a better way? I'm speaking from an backend point of view and not taking into account that the front end would of course filter out the recipes that belong to the user and only show them their own recipes.
There can be two ways. You can filter out queryset or define permission class.
If you override get_queryset method like this.
class RecipeSubmissionViewSet(...):
def get_queryset(self):
return Recipe.objects.filter(owner=self.request.user)
# you can also use filtration based on action name like this
# if self.action == 'update':
# return Recipe.objects.filter(owner=self.request.user)
# return Recipe.objects.all()
User will get 404 response and will never be able to access objects other than he owns.
Second choice is permission class. You can define custom permission class and check ownership explicitly like this.
from rest_framework.permissions import BasePermission
class RecipeSubmissionPermission(BasePermission):
def has_object_permission(self, request, view, obj):
# you can also check permission here based on action
# if view.action == 'update':
# pass
return request.user.is_authenticated and obj.owner == request.user
class RecipeSubmissionViewSet(...):
permission_classes=[RecipeSubmissionPermission]
In this case user will get 403 permission error.
If you use both of these methods. 404 will be preferred.
You can use whichever method you want or both of these. Permission class looks more programmatic and structured way of doing it but user will know that object with this id exists but he did not have permission to update it. But if you override queryset, user is not even be able to know if object exists or not thus more secure.
Are you using the django authentication system? Then you should be able to access request.user in the views and set the owner field accordingly.
EDIT: I think I misunderstood the question.
But this could help and Nafees Anwar looks good.
Nafees answer is pretty much the way to go.
I have been developing microservices with multitenancy to users and the rules for them(as per my projecs spec) are:
Users cannot create items on behalf of another company/user
Users cannot view/edit items belonging to another company.
The way I do this is simple.
To prohibit viewing/editing of someone else's stuff
def get_queryset(self):
return self.queryset.filter(user=request.user)
And to prohibit editing of someone else's stuff, this is done on a serializer
class SomeSerializer(...):
def validate(self, data):
data.pop('user', None)
data['user'] = self.context['request'].user
With the above, the get_queryset in the viewset will always return a 404 if user1 requests user2 info.
And the validate function will prevent assignment. Ie. If user1 creates something assigned to user2, it will rather assign user1, this behaviour is what I needed in my application, you can always raise serializers.ValidationError("You cannot assign stuff to user2") instead if thats what you need instead of reassigning the user as I do in my use case. With having this logic in the validate you can be sure that any writable function will always carry the same behaviour for that serializer.
Hope that this helps.
I have a Django intranet which is reachable on http(s)://somename/ and http(s)://10.10.0.30/, using the ALLOWED_HOSTS setting:
ALLOWED_HOSTS = [u'10.10.0.30', u'somename',]
Now I'd like to allow certain users to login into the website remotely as well. As a first step I'll have to add my external URL (like somename.com) to the ALLOWED_HOSTS; no problem there. But from that moment on, everyone with an account will be able to log in, which is not what I want.
I was thinking in terms of having some group called PermitRemoteLogin - when a user is part of that group, logging in from host somename.com would be allowed. But I'm unsure about the actual implementation and/or whether this is doable in the first place (?).
When searching e.g. DjangoPackages, no results were found. Any idea whether this has been done before?
I've done similar things in the past, it's quite easy actually. You simply need to replace the normal authentication backend with your own: https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#writing-an-authentication-backend
The default backend looks like this: https://github.com/django/django/blob/master/django/contrib/auth/backends.py#L113-143
class ModelBackend(object):
...
def authenticate(self, remote_user):
"""
The username passed as ``remote_user`` is considered trusted. This
method simply returns the ``User`` object with the given username,
creating a new ``User`` object if ``create_unknown_user`` is ``True``.
Returns None if ``create_unknown_user`` is ``False`` and a ``User``
object with the given username is not found in the database.
"""
if not remote_user:
return
user = None
username = self.clean_username(remote_user)
UserModel = get_user_model()
# Note that this could be accomplished in one try-except clause, but
# instead we use get_or_create when creating unknown users since it has
# built-in safeguards for multiple threads.
if self.create_unknown_user:
user, created = UserModel._default_manager.get_or_create(**{
UserModel.USERNAME_FIELD: username
})
if created:
user = self.configure_user(user)
else:
try:
user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
pass
return user
What you need to do is inherit this class and add the remote host check to it.
Something along the lines of this:
class HostnameAuthenticationBackend(backends.ModelBackend):
def authenticate(self, username=None, password=None,
hostname=None, **kwargs):
user = backends.ModelBackend.authenticate(
username=username, password=password, **kwargs)
if user:
# check the hostname and groups here
if hostname_correct:
return user
The one tiny snag you'll hit is that by default the hostname won't be available, you'll have to pass it along from the login view to the authentication backend.
If you want to allow users from outside of the intranet to access the page, but not to be able to login (except of those with special permissions), then I suggest overriding the default login view and check whether the user that is trying to log in has appropriate permissions.
For a password change I am using auth_views.password_change and for the password reset auth_views.password_reset.
How can I be notified when a user successfully changes their password? I do not need to know old nor new password. Just that the event took place, and for which user.
Similarly, I would like to get notified when someone requested a password reset and also when they successfully completed the reset procedure.
Can i do the above with signals or some simple patching? Or do I need to write my own views to do this?
Create a decorator:
def notify_admins(func):
def wrapper(request, *args, **kwargs):
# send email to admins
return func(request, *args, **kwargs)
return wrapper
Then, just add wrap it around the appropriate views in your urls.py:
urlpatterns = patterns('',
...
(r'^password_change/done/$', notify_admins(auth_views.password_change_done)),
(r'^password_reset/done/$', notify_admins(auth_views.password_reset_done)),
(r'^reset/done/$', notify_admins(auth_views.password_reset_complete)),
...
)
Keep in mind that sending email directly from a view, or in this case a decorator, will tie up the request. Instead of sending the email there directly, it would be better to create a custom signal and a handler that will fire off a thread to actually send the email. Then, in the decorator, you simply send the signal.
You could write a custom password_change_form that you pass to password_change. This form would extend django's PasswordChangeForm overriding its save method to first notify you of the change and then call it's parent PasswordChangeForms save method.
Docs on password_change view:
https://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.views.password_change
Docs on ChangeForm:
https://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.forms.PasswordChangeForm
Code for PasswordChangeForm:
https://code.djangoproject.com/browser/django/trunk/django/contrib/auth/forms.py
Beginning in Django 1.9, you can define your define your own password validators. You could even simply re-define an existing one, if you like. When you do, add a method:
from django.contrib.auth.password_validation import MinimumLengthValidator
class MyPasswordValidator(MinimumLengthValidator):
def password_changed(self, password, user):
# put your password changed logic here
Be sure to include your new class in your settings as follows:
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'my_package.password_validators.MyPasswordValidator',
'OPTIONS': {
'min_length': 8,
}
},
...
]
Now, each time a password is changed by the user, your class MyPasswordValidator will be notified. In my experience, this is the best way to do this because:
When using signals to capture these events, you will also capture events where the system re-encoded an existing password due to a change in hashing parameters, in most cases, you would not want to capture these events and there's no obvious way to prevent it with signals.
You could simple add a function-call in the save() method of all your password-handling forms, but this becomes difficult when you want to do the same with the built in admin change password form and will not help you if password changes are made programmatically outside of a form.
I will caution you to be aware that the password parameter in password_changed() is in fact the user's raw password. Take care when handling this and absolutely never store this anywhere unencrypted/unhashed.
If you are already using the auth_views.password_change built in view, then it would be easy to notify yourself once they are redirected after a successful change:
https://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.views.password_change
password_change(request[, template_name, post_change_redirect, password_change_form])
If you set the post_change_redirect url to redirect to one of your own views, then you simply take whatever action you want in that view to send a notification (email, database updates, etc).
You could even, in your redirect view, just do your notification and then return password_change_done(request[, template_name])
You could also capture the signal and check if the password has changed. Just keep in mind that this code will run every time a user changes.
#receiver(pre_save, sender=User)
def record_password_change(sender, **kwargs):
user = kwargs.get('instance', None)
if user:
new_password = user.password
try:
old_password = User.objects.get(pk=user.pk).password
except User.DoesNotExist:
old_password = None
if new_password != old_password:
# do what you need here
I have a meta class for the Django User model that I use to add extra methods (overly simplified version):
# project.models.pie_lover.py
from django.contrib.auth.models import User
class PieLover(User):
class Meta:
app_label = "core"
proxy = True
def likes_pie(self):
return True
In my view, I wish to get the logged in PieLover and see if he likes pie (silly thing to do because a PieLover always loves pie, but in a real world situation this may not be the case). My problem lies in the way Django logs in the users, I use the built-in login(request) function for this and as a result the object stored in request.user is a User object, not a PieLover.
# project.views.like_pie.py
from ..models.pie_lover import PieLover
def index(request):
pie_lover = request.user
if pie_lover.likes_pie():
print "Try pie, try!"
else:
print "BLASPHEMER!"
If I try to do this Django tells me that the User object has no method likes_pie which is to be expected as request.user is not a PieLover instance.
As a quick workaround I just get the PieLover which has the same ID as the User but that means an extra DB hit.
How can I make Django use PieLover by default in the request?
I was thinking that instead of making another database query to get the proper PieLover object to create a new PieLover object and pass request.user to it at initialization but I don't know what the implications of this are.
After poking around I found, what seems to me, the easiest and non-hackish way to access the PieLover methods for a given User instance. I have added this to a custom middleware:
from models.pie_lover import PieLover
class PieLoverMiddleware(object):
def process_request(self, request):
request.pie_lover = PieLover(request.user)
How can I make Django use PieLover by default in the request?
You don't.
Read this before you do anything else: https://docs.djangoproject.com/en/1.3/topics/auth/#storing-additional-information-about-users
Your "extension" to user should be a separate model with all of your extension methods in that separate model.
You can then navigate from User (in the request) to your extension trivially using the get_profile() method already provided.
from django.contrib.auth import models as auth_models
def _my_func(self):
return True
auth_models.User.my_func = _my_func