How to re-use a view to update objects in Django? - python

I am trying to get my head around Class based views (I'm new to Django). I currently have a project that uses function based views. My 'create' view renders a form and successfully submits to the database. However, I need an edit/update function so the obvious option is to re-use the 'create' function I made but I'm struggling to work it out and adhere to the DRY principle.
Is using Class based views the right way to go?
Do they handle the creation of all the 'CRUD' views?
I'm currently working my way through the GoDjano tutorials on Class based views but its still not sinking in.
Any help/pointers would be, as usual, much appreciated.

As you can see in the source code, a CreateView and an UpdateView are very similar. The only difference is that a CreateView sets self.object to None, forcing the creation of a new object, while UpdateView sets it to the updated object.
Creating a UpdateOrCreateView would be as simple as subclassing UpdateView and overriding the get_object method to return None, should a new object be created.
class UpdateOrCreateView(UpdateView):
def get_object(self, queryset=None):
# or any other condition
if not self.kwargs.get('pk', None):
return None
return super(UpdateOrCreateView, self).get_object(queryset)
The GoDjango tutorials don't seem to be out of date (CBVs have barely changed since their introduction), but they do seem to be missing some of the essential views in their tutorials.

CBV is in my opinion never the solution. A dry FBV is (assuming you have created an imported a form RecordForm and a model Record, imported get_object_or_404 and redirect):
#render_to('sometemplate.html')
def update(request, pk=None):
if pk:
record = get_object_or_404(Record, pk=pk)
else:
record = None
if request.POST:
form = RecordForm(request.POST)
if form.is_valid():
form.save()
return redirect('somepage')
else:
// ....
elif record:
form = RecordForm(instance=record)
else:
form = RecordForm()
return { 'form': form, 'record': record }
I also integrate the messages framework to for example add an error message when form.is_valid() is False.
I use a render_to decorator but that's not necessary (but then you have to return the view results differently).

Related

Django DeleteView only works on second attempt

Bit of a strange one and wondering if anyone else here has come across this.
I have a standard DeleteView with the GET showing a confirmation page containing a form that posts to the delete view.
Whenever I click confirm nothing happens - the post to the view occurs and it redirects as intended, however the object is not deleted.
If I then perform the action a second time the object is deleted.
class MetricDeleteView(DeleteView):
template_name = "dashboard/administration/metric/delete.html"
button_title = "Update metric"
form_class = MetricUpdateForm
model = dashboard_metric
#cached_property
def dashboard_score(self):
return self.get_object().score
def get_success_url(self):
return reverse_lazy("administration:dashboard:update_score", kwargs={
'dashboard': self.dashboard_score.dashboard.id,
'pk': self.dashboard_score.id
})
I can't for the life of me figure out why this is occurring across all some models on my site.
Hm, interesting. As the view is generic, have you looked at the model to check it doesn't override the delete functionality? Perhaps it doesn't delete on the first pass and sets a variable to 'deleted' instead, especially if you're working with syncing across platforms. WatermelonDB, for instance. Nathan. :D

in Django, how to implement CreateView's function with based-function views?

here is based-class views code:
# views.py
class ObjectCreate(CreateView):
model = ObjectModel
fields = "__all__"
its simple to create an object and save it use this class.
I wonder how?
what if I want to use based-function views to achieve it?
Using a function view you would need to implement everything, including creating a form for your model:
def create_object(request):
if request.method == 'GET':
form = ObjectForm()
if request.method == 'POST':
form = ObjectForm(request.POST)
if form.is_valid():
instance = form.save() # instance created
# now redirect user or render a success template
return redirect(...)
# if request method is GET or form is invalid return the form
return render(request, 'path/template_name.html', {'form': form})
If you want to learn how the CreateView works, look at its source code. Or for easier overview of the structure, look at this site which lists all the Django CBVs.
You'll find that CreateView inherits from 9 other classes, has about 20 attributes (of which model and fields) and 24 methods that you can override to customise its behaviour.

How do I prevent repeat code for handling forms in views.py using decorators?

Context
I'm handling a form in a python view. Basic stuff.
def index(request):
# Handle form.
if request.method == 'POST':
form = CustomForm(request.POST)
if form.is_valid():
# Do stuff
return HttpResponseRedirect('/thankyou/')
else:
form = CustomForm()
# Render.
context = RequestContext(request, {
'form':form,
})
return render_to_response('app/index.html', context)
This form is shown on multiple pages, and I've ended up having duplicates of the form-handling code in multiple functions in views.py, rendering different templates. (However, the template code for the form resides in the base template)
That's dumb, so I tried looking around for ways to prevent the repeat of code. I like the suggested use of python decorators in this Stackoverflow question. I also found an excellent explanation of python's decorators here.
Question
I'm having trouble with trying to write the decorator. I need to return a form after the first if statement, followed by executing another if statement. But in a python function, no code after a return function gets executed... Does this require something like a nested decorator..?
Suggestions? Non-decorator suggestions welcome.
This is not the answer to your main question but this info may be helpful to you or somebody.
The question with suggestion about decorators is pretty old. Started from 1.3 version django have class based views - i think this is what you are looking for. By subclassing views you can reduce duplication of code (code from django docs just for example):
# Base view
class MyFormView(View):
form_class = MyForm
initial = {'key': 'value'}
template_name = 'form_template.html'
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
# <process form cleaned data>
return HttpResponseRedirect('/success/')
return render(request, self.template_name, {'form': form})
Now you can create another views classes based on MyFormView view. Form processing code stays same, but you can change it of course:
class AnotherView(MyFormView):
form_class = AnotherForm
initial = {'key1': 'value1'}
template_name = 'form1_template.html'
# you dont need to redefine post here if code stays same,
# post from base class will be used

Django multi model best practice

Basically what I'm trying to achieve is a multi-model django app where different models take advantage of the same views. For example I've got the models 'Car' 'Make' 'Model' etc and I want to build a single view to perform the same task for each, such as add, delete and edit, so I don't have to create a seperate view for add car, ass make etc. I've built a ModelForm and Model object for each and would want to create a blank object when adding and a pre-populated form object when editing (through the form instance arg), with objects being determined via url parameters.
Where I'm stuck is that I'm not sure what the best way to so this is. At the moment I'm using a load of if statements to return the desired object or form based on parameters I'm giving it, which get's a bit tricky when different forms need specifying and whether they need an instance or not. Although this seems to be far from the most efficient way of achieving this.
Django seems to have functions to cover most repetitive tasks, is there some magic I'm missing here?
edit - Here's an example of what I'm doing with the arguments I'm passing into the url:
def edit_object(request, object, id):
if(object==car):
form = carForm(instance = Car.objects.get(pk=id)
return render(request, 'template.html', {'form':form})
What about using Class Based Views? Using CBVs is the best way in Django to make reusable code. For this example maybe it can be a little longer than function based views, but when the project grows up it makes the difference. Also remember "Explicit is better than implicit".
urls.py
# Edit
url(r'^car/edit/(?P<pk>\d+)/$', EditCar.as_view(), name='edit-car'),
url(r'^make/edit/(?P<pk>\d+)/$', EditMake.as_view(), name='edit-make'),
# Delete
url(r'^car/delete/(?P<pk>\d+)/$', DeleteCar.as_view(), name='delete-car'),
url(r'^make/delete/(?P<pk>\d+)/$', DeleteMake.as_view(), name='delete-make'),
views.py
class EditSomethingMixin(object):
"""Use Mixins to reuse common behavior"""
template_name = 'template-edit.html'
class EditCar(EditSomethingMixin, UpdateView):
model = Car
form_class = CarForm
class EditMake(EditSomethingMixin, UpdateView):
model = Make
form_class = MakeForm
class DeleteSomethingMixin(object):
"""Use Mixins to reuse common behavior"""
template_name = 'template-delete.html'
class DeleteCar(DeleteSomethingMixin, DeleteView):
model = Car
class DeleteMake(DeleteSomethingMixin, DeleteView):
model = Make
Just pass your class and form as args to the method then call them in the code.
def edit_object(request, model_cls, model_form, id):
form = model_form(instance = model_cls.objects.get(pk=id)
return render(request, 'template.html', {'form':form})
then just pass in the correct classes and forms in your view methods
def edit_car(request,id):
return edit_object(request, Car, CarForm, id)
each method knows what classes to pass, so you eliminate the if statements.
urls.py
url(r'^car/delete/(?<pk>\d+)/', edit, {'model': Car})
url(r'^make/delete/(?<pk>\d+)/', edit, {'model': Make})
views.py
def edit(request, id, model):
model.objects.get(id=id).delete()

Django, class-views: How can I save session data with a form's object?

I'm trying to store the username from the current request's session into a db object. How can I do this from within a class-based view? Is there a "clean" way to do this? What should I override/subclass?
I have a model that looks like this:
from django.contrib.auth.models import User
class Entry(django.db.models.Model):
...
author = models.ForeignKey(User, editable=False)
I also have a view based on the built-in generic view django.views.generic.CreateView. I'm also using the default ModelForm class that goes with my model, and the default {{ form }} in my template. AFAIK, the session and authentication apps/middleware are set up properly---as per default in new Django projects.
I found this post, which is getting at about the same thing, but from the wrong angle, and using function views instead.
My thinking so far was to override something in the form class and insert the username into the cleaned data. Is there a better way? Is there a right way?
Edit: Solution so far, non-working, with an IntegrityError: author_id cannot be null
from django.views.generic import CreateView
class Index(CreateView):
model = magicModel
template_name = "index.html"
success_url = "/magicWorked"
...
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.author = request.user
return super(Index, self).form_valid(form)
I wrote this based on what I found in django/views/generic/edit.py, which uses this implementation for class ModelFormMixin:
def form_valid(self, form):
self.object = form.save()
return super(ModelFormMixin, self).form_valid(form)
This is the method called by super().form_valid() above.
Edit: The problem with my solution was my understanding of Python's inheritance model. When the super-class calls form_valid(), it calls its own version, not my override; my code was never running at all.
The "correct" way to do this is to write your own view for object creation if the generic view doesn't suffice. Creation views are relatively short and there are numerous examples of how to save foreign keys.
Incidentally, Django's 1.3 docs say somewhere in there that modifications to the authentication model used by the admin app are being "discussed," such as adding per-instance permissions. (The current auth model supports only per model permissions.) The dev's might also add an implementation for what I'm trying to achieve. After all, user-associated data is used by nearly all websites.

Categories

Resources