I've got a django app with multiple models, and mostly crud functionalities. Most of my views are very similar, ie. for example, all ModelnameCreate functions have very similar structure.
So I decided to make helper function: (here is an example of helper for creating new objects)
def createFuncHelper(request, title_label , formClass , form_render , success_render , renderToViewWithId , success_message):
form = formClass()
if request.method=='POST':
form = formClass(request.POST, request.FILES)
if form.is_valid():
form.instance.updatedBy = request.user
newObject = form.save()
messages.success(request, success_message)
if(renderToViewWithId):
return redirect(success_render, id=newObject.pk)
else:
return redirect(success_render)
return render(request, form_render, {'form':form, 'title_label':title_label})
Which is called from my each view (for each model) eg.:
def BookingCreate(request):
return createFuncHelper(request, etc...
It works, but the question for my more experience collegues is - if it is the right approach, or are there any serious risks I'm not aware?
Related
I'm building an app where the user enters data and then gets redirected to a page that shows results based on their input with some simple equations. However, every time I refresh the results page, a new model instance is saved on the database.
Is there another (more efficient and effective) way of passing the data from this view to another view where I have access to the instance of that model submitted through the form view? What's the Django way of passing form data to a view?
The only limitation is I don't want user authentication so using self.request.user is not an option unless it can be implemented in a way that doesn't require users to sign up and sign in.
I'm still somewhat new to Django so any pointers to obvious solutions that I'm overlooking would be greatly appreciated.
This is the view that processes the model form:
def createcalculation(request):
form = CalcForm()
if request.method == 'POST':
form = CalcForm(request.POST)
if form.is_valid():
item = form.save()
m_data = get_object_or_404(Calculate, id=item.id)
context = {'c_data': form.cleaned_data, 'm_data': m_data}
return render(request, 'calc/res_ca.html', context)
context = {'c_form': form}
return render(request, 'calc/calc.html', context)
It is advisable to always do a redirect after a successful POST. Your code should look something like this:
from django.shortcuts import get_object_or_404, render, redirect
from django.urls import reverse
...
def createcalculation(request):
form = CalcForm()
if request.method == 'POST':
form = CalcForm(request.POST)
if form.is_valid():
item = form.save()
m_data = get_object_or_404(Calculate, id=item.id)
context = {
'c_data': form.cleaned_data,
'm_data': m_data
}
return redirect(reverse('app_name:view_name', kwargs=context))
context = {'c_form': form}
return render(request, 'calc/calc.html', context)
You can pass the newly created item object in the context as well. Also, you should change app_name and view_name text to match your situation.
So I'm trying to make a calculator of sorts where the user enters some data and then they are presented with a view that gives them the results they need (printer-friendly format so they can print it).
What I've done so far:
Created a model and a form which they work as intended.
**class CalcModel**(models.Model):
income = models.DecimalField...
civil_status = models.CharField...
have_children = models.CharField..
**class CalcForm**(ModelForm):
class Meta:
**model = Calculate**
fields = ['income', 'civil...]
The view that processes the form and redirects to another view if submitted data is valid:
data_d = {}
def createcalculation(request):
form = CalcForm()
if request.method == 'POST':
form = CalcForm(request.POST)
if form.is_valid():
**data_d['cleaned_data'] = form.cleaned_data**
form.save()
return redirect('res-view')
context = {'c_form': form}
return render(request, 'calc/calc.html', context)
I think there should be a way to pass the model instance data to the view where the user is redirected but I can't figure it out. So, help is highly appreciated. Right now, I'm 'manually' passing a dictionary containing the data from the form but it doesn't work:
def res_ca(request):
context = data_d
return render(request, 'calc/res_ca.html', context)
I can't seem to figure out how to pass the data for the current session to the res_ca view.
The urls if that helps:
path('calc', createcalculation, name='calculate'),
path('res/', res_ca, name='res-view'),
As suggested by #vinkomlacic, I found a way to include the id of the model instance by switching to the render method instead of redirect and it worked like a charm.
if form.is_valid():
messages.success(request, f'Successful!')
item = form.save()
m_data = Calculate.objects.get(id=item.id)
context = {'c_data': form.cleaned_data, 'm_data': m_data}
return render(request, 'calc/res_ca.html', context)
This way, the form is saved, I can pass that instance of the model to the next view and it also allows me to add additional context to the template directly from model methods.
I am trying to use same view for creating form and updating any object.
My code is as below, I tried in many ways nothing is working, since I am excluding the shof from form and adding it after form.is_valid() it makes lot of confusion. If I update it creates new object. I have two urls one without ql (create new) and one with ql (update existing), I have a class vdview which provides v.shof which needs to applied in the f.shop in form. please help fix this,
#csrf_protect
#login_required
def addmenu(request, qs, ql=None):
v = vdview(request, qs)
ctgobj = get_object_or_404(v.shopcategs, pk=ql) if ql else None # ctgobj = ShopCtg(shop=v.shof)
if ql:
form = ShopCtgForm(instance=ctgobj) # Tried ShopCtgForm(instance=ctgobj, data=request.POST)
else:
form = ShopCtgForm(data= request.POST)
if request.method == 'POST':
if form.is_valid():
f=form.save(commit=False)
f.shop = v.shof
f.save()
#form.save_m2m()
return redirect('vendor-shop', qs) #thing='%s added' %f.name)
else:
pass
#else:
# form = ShopCtgForm()
return render(request,'vendorshop.html', {'shop':v.shof, 'shopcategs':v.shopcategs, 'form': form,
'heading':'Create New Category', 'createcateg': 'createcateg', 'pkaddmenupk':'y' } )
Use try blocks to handle both scenarios. The simplified example below will look for a given model instance pk and if it doesn't find it, will assume you want to create it. try will prevent django from throwing an error if the model instance doesn't exist. Rather, it will just return the empty model form.
It does this first to render the correct form in the template (the first try block) then again in the second try block after request.method == 'POST': to submit new data or update existing data.
Views.py
from .models import Books
from .forms import BookForm
def create_and_update_book_view(request, pk):
books = Books.objects.get(id=pk)
try: # get pre-populated form with model instance data (for update)
form = BookForm(instance=books.id)
except: # If it doesn't exist, show an empty form (for create)
form = BookForm(request.POST or None)
if request.method == 'POST':
try: # Do the same as above
form = BookForm(instance=books.id)
except: # Same as above
form = BookForm(request.POST or None)
if form.is_valid():
form.save()
return render(request, "create_and_update_book_page.html", {'form':form})
I am storing some form values, which is a list filter, from a post method in the request.session in order to use it in another view function to render the filtered results. The problem is any user that I log in keep the results stored, if they access the results page directly they will see other users filter results.
I use pagination (digg without AJAX), I am using django-el-pagination.
the views.py
def search(request):
if request.method == 'POST':
form = ComprarBuscaForm(request.POST)
if form.is_valid():
cidade = form.cleaned_data['cidade']
request.session['cidade'] = form.cleaned_data['cidade']
request.session['quartos'] = form.cleaned_data['quartos']
request.session['tipo_imovel'] = form.cleaned_data['tipo_imovel']
request.session['preco_minimo'] = form.cleaned_data['preco_minimo']
request.session['preco_maximo'] = form.cleaned_data['preco_maximo']
request.session['area_minima'] = form.cleaned_data['area_minima']
request.session['area_maxima'] = form.cleaned_data['area_maxima']
return HttpResponseRedirect(reverse('imoveis:resultado_busca'))
else:
form = ComprarBuscaForm()
return render (request, 'imoveis/busca_comprar.html', {'form':form})
def search_result(request):
anuncios = Anuncio.objects.filter(quartos=request.session['quartos'],
cidade=request.session['cidade'],
tipo_imovel=request.session['tipo_imovel'],
preco_venda__gte=request.session['preco_minimo'],
preco_venda__lte=request.session['preco_maximo'],
area_construida__gte=request.session['area_minima'],
area_construida__lte=request.session['area_maxima'],
tipo_anuncio='Venda')
return render(request, 'imoveis/resultado_busca_comprar.html', {'anuncios': anuncios})
Everything is working fine although the fact I mentioned before. I am wondering if what I am doing is the right approach for this kind of situation.
Is it really necessary to use two views for this? I would filter and render in the same form view if I were you.
def search(request):
if request.method == 'POST':
form = ComprarBuscaForm(request.POST)
if form.is_valid():
anuncios = Anuncio.objects.filter(quartos=request.session['quartos'],
cidade=request.session['cidade'],
tipo_imovel=form.cleaned_data['tipo_imovel'],
preco_venda__gte=form.cleaned_data['preco_minimo'],
preco_venda__lte=form.cleaned_data['preco_maximo'],
area_construida__gte=form.cleaned_data['area_minima'],
area_construida__lte=form.cleaned_data['area_maxima'],
tipo_anuncio='Venda')
return render(request, 'imoveis/resultado_busca_comprar.html', {'anuncios': anuncios})
else:
form = ComprarBuscaForm()
return render (request, 'imoveis/busca_comprar.html', {'form':form})
This question may seem obvious, but I have been thrown for a loop the past few days. The vast majority of tutorials and documentation I find on django-forms shows them as their own solitary views.
Take this example from django 1.6 official docs as a typical example, 'Using a form in a view':
from django.shortcuts import render
from django.http import HttpResponseRedirect
def contact(request):
if request.method == 'POST': # If the form has been submitted...
form = ContactForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/thanks/') # Redirect after POST
else:
form = ContactForm() # An unbound form
return render(request, 'contact.html', {
'form': form,
})
The problem I have is that I almost never dedicate a whole page or view just to one form. I can't really {% include %} the form view due to context variables. I also can't {% extend %} it without bringing in way to many items or reworking all of my templates and views.
Is it a good practice to combine form logic along with other references and variables in the same view? I would just like to confirm that yes this is normal and acceptable or that no I am doing things unacceptably. Off-hand there looks like some non DRY boilerplate if I do this in each view that needs a form.
There also appears to be some debate in the blogs & tutorials for form logic. Its hard to tell whether modifying a CBV or using some good ol' FBV is preferred. I just don't want to get any bad habits while I am still new and learning.
This is my currently "working" code for my view:
def home_page(request):
all_sliders = Slider.objects.all()
all_marketing = Marketing.objects.all()
all_features = Feature.objects.all()
skill_set = Skills.objects.all()
#clunky way of adding the form...yes? no?
errors = []
if request.method == 'POST':
form = ContactForm(request.POST)
if not request.POST.get('subject', ''):
errors.append('Enter a subject.')
if not request.POST.get('message', ''):
errors.append('Enter a message.')
if request.POST.get('email') and '#' not in request.POST['email']:
errors.append('Enter a valid e-mail address.')
if not errors:
send_mail(
request.POST['subject'],
request.POST['message'],
request.POST.get('email', 'noreply#mysite.com'),
# email address where message is sent.
['email#mysite.com'],
)
return HttpResponseRedirect('frontpage/thanks/')
else:
form = ContactForm()
context = {'sliders': all_sliders, 'marketing': all_marketing,
'feature': all_features, 'skills': skill_set,
#this is tied to my form logic
'form': form, 'errors': errors,
}
return render(request, 'frontpage/home.html', context)
In closing, I don't want to use an add-on library. I am learning django and want to learn it well before using libraries for basic things like forms.
There is nothing wrong with providing extra context/whatnot to a view with a form. In fact, it's pretty easy to do with CBVs. The docs for the Generic FormView provide a good example of a contact form, and you can extend it to add your extra context by overriding the get_context_data. What you should not do is form validation in the view. Leave that to your form class in forms.py. Here's an example pieced together from the docs:
# forms.py
class ContactForm(forms.Form):
subject = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
email = forms.CharField()
# Whatever fields you had.
def clean(self):
"""This is where you should be checking for required
fields and making sure the submitted data is safe."""
pass
def send_email(self):
# send email using the self.cleaned_data dictionary
send_mail(
self.cleaned_data['subject'],
self.cleaned_data['message'],
self.cleaned_data.get('email', 'noreply#mysite.com'),
['email#mysite.com']
)
# views.py
class HomePageFormView(FormView):
template_name = 'frontpage/home.html'
form_class = ContactForm
success_url = 'frontpage/thanks/'
def get_context_data(self, **kwargs):
context = super(HomePageFormView, self).get_context_data(**kwargs)
context['sliders'] = Slider.objects.all()
context['marketing'] = Marketing.objects.all()
context['feature'] = Feature.objects.all()
context['skills'] = Skills.objects.all()
return context
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
form.send_email()
return super(HomePageFormView, self).form_valid(form)
Is it a good practice to combine form logic along with other
references and variables in the same view? I would just like to
confirm that yes this is normal and acceptable or that no I am doing
things unacceptably.
It is perfectly acceptable to add other things to the context in your views (otherwise, how would you render things?).
The problem comes when you are talking about form logic, and here is where there are some issues with your code/approach.
There is a convention in django (which you are free to violate), is that forms go in a forms.py file that is part of your application.
You can perfectly declare all your form classes in your views.py, there is nothing wrong with this approach but once you start collaborating with others, or start combining public django apps into your code, it is best to use a convention. After all, a software development framework is nothing but a bunch of conventions and some helpers all bundled together nicely.
However, a more serious problem with your logic is that you are not using form validation - and this you must absolutely stop right now.
Forms are - at their core - a way to validate dictionaries, and they are one of the most powerful features of the django framework. They allow you to validate any dictionary and are used anywhere you are working with models or data.
The code you have written is almost exactly what the form validation does - it checks if required fields are missing in the form (or, think of another way - required keys are None in the dictionary) and then adds error messages and marks the form (or dictionary) as invalid.
The basic logic of using forms is like this:
def someview(request):
form = SomeForm() # This creates an unbound (empty) form
if request.method == 'POST':
form = SomeForm(request.POST, request.FILES) # Bind the form to
# POST data
if form.is_valid():
# perform validation
# do something with form's data
data = form.cleaned_data['somefield']
# Or, if its a model form (a form that is tied to a
# model), save the model since the form is validated
obj = form.save()
# All post requests should redirect
return redirect('index')
else:
# The form was not valid, return the form
# to the view, except this time it will
# contain helpful error messages
return render(request, 'form.html', {'form': form})
else:
# Return an empty form to the view
# for the user to fill in, as this is a GET not POST
# request
return render(request, 'form.html', {'form': form})
You can always customize the validation rules for a form either on a field-by-field basis, or on the overall data in the form. This is discussed in the documentation on form and field validation.
Off-hand there looks like some non DRY boilerplate if I do this in
each view that needs a form.
The new CBV have solved this problem by taking care of the repeated logic (one of the benefits of inheritance and classes). The code I pasted above can be minimized to the following when using FormView:
from django.core.urlresolvers import reverse_lazy
from django.views.generic.edit import FormView
class SomeView(FormView):
template_name = 'form.html'
form_class = SomeForm
success_url = reverse_lazy('index')
def form_valid(self, form):
data = form.cleaned_data['somefield']
return super(SomeView, self).form_valid(form)
There also appears to be some debate in the blogs & tutorials for form
logic. Its hard to tell whether modifying a CBV or using some good ol'
FBV is preferred. I just don't want to get any bad habits while I am
still new and learning.
There is nothing wrong with using FBV - they are still perfectly valid django. The benefit you get with CBV is that common functionality is only written once. My advice is to use CBV when you have common logic that you want to modify on a per-view basis. Forms is a good example, displaying models, pagination, rendering simple templates, downloading data (for example, you can have one base view that converts objects to Excel, and then inherit from here in any view that needs to provide a download feature) are good candidates for CBV.