I have a Django custom form classed-based view. Under normal conditions I want it to be accessible to both authenticated and logged out or anonymous visitors. However, under certain conditions I'd like it to alert the user they need to login to submit the form (and redirect them). Example:
class CustomFormView(FormView):
...
def form_valid(self, form):
user = self.request.user
req_id = self.request.GET.get("req_id")
if req_id:
errors = False
if not user.is_authenticated():
messages.error(self.request, "You must be logged in to fill out this form. Please sign in, then visit this link again.")
errors = True
# redirect
try:
...
if errors:
ctx = self.get_context_data()
return self.render_to_response(ctx)
I'm aware of LoginRequiredMixin (docs) as well as a decorator #method_decorator(login_required) but I don't want to apply it at the view level or to the entire form_valid() function, only check login state when if req_id condition is met. Currently the view's not executing my if not user.is_authenticated(): so my attempt isn't correct. Is there a way to accomplish this within form_valid()? thanks
Related
A client requires the ability for managers to add users to a company (with a random one time password) where the user must change their password before accessing anything. I am developing the app in Django 2.2
I made a custom user, replacing username with an email address and I added a change_password bool flag to the user. My change_password form/function works properly, but redirecting does not.
urls.py
path('change-password/', views.change_password, name='change-password'),
views.py
class Login(LoginView):
def form_valid(self, form):
# form is valid (= correct password), now check if user requires to set own password
if form.get_user().change_password:
return HttpResponseRedirect(reverse('change-password'))
else:
auth_login(self.request, form.get_user())
return HttpResponseRedirect(self.get_success_url())
def change_password(request):
if request.method == 'POST':
form = PasswordChangeForm(data=request.POST, user=request.user)
if form.is_valid():
form.save()
request.user.change_password = False
request.user.save()
update_session_auth_hash(request, request.user)
return redirect(reverse('user_view'))
else:
return redirect(reverse('change-password'))
else:
form = PasswordChangeForm(user=request.user)
args = {'form': form}
return render(request, 'users/change_password.html', args)
The expected behavior is to redirect to change-password if the change_password flag is True, however, while the app does redirect to change-password, upon Submission the following error is thrown:
NotImplementedError: Django doesn't provide a DB representation for AnonymousUser.
If I add the decorator #login_required to my change_password function this error goes away, however, I am redirected back to the login page with the URL: users/login/?next=/users/change-password/
The problem is that in form_valid method you are calling form.get_user() which authenticates/gets the user and checks for the change_password correctly, but it does not log the user in, meaning that the user making the requests is still anonymous to the system. So while the user gets redirected they are not authenticated, which means that the request.user objects is of type AnonymousUser which does not live in the database hence the Django doesn't provide a DB representation for AnonymousUser error.
And when you use the #login_required decorator the user gets redirected to the login page because it is not a logged in user and the decorator requires the user to be logged in to see the view it is decorating.
The URL that you see users/login/?next=/users/change-password/ is basically how the login_required decorator works and it is doing two things:
1. redirect anonymous user to the login page (the users/login part of the URL)
2. once they have successfully logged in redirect them back from where they came from (?next=/users/change-password/)
My suggestion is that you pass the username of the user that tried to log in but has to change their password to the change_password view and have a form waiting for the user there that asks for the current password, new one and a confirmation of the new password. It is the simplest way to do what you want to do, but you will have to confirm that the users current password is correct again though.
Sorry for the confusing first answer, I didn't read the question right the first time, hopefully this makes more sense :)
So I have a my user profile view you can see as a logged in user. I wanted to add a second view so other logged in users can visit profile pages as well, but I'm not really sure I'm doing it the right way
urls.py
url(r'^accounts/profile/', main_views.uprofile, name='uprofile'), #the page you see as my profile
url(r'^profile/(?P<pk>\d+)/$', main_views.oprofile, name='oprofile'), # the page i use so other users can view the profile page
url(r'^accounts/update/(?P<pk>\d+)/', User_Profile_views.edit_user, name='edit_user'), #Custom update profile page
main_views.py
#login_required(login_url='/accounts/login/')
def uprofile (request):
context = locals()
template = 'profile.html'
return render (request, template, context)
def oprofile (request, pk):
user = User.objects.get(pk=pk)
context = locals()
template = 'profile.html'
return render (request, template, context)
From product point of view, you would want to keep same url for both uprofile and oprofile. One simple reason being, when I visit my profile and if I want to share it with someone else, I'd just copy-paste the url.
How to do that?
In your view, pass a flag that helps your template to render the right elements. For instance, if the user is same as the profile being visited, pass a flag, say editable, and use it to show edit buttons. And instead of two views, you can have single view.
Also, instead of id, people tend to remember their username/handle. So it's better to have username. However, make sure you have unique username for all users.
urls.py
url(r'^profile/(?P<username>[\w\-]+)/$', main_views.profile, name='profile'),
views.py
def profile (request, username):
# If no such user exists raise 404
try:
user = User.objects.get(username=username)
except:
raise Http404
# Flag that determines if we should show editable elements in template
editable = False
# Handling non authenticated user for obvious reasons
if request.user.is_authenticated() and request.user == user:
editable = True
context = locals()
template = 'profile.html'
return render (request, template, context)
Currently I have different Signup and Login Views both implemented as class based view, and are used at diffrent urls. Both use the same form which has two fields email and password.
These two views have different form_valid and thus different logic. So, signup view creates a user, sends verification mail etc. Login view only logs the user in.
The use case has changed to allow both signup and login at the same url using a single form.
I want to have the same view, handle both these conditions. So, when form is submitted, I will check if a user exists in db with the submitted email. If yes then use LoginView logic. If not then use SignupView logic. How can I handle both these conditions in a single view by reusing SignupView and LoginView.
In your case you can use one Form and one view.
This form check if email exist, if it exist, raise a ValidationsError
class LoginOrSignupForm(forms.ModelForm):
email = forms.EmailField()
password = forms.CharField(widget=forms.PasswordInput(render_value=False))
def clean_email(self):
existing = User.objects.filter(email__iexact=self.cleaned_data['email'], username__iexact=self.cleaned_data['email'])
if existing.exists():
raise forms.ValidationError(_("A user with this e-mail already exists."))
return self.cleaned_data['email']
And then in your view
if form.is_valid():
#signup
elif form['email'].errors and form['email'].errors[0] == _("A user with this e-mail already exists."):
#login
user = auth.authenticate(username=form['email'].value(), password=form['password'].value())
auth.login(request, user)
This solution work in function based view. So for your case you want to use CBV, we can just override form_invalid methods in your CBV:
def form_invalid(self, form):
if form['email'].errors and form['email'].errors[0] == _("A user with this e-mail already exists."):
user = auth.authenticate(username=form['email'].value(), password=form['password'].value())
auth.login(request, user)
return HttpRedirect('your page')
return super(YouViewName, self).form_invalid(form)
For the form_valid we don't need to override it, just keep in mind that if your form is valid, it should register a new user. In this case a django.views.generic.CreateView can help.
In my Django project, I have various users created by Django's built-in authentication system. Each user can create their own instances of the App model. I would like to restrict user access to objects such that users can only view the instances they have created. To do that I have created this view:
#login_required
def appDetail(request, app_id):
try:
app = App.objects.get(pk=app_id)
# Testing if the currently logged in user is
# the same as the user that created the 'app':
if request.user.id == app.user.user.id:
if request.method == 'POST':
form = AppForm(request.POST, instance=app)
if form.is_valid():
edited_app = form.save()
return HttpResponseRedirect('/thanks/')
else:
form = AppForm(instance=app)
# If 'app' does not belong to logged in user, redirect to 'accessdenied' page:
else:
return HttpResponseRedirect('/accessdenied/')
except LeaveApp.DoesNotExist:
raise Http404
return render(request, 'AppDetail.html', {'form':form})
It works, but I'm wondering if there's a more commonly accepted and/or safe way to do this?
This is called row-level permissions and it's a very common problem. See here for all the apps that solve it.
If that particular test is all you need to do, go for a custom solution like yours (though, since it's boilerplate, it's preferable to move it to a decorator). Otherwise, just use an existing app.
I would put the form submission in a different view and write a custom decorator, which you could also use for similar issues.
I would also return a 404 instead of access denied. You might not want to show users that you are protecting something.
There is a decorator called user_passes_test that restricts access to a view based on if the user passes a certain check
from django.contrib.auth.decorators import login_required, user_passes_test
#login_required
#user_passes_test(lambda user: user.username == app.user.user.id)
MyView(request):
...
You can also add in an optional argument for a url to redirect to in the event they fail the check.
Trying to do this from the admin page is also pretty easy, but takes a few extra steps.
Docs Here
I have this page :8000/edit/6/ that show a form to update an exciting model and i logged in as X, if i looged in as Y and try to open that page i can see it and update. So this is with no doubt a big bug and dangerous.
Here is my view code
class VideoUpdate(UpdateView):
form_class = VideoForm
model = Video
template_name = 'videos/video_update.html'
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(VideoUpdate, self).dispatch(*args, **kwargs)
def form_valid(self, form):
messages.info(self.request, _('Event is updated successfully'))
return super(VideoUpdate, self).form_valid(form)
Is there a way to check the model object id with the user id. A simple question from a newbie
Solution:
Actually there are two solutions that works for me in views.py, one is using the get_queryset method
def get_queryset(self):
base_qs = super(VideoUpdate, self).get_queryset()
return base_qs.filter(user=self.request.user.get_profile)
or using get_object method
def get_object(self):
video = get_object_or_404(Video, pk=self.kwargs['pk'])
if video.user != self.request.user.get_profile():
raise Http404
return video
Your question is not entirely clear to me but I think you want to restrict a view from registered but unauthorized users. Usually, this can be better achieved in your views instead of your models:
# views.py
def edit_form(request, parameter_indicating_user):
user = request.user
if #some logic:
# if user is equal to the user indicated by some parameter
# (id, username, etc) then allow that view to be rendered
else:
raise Http404 # or redirect the unauthorized user
I am interpreting your question as meaning the following.
When using class-based views - is there a way to control whether the specific instance of a form is editable by a given user ie. we have record #1 of a model. When you are logged in as user X you can edit record #1, but user Y is not allowed to edit record #1.
If this is what you are talking about you are going to require row/object level permissions, which I have found is best when using django-guardian.
Specifically, when using class-based views you can use the PermissionRequiredMixin, which can be found here: http://packages.python.org/django-guardian/api/guardian.mixins.html#permissionrequiredmixin
If you are looking for just controlling whether User X vs. User Y can edit any instance of that form. ie. User X can edit Form A values. Then you will just need to manage the permissions appropriately and then check if the user has that permission in the view.
JD