I am using Django DeleteView to delete items in my database. I have use separate template to show the delete confirmation message, but when I press the yes button I get ProtectedError since customer table is linked with Accounts table. Hence I want to handle the ProtectedError and give user another message in the same template.
Here is the code I have used to perform the delete:
class Customer(DeleteView):
#Delete Customers
model = Customer
template_name = 'project_templates/delete_customer.html'
def get_success_url(self):
return reverse('inactive_customers')
It would be really great if someone can suggest me a method to handle this situation.
You should be able to catch the exception. When you look at the DeletionMixin:
https://github.com/django/django/blob/master/django/views/generic/edit.py#L256
You can override the post method and achieve something like:
def post(self, request, *args, **kwargs):
try:
return self.delete(request, *args, **kwargs)
except ProtectedError:
# render the template with your message in the context
# or you can use the messages framework to send the message
Hope this helps.
I had the same issue, and overriding the delete() method worked for me on Django 3.2. I used the messages framework to display the error message - that requires additional setup (see https://docs.djangoproject.com/en/dev/ref/contrib/messages/):
from django.db.models import ProtectedError
from django.contrib import messages
.
.
.
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
success_url = self.get_success_url()
try:
self.object.delete()
except ProtectedError:
messages.error(request, "custom error message")
finally:
return redirect(success_url)
Related
I'm making an user authentication app using Django authentication framework. I'm using provided views like LoginView and other views (from django.contrib.auth). Problem is when users (authenticated or anonymous) access urls:
path('password_reset/', views.PasswordResetView.as_view(), name='password_reset'),
path('password_reset/done/', views.PasswordResetDoneView.as_view(), name='password_reset_done'),
path('reset/done/', views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
they see the these pages. for example the page that confirms that a link to reset their password has been sent to their email. while the user is already signed in and they didn't ask for password reset and there is no email send to their email because they never asked to reset their password. they just manually accessed email has been sent confirmation page 127.0.0.1:8000/account/password_reset/done/.
How to prevent them from accessing these urls or show them appropriate message?
For such cases, you need to generate a unique request no and send it through query params e.g. 127.0.0.1:8000/account/password_reset/done?req_no=XXXXXXXX or just add a lookup field in URL e.g.
path('password_reset/done/<int:req_no> or <str:req_no>', views.PasswordResetDoneView.as_view(), name='password_reset_done'),
now you can get request no. in view, check if such request no exist if so send the templet for success message else redirect to home page or send 404 or anything you like,
to store the request no. you should create a new model and after sending the success templet, delete that req no from the database.
You can use the UserPassesTestMixin which can be imported from
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
then you can inherit the method you want to override in views,
for ex.
from django.contrib.auth.views.PasswordResetView
after inheriting the view you can perform the thing you want to do in test function
class ClassName(UserPassesTestMixin, PasswordResetView):
def test_func(self):
# do your thing
Thanks to answers i came up with this solution. (you can use value of was_sent to decide whatever). But as i'm new and have low experience there are major doubts about the practicality of this solution. comments are appreciated. (most of the code is copied from the source. added lines are marked with #)
class CustomPasswordResetDoneView(PasswordResetDoneView):
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
self.request.session['was_sent'] = False #
return self.render_to_response(context)
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
context['was_sent'] = self.request.session.get('was_sent', False) #
return context
class CustomPasswordResetView(PasswordResetView):
def get(self, request, *args, **kwargs):
self.request.session.get('was_sent', False) #
self.request.session['was_sent'] = False #
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
self.request.session['was_sent'] = True #
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
context['was_sent'] = self.request.session['was_sent'] #
return context
I have found this snippet in the django codebase:
# Add support for browsers which only accept GET and POST for now.
def post(self, request, *args, **kwargs):
return self.delete(request, *args, **kwargs)
What does this mean? Do browsers delete resources with GET / POST requests? Why? Can somebody provide a rationale / history / link for why this might be so?
It's for django.views.generic.edit.DeleteView. Your code is from DeletionMixin, and DeleteView inherit this mixin for delete object.
here's self.delete() code
def delete(self, request, *args, **kwargs):
"""
Call the delete() method on the fetched object and then redirect to the
success URL.
"""
self.object = self.get_object()
success_url = self.get_success_url()
self.object.delete()
return HttpResponseRedirect(success_url)
You can check about DeleteView in docs (here).
Basically, DeleteView receive both get and post for deleting object. That's why
First of all I want both views use exact same URL because I don't want to make my URLConf more complicated. I want separate views for GET and POST to make my code cleaner. The code is something like this:
def view2 (request):
# handle POST request, possibly a ajax one
return HTTPRESPONSE(json_data, mimetype="Application/JSON")
def view1 (request):
if method == POST:
view2(request)
# What should I return here???
else:
# handle GET
return render(request, template, context)
My question is about the # What should I return here??? line. If I don't put a return there, error occurs:
not returning http response
But I already return an HTTP response in view2. How can I make this work?
Another, probably a bit cleaner way would be using class-based views
from django.views.generic import TemplateView
class View1(TemplateView):
def get(self, request, *args, **kwargs):
"""handle get request here"""
def post(self, request, *args, **kwargs):
"""handle post request here"""
def head(self, request, *args, **kwargs):
"""handle head request here. Yes, you can handle any kind of requests, not just get and post"""
Of course you can add common methods, __init__ (which is useless unless you are sure what you are doing), apply login_required (see this SO question) and pretty much everything you can do with django views (e.g. apply middleware, permissions, etc.) and python classes (e.g. inheritance, metaclasses/decorators, etc.)
Also, there's a whole bunch of generic class based view coming with Django to address common situations like list page, details page, edit page, etc.
You need to return the results of view2:
def view1 (request):
if request.method == 'POST':
return view2(request)
else:
# handle GET
return render(request, template, context)
I'm using Django's class based DetailView generic view to look up an object for display. Under certain circumstances, rather than displaying the object, I wish to back out and issue a HTTP rediect instead. I can't see how I go about doing this. It's for when a user hits an object in my app, but without using the canonical URL. So, for example, on StackOverflow URLs take the form:
http://stackoverflow.com/<content_type>/<pk>/<seo_friendly_slug>
eg:
http://stackoverflow.com/questions/5661806/django-debug-toolbar-with-django-cms-and-django-1-3
You can actually type anything as the seo_friendly_slug part and it will redirect you to the correct canonical URL for the object looked up via the PK.
I wish to do the same in my DetailView. Retrieve the object, check that it's the canonical URL, and if not redirect to the item's get_absolute_url URL.
I can't return an HttpResponseRedirect in get_object, as it's expecting the looked up object. I can't seem to return it from get_context_data, as it's just expecting context data.
Maybe I just need to write a manual view, but I wondered if anyone knew if it was possible?
Thanks!
Ludo.
This isn't a natural fit for DetailView. To do this you need to override the get method of BaseDetailView, which looks like:
class BaseDetailView(SingleObjectMixin, View):
def get(self, request, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
So in your class you'd need to provide a new get method which did the URL check between fetching the object and setting up the context. Something like:
def get(self, request, **kwargs):
self.object = self.get_object()
if self.request.path != self.object.get_absolute_url():
return HttpResponseRedirect(self.object.get_absolute_url())
else:
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
As you end up overriding so much of the functionality it becomes questionable whether it's worth actually using a generic view for this, but youknow.
Developing on Rolo's answer and comments, I came up with the following generic view to serve this purpose:
from django import http
from django.views import generic
class CanonicalDetailView(generic.DetailView):
"""
A DetailView which redirects to the absolute_url, if necessary.
"""
def get_object(self, *args, **kwargs):
# Return any previously-cached object
if getattr(self, 'object', None):
return self.object
return super(CanonicalDetailView, self).get_object(*args, **kwargs)
def get(self, *args, **kwargs):
# Make sure to use the canonical URL
self.object = self.get_object()
obj_url = self.object.get_absolute_url()
if self.request.path != obj_url:
return http.HttpResponsePermanentRedirect(obj_url)
return super(CanonicalDetailView, self).get(*args, **kwargs);
This is used in the same manner as the normal DetailView, and should work for any model which implements get_absolute_url correctly.
I am just using the admin site in Django. I have 2 Django signals (pre_save and post_save). I would like to have the username of the current user. How would I do that? It does not seem I can send a request Or I did not understand it.
Thanks
Being reluctant to mess around with thread-local state, I decided to try a different approach. As far as I can tell, the post_save and pre_save signal handlers are called synchronously in the thread that calls save(). If we are in the normal request handling loop, then we can just walk up the stack to find the request object as a local variable somewhere. e.g.
from django.db.models.signals import pre_save
from django.dispatch import receiver
#receiver(pre_save)
def my_callback(sender, **kwargs):
import inspect
for frame_record in inspect.stack():
if frame_record[3]=='get_response':
request = frame_record[0].f_locals['request']
break
else:
request = None
...
If there's a current request, you can grab the user attribute from it.
Note: like it says in the inspect module docs,
This function relies on Python stack frame support in the interpreter, which isn’t
guaranteed to exist in all implementations of Python.
If you are using the admin site why not use a custom model admin
class MyModelAdmin( admin.ModelAdmin ):
def save_model( self, request, obj, form, change ):
#pre save stuff here
obj.save()
#post save stuff here
admin.site.register( MyModel, MyModelAdmin )
A signal is something that is fired every time the object is saved regardless of if it is being done by the admin or some process that isn't tied to a request and isn't really an appropriate place to be doing request based actions
You can use a middleware to store the current user: http://djangosnippets.org/snippets/2179/
Then you would be able to get the user with get_current_user()
We can solve this problem using middleware classes.
Create singleton class in where will be storing user variable.
class Singleton(type):
'''
Singleton pattern requires for GetUser class
'''
def __init__(cls, name, bases, dicts):
cls.instance = None
def __call__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super(Singleton, cls).__call__(*args, **kwargs)
return cls.instance
class NotLoggedInUserException(Exception):
'''
'''
def __init__(self, val='No users have been logged in'):
self.val = val
super(NotLoggedInUser, self).__init__()
def __str__(self):
return self.val
class LoggedInUser(object):
__metaclass__ = Singleton
user = None
def set_user(self, request):
if request.user.is_authenticated():
self.user = request.user
#property
def current_user(self):
'''
Return current user or raise Exception
'''
if self.user is None:
raise NotLoggedInUserException()
return self.user
#property
def have_user(self):
return not user is None
Create own middleware class that will be setting user for LoggedInUser instance,and insert out middleware after 'django.contrib.auth.middleware.AuthenticationMiddleware' in settings.py
from useranytimeaccess import LoggedInUser
class LoggedInUserMiddleware(object):
'''
Insert this middleware after django.contrib.auth.middleware.AuthenticationMiddleware
'''
def process_request(self, request):
'''
Returned None for continue request
'''
logged_in_user = LoggedInUser()
logged_in_user.set_user(request)
return None
In signals import LoggedInUser class and get current user
logged_in = LoggedInUser()
user = logged_in.user
In both signals, signal send three arguments,
Sender
Instance
Using
What you need is the Instant being modified...
def signal_catcher(sender, instance, **kwargs):
uname = instance.username
http://docs.djangoproject.com/en/dev/ref/signals/#pre-save