Convert django function generic view into class-based generic view - django

I've added a "user" field to all of my models. When a user creates an object, I want to attach their ID through a foreign key
user = models.ForeignKey(User)
Previously, I was using create_object and update_object. I believe I need to switch to class-based generic views in order to most easily insert the required user into the record. But I'm confused on how I institute some of the pre-calculations that were occuring in my previous function before create_object or update_object were called.
I have one function that handles all object editing, whether creating or updating:
#login_required
def edit_item(request, modelname, submodelname=None, slug=None, id=None):
# Get parameter "next" to determine where to send user after object is created or updated
# Define which template to use
# Determine whether user is converting an object to another type
# Determine which form_class to use based on modelname and whether user is converting or not
# Redirect user if slug and id are not both correct
# Abort if user hit cancel instead of submit
# If object exists (slug and id are defined):
# Update_object with form_class, object_id, template_name, post_save_redirect, and extra_context
# Else
# Create_object with form_class, template_name, post_save_redirect, and extra_context
Within a class-based generic view, how/where/when do I perform some of these calculations (logic around defining template or form_class based on criteria)? I'm confused because the docs seem to go straight to the definitions:
class ContactView(FormView):
template_name = 'contact.html'
form_class = ContactForm
success_url = '/thanks/'
Could I just throw the logic there?
class ContactView(FormView):
A = 1 + 2
if A == 3:
template_name = 'contact.html'
else:
template_name = 'contact_two.html'
form_class = ContactForm
success_url = '/thanks/'
And how would/should I alter my logic to divert into using CreateView or UpdateView vs what I've done here in using create_object or update_object in the same function depending on whether slug/id are defined?

Class-based generic views have methods that are used for the tasks you need. For example, for a form that creates an object you use CreateView, and to define which form is used override get_form_class() method.
I strongly suggest you to, instead of trying to convert your current logic exactly, take some time to learn the details of the class-based views, as many common features are already solved there in detail.

Related

django updateview pass request.id to field

I have a UpdateView class and I want to pass the user.id in event_agency of fields i.e the value of event_agency will be set to user.id and I will not have to take it as an input.
# view for the event update page
class EventUpdate(UpdateView):
model = Event
# the fields mentioned beindexlow become the entyr rows in the update form
fields = ['event_name', 'event_venue', 'event_type', 'event_agency']
Without using UpdateView it can be done by creating a form instance and passing request, but how to do it with UpdateView class?
I cannot for instance do
class EventUpdate(UpdateView, request):
Usually in that case, you do not add a field to that form. You can "patch" the instance in the form in the form_valid method [Django-doc], like:
from django.contrib.auth.mixins import LoginRequiredMixin
class EventUpdate(LoginRequiredMixin, UpdateView):
model = Event
# no event_agency
fields = ['event_name', 'event_venue', 'event_type']
def form_valid(self, form):
form.instance.event_agency = self.request.user.pk
super().form_valid(form)
That being said, it looks like event_agency should be a ForeignKey to your user model, in that case, you should set this like from.instance.event_agency = self.request.user.
You can add the LoginRequiredMixin mixin [Django-doc] to prevent user from accessing your view if these are not authenticated.

Readonly View for 10 Django Class Based Views

I have 10 Django Class Based Views and I want to display them read-only to the user.
I want the whole form to be read-only, not only some values. Submitting the form should be disabled on the client (HTML) and a second time on the server (POST not allowed).
Is there a MixIn or an other simple solution?
Here's a mixin that does two simple things:
Sets html attributes for all fields in form for disabled andreadonly.
Overrides the form_valid method of your CBV so that no model saving ever happens; instead, the template is rendered (just as if there was no submitted data). The user, this way, does not cause any action if they submitted the form.
Form field errors may appear next to disabled fields if you are rendering the full form in your template; solve this by either erasing the form's error dictionary or by rendering each field individually without errors.
from django.views.generic.edit import FormMixin, ModelFormMixin
class ReadOnlyModelFormMixin(ModelFormMixin):
def get_form(self, form_class=None):
form = super(ReadOnlyModelFormMixin, self).get_form()
for field in form.fields:
# Set html attributes as needed for all fields
form.fields[field].widget.attrs['readonly'] = 'readonly'
form.fields[field].widget.attrs['disabled'] = 'disabled'
return form
def form_valid(self, form):
"""
Called when form is submitted and form.is_valid()
"""
return self.form_invalid(form)
Extending this concept for a non-model FormView is pretty simple; inherit from class FormMixin instead. :)
To disallow POST requests in general for class-based views you could use the following mixin:
class DisallowPostMixin(object):
def post(self, request, *args, **kwargs):
return self.http_method_not_allowed(self, request, *args, **kwargs)
If you also want to disable certain form fields etc. you could add the get_form method from Ian Price's answer.
You can hack it through middleware. On request - check view name and request method (if post - redirect), on response - add input attrs in response.content. But mixin - best solution.

Using Django generic CreateView with field editable=False

I'm using a url scheme similar to /foo/1/bar/create where I'm creating a Bar object that has a foreign key to an existing Foo object with an id of 1. I've made the foreign key field back to Foo within Bar with editable=False.
The Django docs give some general advise on dealing with fields editable=False, or fields you've excluded from the form but that are required on the save(), but not any advice specific to using the generic CreateView.
My CreateBarView already gets the Foo id via the url and hence it is already in the view's self.kwargs. I'm trying to embrace the generic views to keep things DRY.
Is there a straight forward way of doing this in Django's Generic Views? Particularly without having to subclass the form?
Note: overriding get_initial() doesn't work, and I believe the docs support that.
edit: Django 1.9
Reading over the Django Generic Views, it looks like overriding get_form() via get_form_kwargs might be the most straightforward if I follow the general docs, e.g.,
class FormMixin(...): # from django.views.generic.edit
def get_form(self, form_class=None):
"""
Returns an instance of the form to be used in this view.
"""
if form_class is None:
form_class = self.get_form_class()
return form_class(**self.get_form_kwargs())
So,
class CreateBarView(...):
def get_form_kwargs(self):
kwargs = super(CreateBarView, self).get_form_kwargs()
kwargs['instance'] = Bar(foo_id=self.kwargs['foo_id'])
return kwargs

I have two views that are really similar. How can I "superclass" them?

I am trying to design a Django application that facilitates the lending and borrowing of musical instruments between musicians.
I have one template page that includes the form to post an instrument for lending or borrowing, and another template that includes the form for searching for available listings.
The difference between the two views other than the templates that are rendered (slightly different designs and buttons) is the name of the form they add to the context (i.e. PostForm() and SearchForm())
Basically, I have two views with almost completely the same code. This is bad practice usually.
Is there any way I can consolidate the two views into a "super-view" of sorts so that changes to one view are automatically made across both? I want to avoid duplicate code wherever possible.
This is very easy to do with Class Based Views (CBV).
For example, you may use django.views.generic.FormView, as follows in your views.py:
from django.views import generic
class ClassyFormView(generics.FormView): # Of course name the view whatever you like
# Note that I'm not setting any of the specific attributes here
# As I am planning on overriding this view for each form's specifics
# This is where you may override methods of the FormView
def get_context_data(self, *args, **kwargs):
""" This method allows you to edit the context passed to the template """
context = super(ClassyFormView, self).get_context_data(*args, **kwargs) # Get context from FormView
# Add variables to the context data
context['message'] = "Hello World!"
return context
def form_valid(self, form):
"""
This method is called once the form has been instantiated with
POST data and has been validated
"""
# Do whatever you want after the form is validated
print(form.cleaned_data['some_field'])
def form_invalid(self, form):
# Do something if the form is invalid
pass
You can then override your custom class, to maintain the specific things it does over FormView, but use the correct form class:
class SearchFormView(ClassyFormView):
form_class = SearchForm
class PostFormView(ClassyFormView):
form_class = PostForm
Note that you can (and probably will) also set fields such as prefix, success_url, and template_name, as well as override tons of other methods that may be useful!
Note that if your forms are ModelForms, then you will probably want to use one of the model specific generic form views, such as CreateView or UpdateView. Using these will allow you to access the object that the form is acting on. So, after setting the correct model in the class, your form_valid method may look like this:
def form_valid(self, form):
self.object = form.save(commit=False)
# Do stuff here to the object before you save it
# Such as altering or setting fields
self.object.some_field = "I'm a string"
self.object.save()
I can't explain everything about CBV, or even the class based form views here, so make sure to look at further documentation:
Django Class-Based-View Inspector is a really awesome site that not many people seem to know about! Usually easier than diving into the source code.
Relevant Django docs:
CBVs
Generic editing views
Decided to add a few more details that may be helpful.
The generic views specify defaults for their class attributes, but unless you're being really generic, it's usually a good idea to override them. Some explanation on the specific attributes:
prefix specifies a prefix for the form. Useful in cases where using multiple forms.
Defaults to None.
If you require more advanced logic, you can set the prefix by returning it in the get_prefix() method.
success_url specifies where the form will redirect to on success. For the model form views, this will default to the model's get_absolute_url()
Can be set by returning success url in get_success_url() method
template_name specifies the name of the template that the view will display upon a get request
Can be set by returning template name in get_template_name() method.
Configuring URLs for CBV is easy, too. Use the as_view() method as follows:
url(r'^some_url/', SearchFormView.as_view(), name="some-url")

How do I use CreateView with a ModelForm

I get an error in my class AuthorCreateForm when I submit my form.
NameError
self is not defined
How do I use a CreateForm?
I have created a class in my Author.py file
from django.views.generic import TemplateView, ListView, CreateView
from books.models import Author, Publisher, Book
from books.forms import AuthorForm
class AuthorCreateView(CreateView):
objAuthorForm = AuthorForm(self.request.POST)
if(objAuthorForm.save()):
success = "Form saved!"
else:
error = "There was an error!"
and I have a html template which submits to /Author/Create
and I have the following line in my urls.py
('^authors/create/$', Author.AuthorCreateView.as_view()),
I render the form at this URL
('^authors/new/$', TemplateView.as_view(template_name="author_new.html")),
I find the class based views confusing, does anyone have a good tutorial on how to use it for CRUD operations?
Thanks
What you have is a python error -- self is not defined. self is generally what refers to the class instance itself on class methods.
Anyways, I agree, it's brand spanking new and not as documented. I think looking at the source is absolutely key at this point.
To get comfortable with class based views, I'd start by subclassing django.views.generic.base.View, which implements only a few methods, namely attempting to call a function on the class based on the request method (post, get, head, - look at source).
For example, here's the first step to replace view functions with the new view classes:
class MyClassBasedView(View):
def get(self, request):
# behave exactly like old style views
# except this is called only on get request
return http.HttpResponse("Get")
def post(self, request):
return http.HttpResponse("Post")
(r'^foobar/$', MyClassBasedView.as_view())
Back to your specific question:
All TemplateView.as_view() does is render the template - CreateView is a combination of several other classes that handle ModelForms and template rendering (TemplateView).
So, for a very basic example, look to the docs for what class mixins are used by CreateView.
We see it implements TemplateResponseMixin, ModelFormMixin, and ProcessFormView, each containing a list of methods for those classes.
The most basic CreateView
At the most basic level, provide CreateView's ModelFormMixin with the model or custom ModelForm class as documented here.
Your CreateView class would look something like the following
class AuthorCreateView(CreateView):
form_class = AuthorForm
template_name = 'author_new.html'
success_url = 'success'
With those 3 core attributes set, call it in your URLs.
('^authors/create/$', Author.AuthorCreateView.as_view()),
Render the page and you'll see your ModelForm passed to the template as form, handling the form validation step (passing in request.POST / re-render if invalid), as well as calling form.save() and redirecting to the success_url.
Start overriding the class methods
To customize behavior, start overriding the methods documented for the mixins.
Remember that you simply need to return an HttpResponse from one of these methods just like any regular view function.
Example overriding form_invalid documented in ModelFormMixin:
class AuthorCreateView(CreateView):
form_class = AuthorForm
template_name = 'author_new.html'
success_url = 'success'
def form_invalid(self, form):
return http.HttpResponse("form is invalid.. this is just an HttpResponse object")
This per-method overriding starts becoming extremely useful as your forms grow more advanced and ultimately lets you build huge forms with a handful of lines of code, overriding only what is necessary.
Say you want to pass your form custom parameters such as the request object (very common if you need access to the user in the form): you merely need to override get_form_kwargs.
class MyFormView(FormView):
def get_form_kwargs(self):
# pass "user" keyword argument with the current user to your form
kwargs = super(MyFormView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
Class based views are a shining example of smart class usage. It gave me a great intro towards building my own mixins for views and python classes in general. It is saving countless hours.
Wow this got long. To think it started as a mere URL to the docs comment :)