I have the following views which are working fine:
class FriendView(View):
message_success = None
message_error = None
def get_pending_received(self):
return models.FriendRequest.objects.filter(recipient_user_id=self.request.user.id)
def get_existing_friends(self):
return models.Friendship.objects.filter(source_user_id=self.request.user.id)
def get_pending_sent(self):
return models.FriendRequest.objects.filter(sender_user_id=self.request.user.id)
def get(self, request):
return render(request, 'template/friends.html',
{'pending_received_requests': self.get_pending_received(),
'existing_friends': self.get_existing_friends(),
'pending_sent_requests': self.get_pending_sent(),
'message_success': self.message_success,
'message_error': self.message_error})
class DeleteFriendView(FriendView):
def get(self, request, friendship_id):
try:
friendship = models.Friendship.objects.get(id=friendship_id)
except models.Friendship.DoesNotExist:
raise Http404
if not ((friendship.source_user_id == request.user.id) or (friendship.dest_user_id == request.user.id)):
return HttpResponseForbidden("Forbidden")
friendship.delete()
message_success = "Friend has been deleted"
return render(request, 'template/friends.html',
{'pending_received_requests': self.get_pending_received(),
'existing_friends': self.get_existing_friends(),
'pending_sent_requests': self.get_pending_sent(),
'message_success': 'Friend has been Deleted'})
My question is, is there a way to put the logic in the DeleteFriendView that is execute before get() without overriding get()? This would be cleaner and would reduce duplicate code but after reading the ClassView documentation I can't seem to figure out the best way to do this, since I can't access self outside of a method in the class view?
I would imagine something like this:
class DeleteFriendView(FriendView):
def before_get(self):
try:
friendship = models.Friendship.objects.get(id=friendship_id)
except models.Friendship.DoesNotExist:
raise Http404
if not ((friendship.source_user_id == request.user.id) or (friendship.dest_user_id == request.user.id)):
return HttpResponseForbidden("Forbidden")
friendship.delete()
self.message_success = "Friend has been deleted"
This way the get() method could be reused.
Thanks,
Mark
Why don't you want to override get()? You can put your logic there, then simply call the superclass method.
class DeleteFriendView(FriendView):
def get(self, request):
# delete-specific logic here
# now call FriendView get()
return super(DeleteFriendView, self).get(request)
Note by the way that it's very poor practice to put data-modifying actions in a GET request. They're far too easy to trigger by accident, or via malicious links. You should always put things like deletion in a POST request.
Related
In the Django admin, I have an Inline, and I would like to filter the list of rows by the parent object.
I can override get_queryset(request) in my Inline, but I don't have access to the parent object.
This snippet is from Django's options.py:
def get_formset_kwargs(self, request, obj, inline, prefix):
formset_params = {
"instance": obj,
"prefix": prefix,
"queryset": inline.get_queryset(request),
}
This would by immediately solved, if Django would provide obj as an argument to inline.get_queryset().
How to implement a get_queryset() of an InlineModelAdmin instance, so that it has access to obj?
class ChildInline(admin.TabularInline):
...
def get_queryset(self, request):
???? how to get the parent object?
These lines of code are the implementation of how inline admin instance are instantiate in Django
def get_inline_instances(self, request, obj=None):
inline_instances = []
for inline_class in self.get_inlines(request, obj):
inline = inline_class(self.model, self.admin_site)
if request:
if not (
inline.has_view_or_change_permission(request, obj)
or inline.has_add_permission(request, obj)
or inline.has_delete_permission(request, obj)
):
continue
if not inline.has_add_permission(request, obj):
inline.max_num = 0
inline_instances.append(inline)
return inline_instances
as you can see there is no obj passed to the inline_class so normally you can't access the parent instance.
Override this function in your parent model's admin class and use assigned attribute 'parent_obj' in your get_queryset method will make it work.
# parent admin class
def get_inline_instances(self, request, obj=None):
inline_instances = super().get_inline_instances(request, obj)
for inline_instance in inline_instances:
inline_instance.parent_obj = obj
return inline_instances
# inline admin class
def get_queryset(self, request):
self.parent_obj # you can now access your parent obj
I reffered to https://stackoverflow.com/a/41065115/17524955 and provided one change:
replace args to kwargs which contain an object_id
def get_parent_obj_from_request(self, request):
resolved = resolve(request.path_info)
if resolved.kwargs.get('object_id'):
return self.parent_model.objects.get(pk=resolved.kwargs['object_id'])
return None
def get_queryset(self, request):
qs = super().get_queryset(request)
parent_obj = self.get_parent_obj_from_request(request)
I found this dirty hack. In my case Event is the parent-model:
class ChildInline(admin.TabularInline):
...
def get_queryset(self, request):
# dirty hack to get the parent-object (event).
# Please fix it and tell me,
# if you know a better way to get it.
event_id = request.path_info.split('/')[-2]
event = Event.objects.get(id=event_id)
...
If i see those questions, i understand, how many people works with django and completely don't understand it.
First.
In the Django admin, I have an Inline,
and I would like to filter the list of rows by the parent object.
You don't need to do it in Inline object. Inline is only the helper, who organize creation of InlineFormSet.
And on the first lines of __init__ of your InlineFormSet - it get parent_object and made "filter the list of rows by the parent object" for inline.queryset. (django.forms.models.py, row 904 in Django 4.07)
It means, if you want to filter inline.queryset by parent_id before, it has not any reason to do it twice.
Second.
Please avoid loops. for example - get_inline_instances is good to set parent_object. But not in form of #pakawinz answer. You can do it better:
def get_inline_instances(self, request, obj=None):
return ((instance, setattr(instance, 'parent_object', obj))[0] for instance in super().get_inline_instances(request, obj))
And you can do it much better:
def get_inlines(self, request, obj):
"""Hook for specifying custom inlines."""
return (type(inline.__name__, (inline,) {'parent_object': obj}) for inline in super().get_inlines(request, obj))
Second example add parent_object like an attribute to inline_class. it give you possibility to get parent_object in classmethods too.
Third
Your question already has the best possibility to give an inline a parent_object to use it in queryset
def get_formset_kwargs(self, request, obj, inline, prefix):
inline.parent_obj = obj
return super().get_formset_kwargs(request, obj, inline, prefix)
Four
#NackiE23 tells you a better way to get parent_object.
Please don't use path.split(), this is not works for add_view in ModelAdmin. in your case:
def get_queryset(self, request):
... # your staff
resolved = resolve(request.path_info) # this is a better way to get it.
if resolved.kwargs.get('object_id'):
event = resolved.func.__self__.get_object(request, resolved.kwargs.get('object_id'), to_field=None) # please check if you don't need to_field
else:
event = resolved.func.__self__.model()
Please check, how work your solution not only for change_view, also for add_view too.
Last
I work only with django.admin.contrib more than 7 years. You can find my Talks about django.admin.contrib on PyCon RU 2021, PyCon DE 2022, DjangoCon EU 2022 and, later, on DjangoCon US 2022.
With my experience I think, probably, you do something unnecessary in your code. But i am not sure, therefore i write you some solutions above.
Let's say I have the following models:
class Post(model):
...
class BlogPost(Post):
...
class OtherPost(Post):
...
Assume my url schema to edit a post is something like,
/site/post/\d+/edit
In other words, I don't have separate url paths for editing OtherPosts vs. BlogPost.
When using UpdateView, I need to set the model -- but of course, the actual model is a subclass of Post.
class Update(generics.UpdateView):
model = Post
What is the Djangoey/DRY way to handle this?
At the moment, looking over the UpdateView code, it looks like I could leave Update.model undefined, and override get_queryset, which would need to return a query with the right submodel. I would also need to override get_form to return the right form.
I'll post my solution when I get it working, but am looking for possibly better (DRYer) integrations.
It looks like the following method is working, which seems fairly minimal.
class Update(generic.edit.UpdateView):
model = Post
def get_form_class(self):
try:
if self.object.blogpost:
return BlogPostForm
except Post.DoesNotExist:
pass
try:
if self.object.otherpost:
return OtherPostForm
except Post.DoesNotExist:
pass
def get_object(self, queryset=None):
object = super(Update, self).get_object(queryset)
try:
return object.blogpost
except Post.DoesNotExist:
pass
try:
return object.otherpost
except Post.DoesNotExist:
pass
Or, if using a polymorphic mixin like InheritanceManager, then something like this:
class Update(generic.edit.UpdateView):
model = Post
form_class = {
BlogPost: BlogPostForm,
OtherPost: OtherPostForm,
}
def get_form_class(self):
return self.form_class[self.object.__class__]
def get_queryset(self):
return self.model.objects.select_subclasses()
I'd like to write an except clause that redirects the user if there isn't something in a queryset. Any suggestions welcome. I'm a Python noob, which I get is the issue here.
Here is my current code:
def get_queryset(self):
try:
var = Model.objects.filter(user=self.request.user, done=False)
except:
pass
return var
I want to do something like this:
def get_queryset(self):
try:
var = Model.objects.filter(user=self.request.user, done=False)
except:
redirect('add_view')
return var
A try except block in the get_queryset method isn't really appropriate. Firstly, Model.objects.filter() won't raise an exception if the queryset is empty - it just returns an empty queryset. Secondly, the get_queryset method is meant to return a queryset, not an HttpResponse, so if you try to redirect inside that method, you'll run into problems.
I think you might find it easier to write a function based view. A first attempt might look like this:
from django.shortcuts import render
def my_view(request):
"""
Display all the objects belonging to the user
that are not done, or redirect if there are not any,
"""
objects = Model.objects.filter(user=self.request.user, done=False)
if not objects:
return HttpResponseRedirect("/empty-queryset-url/")
return render(request, 'myapp/template.html', {"objects": objects})
The advantage is that the flow of your function is pretty straight forward. This doesn't have as many features as the ListView generic class based view (it's missing pagination for example), but it is pretty clear to anyone reading your code what the view is doing.
If you really want to use the class based view, you have to dig into the CBV documentation for multiple object mixins and the source code, and find a suitable method to override.
In this case, you'll find that the ListView behaviour is quite different to what you want, because it never redirects. It displays an empty page by default, or a 404 page if you set allow_empty = False. I think you would have to override the get method to look something like this (untested).
class MyView(ListView):
def get_queryset(self):
return Model.objects.filter(user=self.request.user, done=False)
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
if len(self.object_list == 0):
return HttpResponseRedirect("/empty-queryset-url/")
context = self.get_context_data(object_list=self.object_list)
return self.render_to_response(context)
This is purely supplemental to #Alasdair's answer. It should really be a comment, but couldn't be formatted properly that way. Instead of actually redefining get on the ListView, you could override simply with:
class MyView(ListView):
allow_empty = False # Causes 404 to be raised if queryset is empty
def get(self, request, *args, **kwargs):
try:
return super(MyView, self).get(request, *args, **kwargs)
except Http404:
return HttpResponseRedirect("/empty-queryset-url/")
That way, you're not responsible for the entire implementation of get. If Django changes it in the future, you're still good to go.
again, apologies for what is probably a straightforward question!
Ok, so!
my problem is i have a saveModel function, where it saves a model. If the model is all good (is_valid), it will save the model and redirect to pageA
if the model is bad, or the request is a GET, then i'd like to redirect to pageB
all well and good, but i do this several times, how annoying! I don't want to cut and paste all the time, so i came up with this:
class SaveModel(View):
def as_view(self):
if request.method == "POST":
form = SaveModel.getPostForm(self.request)
if form.is_valid():
processedForm = SaveModel.processForm(self.request)
processedForm.save()
if (self.success_template):
return render_to_response(self.success_template)
else:
return render_to_response('pageA.html')
else:
form = SaveModel.getForm()
if (self.context_object_name):
contextName = context_object_name
else:
contextName = 'form'
if (self.template_name):
return render_to_response(template_name,{contextName:form})
else :
return render_to_response('pageB.html',{contextName:form})
def getForm(self):
return None
def getPostForm(self,request):
return None
def processForm(self,form,request):
return None
THEN, i define other classes to handle particular models, like, for example, so:
class StoryModelView(SaveModel):
def getForm(self,request):
return StoryForm()
def getPostForm(self,request):
return StoryForm(request.POST)
def processForm(self,form,request):
theStory = form.save(commit=False)
theStory.user = request.user
return theStory
and then, finally, in my urls.py i will refer to (as above) the model to use like so:
url(r'^addStory/$',
StoryModelView.as_view(
context_object_name='form',
template_name='accounts/addStory.html',
success_template='accounts/addStorySuccess.html'
)
),
This doesn't seem to work though - pycharm assures me that my references to self.context_object_name and so on are invalid. I'm v. new to python and django (which is why i thought i'd build a website with them! clever andrew!), so i am sure that i've missed a whole bunch of things (abstract methods and stuff... python does that, right?)
what do i need to do to get this all working? Is this how i should be doing things?
ANSWER BY ME!
Ok, so the comments everyone has written about the CreateView are probably correct. "Probably" because i never ended up using it, because i ended up sticking with my code instead.
In case anybody is, like me, new to python and django and wants to see how the whole thing works, here we are!
class SaveModel(View):
success_template = None
context_object_name = None
template_name = None
def post(self, request):
form = self.getPostForm(self.request)
if form.is_valid():
processedForm = self.processForm(form,self.request)
processedForm.save()
if self.success_template:
return render_to_response(self.success_template)
else:
return render_to_response('accounts/addStorySuccess.html')
else:
self.renderValidations(form)
def get(self,request):
form = self.getForm()
self.renderValidations(form)
def renderValidations(self,form):
if self.context_object_name:
contextName = self.context_object_name
else:
contextName = 'form'
if self.template_name:
return render_to_response(self.template_name,{contextName:form})
else :
return render_to_response('accounts/addStory.html',{contextName:form})
def getForm(self):
return None
def getPostForm(self,request):
return None
def processForm(self,form,request):
return None
and that is the main class, then i can override it like so:
class StoryModelView(SaveModel):
def getForm(self):
return StoryForm()
def getPostForm(self,request):
return StoryForm(request.POST)
def processForm(self,form,request):
theStory = form.save(commit=False)
theStory.user = request.user
return theStory
i tripped myself up with how "self" works in python a few times. it seems to be magically sent across with all method calls, but you need it as the first arg in the method declaration (but you never need to use it when calling/using the method)
i think there's only post or get for methods when overriding the View class. i don't have a good idea of the "process" of the call, or what the order is, dispatch was mentioned as something to override, but i suspect that is only where i need to change when/how to deal with differing request types (GET, POST, HEAD etc)
oh! the urls.py!
url(r'^addStory/$',
StoryModelView.as_view(
context_object_name='form',
template_name = 'accounts/addStory.html',
success_template= 'accounts/addStorySuccess.html'
)
),
i can just chuck whatever i want into that "as_view" call, and then, as long as those parameters are defined in the overriding class it's all good.
so yay! my classes all work and women want me. use my code, and this can happen to you too!*
*results atypical and fictional. your results may differ.
I've been experimenting with Django's Class Based Views and am trying to write a simple class based view that processes certain information in request so that the processed information can be used by the "handler" method.
I don't seem to have fully understood what the docs say and am unsure of whether this should be a Mixin, a generic view or something else. I'm thinking of making a class like this:
class MyNewGenericView(View):
redirect_on_error = 'home'
error_message = 'There was an error doing XYZ'
def dispatch(self, request, *args, **kwargs):
try:
self.process_information(request)
# self.process_information2(request)
# self.process_information3(request)
# etc...
except ValueError:
messages.error(request, self.error_message)
return redirect(self.redirect_on_error)
return super(MyNewGenericView, self).dispatch(request, *args, **kwargs)
def process_information(self, request):
# Use get/post information and process it using
# different models, APIs, etc.
self.useful_information1 = 'abc'
self.useful_information2 = 'xyz'
def get_extra_info(self):
# Get some extra information on something
return {'foo':'bar'}
This will allow someone to write a view like:
class MyViewDoesRealWork(MyNewGenericView):
def get(self, request, some_info):
return render(request, 'some_template.html',
{'info':self.useful_information1})
def post(self, request, some_info):
# Store some information, maybe using get_extra_info
return render(request, 'some_template.html',
{'info':self.useful_information1})
Is the above code the right way to go? Is there any simpler/better way of doing this? Will this prevent the above functionalities from being used in another generic view (e.g. a built-in generic view)?
Have a look at this. great example code. http://www.stereoplex.com/blog/get-and-post-handling-in-django-views
It seems I just asked a stupid question.
This can easily be achieved by making a class that processes that information:
class ProcessFooInformation(object):
def __init__(self, request):
self.request = request
#property
def bar(self):
baz = self.request.GET.get('baz', '')
# do something cool to baz and store it in foobar
return foobar
# etc...
Then using old style function views or new class-based views:
def my_view(request):
foo = ProcessFooInformation(request)
# use foo in whatever way and return a response
return render(request, 'foobar.html', {'foo':foo})
I also made this more efficient by using lazy evaluation of properties.
I adapted ideas from the lazy property evaluation recipe and the comments to write a wrapper:
def lazy_prop(func):
def wrap(self, *args, **kwargs):
if not func.__name__ in self.__dict__:
self.__dict__[func.__name__] = func(self, *args, **kwargs)
return self.__dict__[func.__name__]
return property(wrap)
This evaluates the value of the wrapped method only once per instance and uses a stored value on subsequent calls. This is useful if the property evaluates slowly.