How to redirect in a Generic View - django

I am using a generic view to render my blog post item:
class PostUpdateView(UpdateView, LoginRequiredMixin):
model = Post
# etc
I have a model method on the Post model that results in a boolean True or False:
#property
def can_edit(self):
return self.displays_set.count() == 0
If can_edit is False for the Post object, how can I refactor the view to redirect from my UpdateView to a different DetailView?

Override the dispatch method, and check obj.can_edit there. That way the object will be checked for get and post requests.
class PostUpdateView(LoginRequiredMixin, UpdateView):
model = Post
def dispatch(self, *args, **kwargs):
obj = self.get_object()
if not obj.can_edit:
return redirect('/readonly-view/')
return super().dispatch(*args, **kwargs)
With this solution, get_object() is called twice so there is a duplicate SQL query. However this is probably worth it to keep the code simple.

I would say that override dispatch method is best solution,
but if you want to avoid extra database hit, then you need to override get and post methods
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if not obj.can_edit:
return redirect('/readonly-view/')
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if not obj.can_edit:
return redirect('/readonly-view/')
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)

Related

class based templateview context is not rendering

I have a template view that is supposed to take in a user submitted search. I planning to use the get method to do a query in the get_context_data so that I can show some results on the HTML. Unfortunately, my get_context_data does not work while the get method and dispatch in my templateview works fine. The get_context_data does not run at all when the user submits the search.
class PollSearchView(TemplateView):
template_name = 'polls/polls_search.html'
def get(self, request, **kwargs):
self.request.session["search"] = request.GET.get("search")
return render(request, 'polls/polls_search.html')
def dispatch(self, *args, **kwargs):
dispatch = super(PollSearchView, self).dispatch(*args, **kwargs)
#exit if no search
if self.request.GET.get("search") == None:
pass
return redirect('/')
return dispatch
def get_context_data(self, **kwargs):
context = super(PollSearchView, self).get_context_data(**kwargs)
search = self.request.session.get("search")
context["test"] = search
return context
I have another class that is redirecting to the class above based a user input through a form.
class HomeView(TemplateView):
template_name = "home.html"
def get_context_data(self, *args, **kwargs):
context = super(HomeView, self).get_context_data(*args, **kwargs)
context["form"] = SearchForm()
return context
I think the form works completely fine, why the get_context_data does not take in any information baffles me, and I seek alternative ways to render the context based on my results from get. Any guidance on why this does not work and how to go about doing this will be great.
Thanks all
Instead of rendering the form using context["form"] = SearchForm(), include a formclass in the first templateview to render the form for the user.
class HomeView(TemplateView, FormView):
template_name = "home.html"
title = 'Your Dashboard'
form_class = SearchForm
on the second templateview, do a self.request.GET to collect the user input inside get_context_data method.
class PollSearchView(TemplateView):
template_name = 'polls/polls_search.html'
def get_context_data(self, **kwargs):
context = super(PollSearchView, self).get_context_data(**kwargs)
print self.request.GET
return context
This will enable the get_context_data to get the user input.

How to use two different models in CreateView in django

I have following models:
class BankAccount(models.Model):
owner = models.ForeignKey(User)
class MoneyTransfer(models.Model):
sender = models.ForeignKey(BankAccount)
and url:
url(r'^accounts/(?P<pk>\w+)/send_transfer$', SendTransfer.as_view(), name='SendTransfer')
that means "I want to send money from Account with id=pk"
This is my view:
class SendTransfer(View):
form_class = SendTransferForm
template_name = 'dashboard/send_transfer.html'
def get(self, request, *args, **kwargs):
instance = BankAccount.objects.get(id=self.kwargs['pk'])
if instance.is_legal():
if instance.organization.owners.all().filter(user__id=self.request.user.id).count() == 0:
return None
else:
if instance.citizen.user.id != self.request.user.id:
return None
return render(self.request, self.template_name, self.get_context_data())
def post(self, request, *args, **kwargs):
sender = BankAccount.objects.get(id=kwargs['pk'])
form = self.form_class(sender, self.request.user, request.POST)
if form.is_valid():
MoneyTransfer.objects.create(sender=sender,
receiver=BankAccount.objects.get(id=self.request.POST['receiver']),
total=float(self.request.POST['total']),
when=timezone.localtime(timezone.now()),
comment=self.request.POST['comment'])
return redirect('AccountDetail', kwargs['pk'])
data = self.get_context_data()
data['form'] = form
return render(request, self.template_name, data)
def get_context_data(self):
account = BankAccount.objects.get(id=self.kwargs['pk'])
return {'form': SendTransferForm(account, self.request.user),
'user': self.request.user,
'account': account}
I think there's a lot of redudant code for CBV. What can I do for shorting it?
UPD
my current code:
class SendTransfer(SingleObjectMixin, FormView):
model = BankAccount
form_class = SendTransferForm
template_name = 'dashboard/send_transfer.html'
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
return super(SendTransfer, self).dispatch(request, *args, **kwargs)
def get_object(self, queryset=None):
obj = super(SendTransfer, self).get_object(queryset)
if obj.is_legal():
if not obj.organization.owners.filter(user=self.request.user).exists():
raise Http404
else:
if obj.citizen.user != self.request.user:
raise Http404
return obj
def form_valid(self, form):
data = form.cleaned_data
MoneyTransfer.objects.create(sender=self.object,
receiver=data['receiver'], # ModelChoiceField in the form
total=data['total'], # FloatField in the form, etc.
when=timezone.localtime(timezone.now()),
comment=data['comment'])
return redirect('AccountDetail', self.object.pk)
last line of dispatch() method raises TypeError: init() takes at least 3 arguments (1 given)
CBVs are designed for code reuse. If you don't yet have another class that could benefit of code you posted, the actual amount of code is almost identical, be that a CBVs or a plain function.
But the more pythonic and Django-ish (from my biased POV) way would be to:
Inherit your class from the FormView instead of the View. That eases the form management a bit.
Add a SingleObjectMixin to get the object from url kwargs for free.
Move your object validation to the get_object() method. It's a good practive to raise 404 if your object doesn't validate.
Refactor out the get_context_data() as you already have all that data in your context (request, form and object)
Instead of relying on the self.request.POST, clean your data through the form.
class SendTransfer(SingleObjectMixin, FormView):
model = BankAccount
form_class = SendTransferForm
template_name = 'dashboard/send_transfer.html'
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
return super(SendTransfer).dispatch(request, *args, **kwargs)
def get_object(self, queryset=None):
obj = super(SendTransfer, self).get_object(queryset)
if obj.is_legal():
if not obj.organization.owners.filter(user=self.request.user).exists():
raise Http404
else:
if obj.citizen.user != self.request.user:
raise Http404
return obj
def form_valid(self, form):
data = form.cleaned_data
MoneyTransfer.objects.create(sender=self.object,
receiver=data['receiver'], # ModelChoiceField in the form
total=data['total'], # FloatField in the form, etc.
when=timezone.localtime(timezone.now()),
comment=data['comment'])
return redirect('AccountDetail', self.object.pk)
Some of your code has gone thanks to the CBV magic, some just has moved to another methods. Take a look, I'd welcome your comments.

FormView validation on fields not from form

I am writing a FormView which will add for example a comment on Person object.
I want to check if current user wrote a comment for this Person and if he did I'd like to raise Http404.
Question: What is the best place for this validation? I don't want to call validation in get_context_data and form_valid. Is dispatch method a good place for this logic?
Remember that form_valid will only be called when you POST the form so that won't work as GET requests will still render. You could therefore put it in the get method for the FormView which would prevent the view and template loading the initial form. The drawback is that people could technically still POST to that URL if they really wanted to.
As you mentioned, I would put it in the dispatch method. It is very early in the cycle of the FormView so you avoid unnecessary processing.
def dispatch(self, request, *args, **kwargs):
# I'm just guessing your Comment/Person models
user = self.request.user
try:
person = Person.objects.get(user=self.request.user)
except:
raise Http404("No user exists")
if Comment.objects.filter(content_object=person).exist():
raise Http404("Comment already exists")
return super(MyFormView, self).dispatch(request, *args, **kwargs)
EDIT This is a good link describing the various ways you can decorate your class based view to add permission and user checking
I wrote this mixin
class PermissionCheckMixin(object):
def __init__(self, perm=None, obj=None):
self.perm = perm
self.obj = obj
def dispatch(self, request, *args, **kwargs):
if request.user.is_anonymous():
if request.is_ajax():
return JSONResponseForbidden()
else:
return HttpResponseForbidden()
elif request.user.is_authenticated():
if self.perm:
if request.user.has_perm(self.perm, self.obj):
return super(PermissionCheckMixin, self).dispatch(request, *args, **kwargs)
else:
if request.is_ajax():
return JSONResponseForbidden()
else:
return HttpResponseForbidden()
else:
if request.is_ajax():
return JSONResponseForbidden()
else:
return HttpResponseForbidden()
And use it like this:
class TestFormView(PermissionCheckMixin, FormView):
...
You can easily adapt this mixin, somehow like this:
def __init__(self, pk):
self.person_pk = pk
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated():
if request.user.pk == self.person_pk:
return HttpResponseNotFound()

Replace get_context_data with get_object in Django 1.5

I have a CreateView in which I need to output data from an object. It works fine with this code below
class MyCreateView(CreateView):
model = ModelName
def dispatch(self, request, *args, **kwargs):
self.project = get_object_or_404(Project, slug=kwargs['project'])
return super(MyCreateView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, *args, **kwargs):
context_data = super(MyCreateView, self).get_context_data(*args, **kwargs)
context_data.update({'object': self.project})
return context_data
def form_valid(self, form):
obj = form.save(commit=False)
obj.project = self.project
obj.save()
return HttpResponseRedirect(self.get_success_url())
but I was wondering if it's possible to replace the dispatch() and get_context_data() with a get_object(). I've tried the code below, but it does not give me the project as object.
class MyCreateView(CreateView):
model = ModelName
def get_object(self, queryset=None):
return = Project.objects.filter(slug=self.kwargs['project'])
def form_valid(self, form):
obj = form.save(commit=False)
obj.project = self.object
obj.save()
return HttpResponseRedirect(self.get_success_url())
As their respective names imply, the get_object method returns a single object to be manipulated by the view, while get_context_data returns a dict to be displayed in the template. So no, you cannot replace one with the other, and your original code is perfectly fine.

Accessing request.user in class based generic view CreateView in order to set FK field in Django

So I have a model that includes:
class Place(models.Model):
....
created_by = models.ForeignKey(User)
My view is like so:
class PlaceFormView(CreateView):
form_class = PlaceForm
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(PlaceFormView, self).dispatch(*args, **kwargs)
Is there a way for me to access request.user and set created_by to that user? I've looked through the docs, but can't seem to find any hints toward this.
`
How about overriding form_valid which does the form saving? Save it yourself, do whatever you want to it, then do the redirect.
class PlaceFormView(CreateView):
form_class = PlaceForm
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(PlaceFormView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
obj = form.save(commit=False)
obj.created_by = self.request.user
obj.save()
return http.HttpResponseRedirect(self.get_success_url())
I know that this is old, but for other people with this problem:
There is an even simpler way - since saving a form multiple times will always use the same model instance, you can also do:
def form_valid(self, form):
obj = form.save(commit=False)
obj.created_by = self.request.user
return super(PlaceFormView, self).form_valid(form)
That way, you get all the benefits of the super call - it's trivial to see that you're really only adding those two lines of code, and you don't have to repeat yourself by replicating the redirect logic.
An alternate way to do this is to pass the user through overwriting the get_initial() method in the CreateView, and modify save method in the PlaceForm class to save the user:
class PlaceForm(forms.ModelForm):
...
...
...
def __init__(self, *args, **kwargs):
self.created_by = kwargs['initial']['created_by']
super(PlaceForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
obj = super(PlaceForm, self).save(False)
obj.created_by = self.created_by
commit and obj.save()
return obj
class PlaceFormView(CreateView):
...
...
form_class = PlaceForm
def get_initial(self):
self.initial.update({ 'created_by': self.request.user })
return self.initial
This way the saving logic is still encapsulated within the form class.