Success_url from created object url in FormView - django

I have a form class view which creates an object (a product in a catalog) when the user fills the form. The object is created inside the form_valid method of the view. I want that the view redirects to the created object url (the product url) through the "success_url" atribute of the FormView.
The problem is that I do not know how to specify that url in the success_url method, since the object is still not created when the class itself is defined. I have tried with reverse_lazy, or the get_absolute_url() method of the object, but the same problem persists.
class ImageUpload(FormView):
[...]
success_url = reverse_lazy('images:product', kwargs={'id': product.id })
[...]
def form_valid(self, form):
[...]
self.product = Product.objects.create(
user=self.request.user, title=title)

Well at the class-level, there is no product, so you can not use product in the success_url.
What you can do is override the get_success_url, and thus determine the URL, like:
from django.urls import reverse
class ImageUpload(FormView):
def get_success_url(self):
return reverse('images:product', kwargs={'id': self.product.id })
def form_valid(self, form):
self.product = Product.objects.create(user=self.request.user, title=title)
return super(ImageUpload, self).form_valid(form)
In fact by default the get_success_url fetches the success_url attribute, and resolves it.

Related

Django: access form argument in CreateView to pass to get_success_url

I use CreateView to let a user create a Piece. The Piece will automatically be assigned an id. After the user created the Piece I would like to redirect using get_success_url to another CreateView to add Versions of the Piece.
First of all, I do not know where the id of the Piece comes from (since it is generated automatically; I imagine this is the row number of the Piece in the model). How can I access this id to pass it to get_success_url?
The get_context_data method in CreateView seems not to be able to get the Piece id.
views.py
class PieceCreate(LoginRequiredMixin, CreateView):
model = Piece
fields = ['title', 'summary', 'created', 'piece_type']
initial = {'created': datetime.date.today()}
def form_valid(self, form):
form.instance.creator = Creator.objects.get(user=self.request.user)
return super(PieceCreate, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(PieceCreate, self).get_context_data(**kwargs)
return context['id']
def get_success_url(self):
return reverse_lazy('pieceinstance-create', kwargs={'pk': self.get_context_data()})
urls.py
path('pieceinstance/create/<int:pk>', views.PieceInstanceCreate.as_view(), name='pieceinstance-create')
The instance that is constructed in the CreateView can be accessed with self.object [Django-doc], so you can obtain the primary key with self.object.pk:
class PieceCreate(LoginRequiredMixin, CreateView):
model = Piece
fields = ['title', 'summary', 'created', 'piece_type']
initial = {'created': datetime.date.today()}
def form_valid(self, form):
form.instance.creator = Creator.objects.get(user=self.request.user)
return super(PieceCreate, self).form_valid(form)
def get_success_url(self):
return reverse_lazy('pieceinstance-create', kwargs={'pk': self.object.pk})
I would advice not to override the get_context_data function that way: first of all, the contract specifies that it should return a dictionary, so not an id, and multiple functions make use of this, and expect the contract to be satisfied.

How do I use UpdateView?

I have two, presumably related, problems with UpdateView. First, it is not updating the user but creating a new user object. Second, I cannot restrict the fields displayed in the form.
Here is my views.py:
class RegistrationView(FormView):
form_class = RegistrationForm
template_name = "register.html"
success_url = "/accounts/profile/"
def form_valid(self, form):
if form.is_valid:
user = form.save()
user = authenticate(username=user.username, password=form.cleaned_data['password1'])
login(self.request, user)
return super(RegistrationView, self).form_valid(form) #I still have no idea what this is
class UserUpdate(UpdateView):
model = User
form_class = RegistrationForm
fields = ['username', 'first_name']
template_name = "update.html"
success_url = "/accounts/profile/"
and urls.py
url(r'^create/$', RegistrationView.as_view(), name="create-user"),
url(r'^profile/(?P<pk>\d+)/edit/$', UserUpdate.as_view(), name="user-update"),
How do I properly use UpdateView?
Problem 1.
The user is not being updated because you are using the same form
(RegistrationForm) to do your updates and to create new users.
Problem 2. Forms belong in a file of their own called forms.py.
My suggested refactoring:
#forms.py
#place(forms.py) this in the same directory as views.py
class UpdateForm(forms.ModelForm):
#form for updating users
#the field you want to use should already be defined in the model
#so no need to add them here again DRY
class Meta:
model = User
fields = ('field1', 'field2', 'field3',)
#views.py
#import your forms
from .forms import UpdateForm
#also import your CBVs
from django.views.generic import UpdateView
class UserUpdate(UpdateView):
context_object_name = 'variable_used_in `update.html`'
form_class = UpdateForm
template_name = 'update.html'
success_url = 'success_url'
#get object
def get_object(self, queryset=None):
return self.request.user
#override form_valid method
def form_valid(self, form):
#save cleaned post data
clean = form.cleaned_data
context = {}
self.object = context.save(clean)
return super(UserUpdate, self).form_valid(form)
slightly elegant urls.py
#urls.py
#i'm assuming login is required to perform edit function
#in that case, we don't need to pass the 'id' in the url.
#we can just get the user instance
url(
regex=r'^profile/edit$',
view= UserUpdate.as_view(),
name='user-update'
),
You left out a lot of info so not really sure what your setup is.My solution is based on the assumption that you have Django 1.5. You can learn more about handling forms with CBVs
first: user = form.save() saves in the db the form. since there's no pk in the form it creates a new one.
what you have to do is probably to check if a user with that username exists and if not create it (for this part check google).
second: to restrict field you have to specify them in the Meta class of the Form (which you didn't show here) check this https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#modelform.
If you are getting new objects in the database instead of updating existing ones then it is likely that you copied and pasted the template for new objects and forgot to change the form's action attribute. This should point to view that does the update either in the form of a hard-coded path or a URL tag ({% url '<name of URLconf>' object.id %).

CreateView is not returning an HttpResponse

I have the following view which extends the base CreateView:
class PeopleImportCsv(FailedLoginMessageMixin, CreateView):
model = CsvFile
form_class = CustomerCsvImportForm
template_name = 'people/customer_uploadcsv_form.html'
def get_success_url(self):
url = reverse('customer_process_csv', args=[self.object.id])
return url
def form_valid(self, form):
instance = form.save(commit=False)
instance.uploaded_by = self.request.user
super(PeopleImportCsv, self).form_valid(form)
I am using the get_success_url() method so I can get the id of the newly created object in the database. However, when I attempt to submit my form I get the following ValueError message:
The view people.views.PeopleImportCsv didn't return an HttpResponse object.
If I place an assert False immediately after assigning the url in get_success_url() then I can see that it has the correct url I'm expecting so what can I do to sort this out?
You need to have a return from the form_valid method (if you are using a ModelForm):
def form_valid(self, form):
instance = form.save(commit=False)
instance.uploaded_by = self.request.user
return super(PeopleImportCsv, self).form_valid(form)
You can see the methods signature in the Django source
P.S There is a very useful site for referencing Djangos many class based views here: http://ccbv.co.uk/

Django combine DetailView and FormView

I have a view where I need to display information about a certain model instance hence I use a DetailView. I also need that same view to handle a regular form (not a model form), both displaying the form on GET and validating it on POST. To do that, I am trying to use a FormView however the combination of both view clases does not work:
class FooView(FormView, DetailView):
# configs here
In GET (for simplicity of the question I will only show the issue with GET since POST has a different issue), it does not work because the form never gets added to the context. The reason has to do with method resolution order for that class:
>>> inspect.getmro(FooView)
(FooView,
django.views.generic.edit.FormView,
django.views.generic.detail.DetailView,
django.views.generic.detail.SingleObjectTemplateResponseMixin,
django.views.generic.base.TemplateResponseMixin,
django.views.generic.edit.BaseFormView,
django.views.generic.edit.FormMixin,
django.views.generic.detail.BaseDetailView,
django.views.generic.detail.SingleObjectMixin,
django.views.generic.base.ContextMixin,
django.views.generic.edit.ProcessFormView,
django.views.generic.base.View,
object)
Within the request, Django has to get the form and add it to the context. That happens in ProcessFormView.get:
def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates a blank version of the form.
"""
form_class = self.get_form_class()
form = self.get_form(form_class)
return self.render_to_response(self.get_context_data(form=form))
However the first class with the MRO which has get defined is BaseDetailView:
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
As you can see the BaseDetailView.get never calls super hence the ProcessFormView.get will never be called hence the the form will not be added to the context. This can be fixed by creating a mixin view where all these nuances for GET and POST can be taken care of however I do not feel it is a clean solution.
My question: is there any way of accomplishing what I want with Django's default CBV implementation without creating any mixins?
One solution would be to use mixins, as per limelights' comment above.
Another approach is to have two separate views, one a DetailView and the other a FormView. Then, in the template for the former, display the same form you're using in the latter, except that you won't leave the action attribute empty -- instead, set it to the url for the FormView. Something along the lines of this (please beware of any errors as I'm writing this without any testing):
In views.py:
class MyDetailView(DetailView):
model = MyModel
template_name = 'my_detail_view.html'
def get_context_data(self, **kwargs):
context = super(MyDetailView, self).get_context_data(**kwargs)
context['form'] = MyFormClass
return context
class MyFormView(FormView):
form_class = MyFormClass
success_url = 'go/here/if/all/works'
In my_detail_view.html:
<!-- some representation of the MyModel object -->
<form method="post" action="{% url "my_form_view_url" %}">
{{ form }}
</form>
In urls.py:
# ...
url('^my_model/(?P<pk>\d+)/$', MyDetailView.as_view(), name='my_detail_view_url'),
url('^my_form/$', require_POST(MyFormView.as_view()), name='my_form_view_url'),
# ...
Note that the require_POST decorator is optional, in the case that you don't want the MyFormView to be accessible by GET and want it only to be processed when the form is submitted.
Django also has a rather lengthy documentation about this problem.
https://docs.djangoproject.com/en/1.8/topics/class-based-views/mixins/#using-formmixin-with-detailview
They advise to make 2 different views, and have the detail view refer to the form view on post.
I'm currently seeing if this hack might work:
class MyDetailFormView(FormView, DetailView):
model = MyModel
form_class = MyFormClass
template_name = 'my_template.html'
def get_context_data(self, **kwargs):
context = super(MyDetailFormView, self).get_context_data(**kwargs)
context['form'] = self.get_form()
return context
def post(self, request, *args, **kwargs):
return FormView.post(self, request, *args, **kwargs)
By using FormMixin
views.py
from django.contrib.auth import get_user_model
from django.core.urlresolvers import (
reverse_lazy
)
from django.http import Http404
from django.shortcuts import (
render,
redirect
)
from django.views.generic import (
DetailView,
FormView,
)
from django.views.generic.edit import FormMixin
from .forms import SendRequestForm
User = get_user_model()
class ViewProfile(FormMixin, DetailView):
model = User
context_object_name = 'profile'
template_name = 'htmls/view_profile.html'
form_class = SendRequestForm
def get_success_url(self):
return reverse_lazy('view-profile', kwargs={'pk': self.object.pk})
def get_object(self):
try:
my_object = User.objects.get(id=self.kwargs.get('pk'))
return my_object
except self.model.DoesNotExist:
raise Http404("No MyModel matches the given query.")
def get_context_data(self, *args, **kwargs):
context = super(ViewProfile, self).get_context_data(*args, **kwargs)
profile = self.get_object()
# form
context['form'] = self.get_form()
context['profile'] = profile
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
#put logic here
return super(ViewProfile, self).form_valid(form)
def form_invalid(self, form):
#put logic here
return super(ViewProfile, self).form_invalid(form)
forms.py
from django import forms
class SendRequestForm(forms.Form):
request_type = forms.CharField()
def clean_request_type(self):
request_type = self.cleaned_data.get('request_type')
if 'something' not in request_type:
raise forms.ValidationError('Something must be in request_type field.')
return request_type
urls.py
urlpatterns = [
url(r'^view-profile/(?P<pk>\d+)', ViewProfile.as_view(), name='view-profile'),
]
template
username -{{object.username}}
id -{{object.id}}
<form action="{% url 'view-profile' object.id %}" method="POST">
{% csrf_token %}
{{form}}
<input type="submit" value="Send request">
</form>
In Django By Example from lightbird, they're using a library, MCBV, to mix generic views:
My guide’s tutorials will use a library of class-based views based on modified Django generic views; the library is called MCBV (M stands for modular) and the main difference compared to generic CBVs is that it’s possible to mix and match multiple generic views easily (e.g. ListView and CreateView, DetailView and UpdateView, etc.)
You can follow the explanation here: helper-functions
And use it to mix FormView and DetailView, or whatever
Code: MCBV
I performed my solution using ModelForms and something like this:
On method get_context_data of my DetailView I made:
form = CommentForm(
instance=Comment(
school=self.object, user=self.request.user.profile
)
)
context['form'] = form
And my FormView was like:
class SchoolComment(FormView):
form_class = CommentForm
def get_success_url(self):
return resolve_url('schools:school-profile', self.kwargs.get('pk'))
def form_valid(self, form):
form.save()
return super(SchoolComment, self).form_valid(form)
That's a old post but good for reference.
One elegant and reusable away is to use a custom inclusion tag for the form.
templatetags/my_forms_tag.py
from django import template
from ..forms import MyFormClass
register = template.Library()
#register.inclusion_tag('<app>\my_form.html')
def form_tag(*args, **kwargs):
my_form = MyFormClass()
return {'my_form ':my_form}
my_form.html
<form method="post" action="{% url "my_form_view_url" %}">
{{ form }}
</form>
The post will be taken by FormView werever view you put inclusion tag. And it can receive any context you pass in the inclusion. Dont forget to load my_form_tag and create the view for MyForm and include the entry in urls.py

Example of Django Class-Based DeleteView

Does anyone know of or can anyone please produce a simple example of Django's class-based generic DeleteView? I want to subclass DeleteView and ensure that the currently logged-in user has ownership of the object before it's deleted. Any help would be very much appreciated. Thank you in advance.
Here's a simple one:
from django.views.generic import DeleteView
from django.http import Http404
class MyDeleteView(DeleteView):
def get_object(self, queryset=None):
""" Hook to ensure object is owned by request.user. """
obj = super(MyDeleteView, self).get_object()
if not obj.owner == self.request.user:
raise Http404
return obj
Caveats:
The DeleteView won't delete on GET requests; this is your opportunity to provide a confirmation template (you can provide the name in the template_name class attribute) with a "Yes I'm sure" button which POSTs to this view
You may prefer an error message to a 404? In this case, override the delete method instead, check permissions after the get_object call and return a customised response.
Don't forget to provide a template which matches the (optionally customisable) success_url class attribute so that the user can confirm that the object has been deleted.
I've basically sub-classed some of the Generic Class-Based-Views to do exactly that. The main difference is I just filtered out the querysets. I can't vouch for whether this method is any better or worse but it made more sense to me.
Feel free to ignore the "MessageMixin" -- that's just there to easily present Messages using the Django Messaging Framework w/ a variable specified for each view. Here's the code I've written for our site:
Views
from django.views.generic import CreateView, UpdateView, \
DeleteView, ListView, DetailView
from myproject.core.views import MessageMixin
class RequestCreateView(MessageMixin, CreateView):
"""
Sub-class of the CreateView to automatically pass the Request to the Form.
"""
success_message = "Created Successfully"
def get_form_kwargs(self):
""" Add the Request object to the Form's Keyword Arguments. """
kwargs = super(RequestCreateView, self).get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
class RequestUpdateView(MessageMixin, UpdateView):
"""
Sub-class the UpdateView to pass the request to the form and limit the
queryset to the requesting user.
"""
success_message = "Updated Successfully"
def get_form_kwargs(self):
""" Add the Request object to the form's keyword arguments. """
kwargs = super(RequestUpdateView, self).get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
def get_queryset(self):
""" Limit a User to only modifying their own data. """
qs = super(RequestUpdateView, self).get_queryset()
return qs.filter(owner=self.request.user)
class RequestDeleteView(MessageMixin, DeleteView):
"""
Sub-class the DeleteView to restrict a User from deleting other
user's data.
"""
success_message = "Deleted Successfully"
def get_queryset(self):
qs = super(RequestDeleteView, self).get_queryset()
return qs.filter(owner=self.request.user)
Usage
Then, you can easily create your own views to use this type of functionality. For example, I am just creating them in my urls.py:
from myproject.utils.views import RequestDeleteView
#...
url(r'^delete-photo/(?P<pk>[\w]+)/$', RequestDeleteView.as_view(
model=Photo,
success_url='/site/media/photos',
template_name='site/media-photos-delete.html',
success_message='Your Photo has been deleted successfully.'
), name='fireflie-delete-photo-form'),
Forms
Important to note: I have overloaded those get_form_kwargs() methods to provide my Forms with an instance of 'request'. If you don't want the Request object passed to the Form, simply remove those overloaded methods. If you want to use them, follow this example:
from django.forms import ModelForm
class RequestModelForm(ModelForm):
"""
Sub-class the ModelForm to provide an instance of 'request'.
It also saves the object with the appropriate user.
"""
def __init__(self, request, *args, **kwargs):
""" Override init to grab the request object. """
self.request = request
super(RequestModelForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
m = super(RequestModelForm, self).save(commit=False)
m.owner = self.request.user
if commit:
m.save()
return m
This is a bit more than you asked -- but it helps to know how to do the same for Create and Update views as well. This same general methodology could also be applied to ListView & DetailView.
MessageMixin
Just in case anyone wants that MessageMixin I use.
class MessageMixin(object):
"""
Make it easy to display notification messages when using Class Based Views.
"""
def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message)
return super(MessageMixin, self).delete(request, *args, **kwargs)
def form_valid(self, form):
messages.success(self.request, self.success_message)
return super(MessageMixin, self).form_valid(form)
The simplest way to do that is to prefilter the queryset:
from django.views.generic import DeleteView
class PostDeleteView(DeleteView):
model = Post
success_url = reverse_lazy('blog:list_post')
def get_queryset(self):
owner = self.request.user
return self.model.objects.filter(owner=owner)
I would propose that the best (and simplest) way to do this would be to use the UserPassesTestMixin which gives you a cleaner separation of concerns.
Example:
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views.generic import DeleteView
class MyDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
def test_func(self):
""" Only let the user access this page if they own the object being deleted"""
return self.get_object().owner == self.request.user