Rejecting form post after is_valid - django

I'm attempting to overwrite the RegistrationView that is part of django registration redux.
https://github.com/macropin/django-registration/blob/master/registration/backends/default/views.py
Essentially, I want to implement logic that prevents the registration from happening if some condition is present. Checking for this condition requires access to the request.
I'm thinking that I might be able to subclass the register function within this Class based view.
def register(self, form):
#START OF MY CODE
result = test_for_condition(self.request)
if result == False:
messages.error(self.request, "Registration cannot be completed.", extra_tags='errortag')
return redirect('/access/register')
#END OF MY CODE
site = get_current_site(self.request)
if hasattr(form, 'save'):
new_user_instance = form.save(commit=False)
else:
new_user_instance = (UserModel().objects
.create_user(**form.cleaned_data))
new_user = self.registration_profile.objects.create_inactive_user(
new_user=new_user_instance,
site=site,
send_email=self.SEND_ACTIVATION_EMAIL,
request=self.request,
)
signals.user_registered.send(sender=self.__class__,
user=new_user,
request=self.request)
return new_user
What is the correct way to do this using class based views? I've worked almost exclusively with function based views so far.
Thanks!

As per my knowledge, it is recommended to subclass "AsbtractBaseUser" and "BaseUserManager" for user management in Django.
In CustomUserModel, you can define required fields added to the existing fields from BaseUserManager.
In CustomUserManager, you can override create_user, create_staff_user, create_superuser, and save_user. You can define a few conditions that should be checked before registering the user and also you can add permissions or groups to the user based on the role in the "save_user" method.
Create CustomRegistrationSerializer, that controls the fields that need to be displayed on the registration page, and validation of those fields can also be defined.
In POST method of CBV, you can check condition that requires "request" before serializing data. If condition is met, you can continue with serializing and saving otherwise, you can return appropriate response.
Django : https://docs.djangoproject.com/en/3.2/topics/auth/customizing/#extending-the-existing-user-model

Related

How can I restrict an authenticated user from posting on behalf of someone else?

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.

Create a User Profile or other Django Object automatically

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

Is there a way to remove hyperlinks from objects on django admin changelist that user does not have permission to change?

I am currently utilizing the has_change_permission hook in my custom django admin class to implement a simple form of row-level permissions and determine whether a non-superuser can edit a particular object, like so:
def has_change_permission(self, request, obj=None):
if obj is None or request.user.is_superuser or (obj and not obj.superuser_only): # (my model has a 'superuser_only' flag that gets set via fixtures, but its beyond the scope of this question)
return True
return False
This works well enough: all objects are shown to a user, but if they click on an object that they don't have permission to edit then they are taken to my 403 page, presumably because PermissionDenied is raised. However, giving them a hyperlink to a permission denied page doesn't seem ideal for this case; I would like to show the objects but not provide any hyperlinks to the edit page on the list page (in addition to raising PermissionDenied if they tried to manually use the URL for the object). Is there a straightforward hook for removing these hyperlinks without a horrendous hack? I'm an experienced django developer so if you can point me in the right direction (if any exists), I'll be able to implement the details or determine that its not worth it for now.
I was able to accomplish this in a fairly straightforward way by overwriting the default get_list_display_links function in the ModelAdmin class (in my admin.py file):
def get_list_display_links(self, request, list_display):
if request.user.is_superuser:
if self.list_display_links or not list_display:
return self.list_display_links
else:
# Use only the first item in list_display as link
return list(list_display)[:1]
else:
# Ensures that no hyperlinks appear in the change list for non-superusers
self.list_display_links = (None, )
return self.list_display_links
Note that my code maintains the hyperlink for superusers. You can easily just use the latter part of the function if you don't want to make this distinction.

Django Overwrite form data saved

I've posted about this problem before, but I still haven't found a solution so I'm hoping I'll have better luck this time.
I have a form that takes inputted data by the user. In another page, I am creating the identical form that the user has populated (pre-filled with that information) for editing purposes. Users will come to this page to EDIT the information they have already put in. My problem is that it isn't overwriting the instance.
def edit(request):
a = request.session.get('a', None)
if a is None:
raise Http404('a was not found')
if request.method == 'POST':
form = Name_Form(request.POST, instance=a)
if form.is_valid():
j = form.save( commit=False )
j.save()
else:
form = Name_Form( instance = a )
For this form, I'm using "unique_together" for some of the values. I'm also calling on `{{ form.non_field_errors }} in the template.
What is happening is when I make changes in the editing view, if the fields changes involves those defined in "unique_together" then an error is returned telling me that the instance already exists. Otherwise it saves a new instance. It isn't OVERWRITING.
Note that the reason i am using unique_together is that I want to prevent users from initially inputting the same form twice (before the editing stage, in the initial inputting view).
Any ideas?
EDIT: note that "a" refers to a session that includes a drop down box of all the available instances. This carried forward will indicate which instance the user wants to edit.
`
Why not do a database lookup of the model your trying to save and pull the fields from the form to the model then save the model?
Instead to store model a in session you should store it on database. Then edit it:
def edit(request, pk):
a = A.objects.get( pk = pk)
...
pk it the a identifier, you can send it to view via urls.py. I encourage to you to use POST/Redirect/GET pattern.
You can add a 'state' field on your model to control workflow (draft, valid)
You should not save objects in the session. If you really need to use a session - save a PK there and retrieve object right before giving it to Form. But the better solution is to send it in GET or POST parameters or included in url. Sessions are unreliable, data inside it can be destroyed between user's requests.
And you can retrieve value from a session in a more pythonic way:
try:
a = request.session['a']
except KeyError:
raise Http404('a was not found')

How to get notified when a user changes password or requests a password reset?

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