Best practices on saving in a view, based on example code - python

I'm new to Django and looking for best practices on the code I just wrote (below). The code currently exists in my view.py and simply creates a new event. Being familiar with other languages it just 'smells bad' if you know what I mean. Could someone point out how they would do this simple task.
The only thing looking at my code again (and from reading the docs a little more), would be to move the request.user into the models.py save function.
Anything else which is a big newbie mistake below?
#login_required
def event_new(request):
# If we had a POST then get the request post values.
if request.method == 'POST':
form = EventForm(request.POST)
# Check we have valid data
if form.is_valid():
# If form has passed all validation checks then continue to save it.
city = City.objects.get(name=form.cleaned_data['autocompleteCity'])
category = form.cleaned_data['category']
event = Event(
name=form.cleaned_data['name'],
details=form.cleaned_data['details'],
date=form.cleaned_data['date'],
start=form.cleaned_data['start'],
end=form.cleaned_data['end'],
category=category,
city=city,
user=request.user,
)
event.save()
messages.add_message(request, messages.SUCCESS, 'Event has been created.')
return HttpResponseRedirect('/events/invite/')
else:
messages.add_message(request, messages.ERROR, 'Error')
context = {'form': form}
return render_to_response('events/event_edit.html', context, context_instance=RequestContext(request))
else:
form = EventForm
context = {'form': form}
return render_to_response('events/event_edit.html', context, context_instance=RequestContext(request))

You should read about create forms from models. The ModelForm class will save you from copying the fields from the form to the model.
Apart from that this view looks pretty normal to me.
You can even get rid of some of the boilerplate code (if request.method == "POST", if form.is_valid(), etc.) with the generic FormView or CreateView. Since you seam to have some special form handling it might not be of any use for you, but it might be worth a look.
This code is not 100% complete (your special logic for cities is missing) but apart from that should be pretty complete and give you an idea how generic views could be used.
forms.py
from django.forms import ModelForm
class EventForm(ModelForm):
def __init__(self, user, **kwargs):
self.user = user
super(EventForm, self).__init__(**kwargs)
class Meta:
model = Event
def save(self, commit=True):
event = super(EventForm, self).save(commit=False)
event.user = self.user
if commit:
event.save()
views.py
from django.views.generic.edit import CreateView
class EventCreate(CreateView):
model = Event
form_class = EventForm
template = "events/event_edit.html"
success_url = "/events/invite/" # XXX use reverse()
def get_form(form_class):
return form_class(self.request.user, **self.get_form_kwargs())
def form_valid(form):
form.user = self.request.user
messages.success(request, 'Event has been created.')
super(EventCreate, self).form_valid(form)
def form_invalid(form):
messages.error(request, 'Error')
super(EventCreate, self).form_invalid(form)
urls.py
url(r'event/add/$', EventCreate.as_view(), name='event_create'),

I think this looks great. You have followed the conventions from the docs nicely.
Moving request.user to a model would certainly be an anti pattern - a views function is to serve in the request/response loop, so access this property here makes sense. Models know nothing of requests/responses, so it's correct keep these decoupled from any view behavior.
Only thing that I noticed was creating a category variable out only to be used in construction of the Event, very minor.

Related

How to remove built-in errorlist of Django model forms?

I made a model form in Django that enables user to create an instance of that model. All is well save one thing. Built-in error lists of Django is annoying. I want those errors to show itself when user actually made that error. When I open my page it looks like this
Please help me how do I remove these errorlist temporarily so that when user left input fields empty. It pop ups itself?
I don't know if you need these codes to solve this problem, but here it is:
views.py file here
#login_required(login_url='/accounts/login/')
def article_create(request):
article_form = ArticleForm(request.POST or None, request.FILES)
if article_form.is_valid():
instance = article_form.save(commit=False)
instance.author = request.user
article_form.save()
return render(request, 'blog/article-create.html', {'form': article_form})
Thank you
You use the request.POST or None idiom - which I dislike, but never mind - for the data parameter, but not for the files parameter. Since you pass an (empty) dict for the files, Django takes your form as bound, and therefore tries to validate it.
If you insist on using this idiom, you need to do it for both:
article_form = ArticleForm(request.POST or None, request.FILES or None)
But really, it's much more Pythonic to be explicit:
if request.method == 'POST':
article_form = ArticleForm(request.POST, request.FILES)
if article_form.is_valid():
instance = article_form.save(commit=False)
instance.author = request.user
article_form.save()
return redirect('/')
else:
article_form = ArticleForm()
return render(request, 'blog/article-create.html', {'form': article_form})
Note, you always need to redirect after a successful POST, as I have done above.

Django Key Error: KeyError at 'template.html' request when initiating a form

I'm working with Pinax-Stripe library, and I want to create a custom account for the logged in user. I'm trying to rewrite the CreateCustomAccountView() as a function based view. The reason for that, is that I don't understand where CreateBankAccountView comes from and it's nowhere to be seen in the GitHub code.
So for simplicity, I have the following form:
class Form(DynamicForm):
# some form fields
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request")
views.py
def view(request):
if request.method == 'POST':
form = Form(request.POST)
else:
form = Form()
return render(request, 'dashboard/template.html', {'form': form})
I do have request in my template processor. I'm not sure why this happens and how it's possible to instantiate an empty form?
Since your form pops request without a default,
self.request = kwargs.pop("request")
you must pass the request to the form. For example:
if request.method == 'POST':
form = Form(request.POST, request=request)
else:
form = Form(request=request)
The issue you see with the class based view is legit and I guess that developer suddenly overlooked that part of the code.
If you still want to use class-based view try to replace:
form_kwargs = super(
CreateBankAccountView, self
).get_form_kwargs(
*args, **kwargs
)
With:
form_kwargs = super(CreateCustomAccountView, self).get_form_kwargs()
The class is CreateCustomAccountView, and get_form_kwargs() takes no arguments.

"save and add another" in Django (not admin): submit then pre-populate one field of form

I have a form, "results", where one of the fields, "subjectID", is many-to-many because there's more than one result for each subject. I want one of the submit buttons to let me save what I've entered, then redirect to the same form, now unbound except that the many-to-many "subjectID" field stays the same so I can enter more results for that subject.
Edit: I should have made it clear that I wanted the instance that I had selected in the subjectID field to stay the same. I posted the code below that actually seems to be working for me
from models.py
class ResultsForm(forms.Modelform):
class Meta:
model = models.Results
fields = ['subjectID', # this is the field want
# to populate the form with when I "save and add another"
'slideNum', # IntegerField
'resultType' ] # ForeignKey
from views.py
def addResults(request):
if request.method == 'POST'
form = ResultsForm(request.POST)
if form.is_valid():
form.save()
if 'Save_and_add_another' in request.POST:
subjectID = form.fields['subjectID']
prepop = {'subjectID' : subjectID}
form = ResultsForm(initial=prepop)
return render(request, 'slideAdmin/addResults.html', {'form': form})
elif 'Save_and_return' in request.POST:
return HttpResponseRedirect('/home/')
else:
form = ResultsForm()
return render(request, 'slideAdmin/addResults.html', {'form': form})
Right now when I click on "save and add another" from my addResults form, I get this error:
TypeError at /slidebox/addResults
'ModelMultipleChoiceField' object is not iterable
which happens when rendering {{ form.as_p }} in the template.
Edit: Changes I made to views.py
if 'Save_and_add_another' in request.POST:
subjectID = form.cleaned_data.get('subjectID')
form = ResultsForm(initial={'subjectID': subjectID})
return render(request, 'slideAdmin/addResults.html', {'form': form})
As far as I can tell, this change works. Thanks again
You should always use form.cleaned_data.get('subjectID') versus pulling the field directly from the post data. You need to pass in a list of the pk's for the M2M field.
Your view can also use a touch of cleanup:
from django.core.urlresolvers import reverse
def addResults(request):
form = ResultsForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
form.save()
if 'Save_and_add_another' in request.POST:
subjectID = form.cleaned_data.get('subjectID', [])
if subjectID:
subjectID = subjectIDs.split(',')
form = ResultsForm(initial={'subjectID': subjectID})
elif 'Save_and_return' in request.POST:
return HttpResponseRedirect(reverse('home')) # don't hard code
return render(request, 'slideAdmin/addResults.html', {'form': form})
I'm not sure if you will be able to keep the form unbound when initialized.
Your form.fields is an ordered dict of django.forms.fields objects. You just want the ids, and not all the other info that comes across it.
Get the data straight from the POST dictionary.
subjectID = request.POST.get('subjectID', '')
If this is a true many to many model. You need to make sure the data is setup correctly for the initialization.
# We have to special-case M2Ms as a list of comma-separated PKs.
if isinstance(f, models.ManyToManyField):
initial[k] = initial[k].split(",")
Here is the initialization method from the django source code for Admin (or as I call it my super detailed and complicated Django cheat sheet, I am pedantic)
def get_changeform_initial_data(self, request):
"""
Get the initial form data.
Unless overridden, this populates from the GET params.
"""
initial = dict(request.GET.items())
for k in initial:
try:
f = self.model._meta.get_field(k)
except FieldDoesNotExist:
continue
# We have to special-case M2Ms as a list of comma-separated PKs.
if isinstance(f, models.ManyToManyField):
initial[k] = initial[k].split(",")
return initial
Some PEP8 nonsense as well
classes are camel case ex: class MyAwesomeClass(object):
everything else is lower with underscores. ex: awesome_id = awesome1245
Good Luck!!

How do I save post data using a decorator in Django

I have the following view in my django app.
def edit(request, collection_id):
collection = get_object_or_404(Collection, pk=collection_id)
form = CollectionForm(instance=collection)
if request.method == 'POST':
if 'comicrequest' in request.POST:
c = SubmissionLog(name=request.POST['newtitle'], sub_date=datetime.now())
c.save()
else:
form = CollectionForm(request.POST, instance=collection)
if form.is_valid():
update_collection = form.save()
return redirect('viewer:viewer', collection_id=update_collection.id)
return render(request, 'viewer/edit.html', {'form': form})
It displays a form that allows you to edit a collection of images. The footer of my html contains a form that allows you to request a new image source from the admin. It submits to a different data model than the CollectionForm. Since this is in the footer of every view, I want to extract lines 5-7 of the code and turn it into a decorator. Is this possible and if so how might I go about doing that?
I would make a new view to handle the post of the form. And then stick a blank form instance in a context processor or something, so you can print it out on every page.
If you do want to make a decorator, i would suggest using class based views. That way, you could easily make a base view class that handles the form, and every other view could extend that.
EDIT:
Here's the docs on class based views: https://docs.djangoproject.com/en/dev/topics/class-based-views/intro/
Note, I would still recommend having a separate view for the form POST, but here's what your solution might look like with class based views:
class SubmissionLogFormMixin(object):
def get_context_data(self, **kwargs):
context = super(SubmissionLogFormMixin, self).get_context_data(**kwargs)
# since there could be another form on the page, you need a unique prefix
context['footer_form'] = SubmissionLogForm(self.request.POST or None, prefix='footer_')
return context
def post(self, request, *args, **kwargs):
footer_form = SubmissionLogForm(request.POST, prefix='footer_')
if footer_form.is_valid():
c = footer_form.save(commit=False)
c.sub_date=datetime.now()
c.save()
return super(SubmissionLogFormMixin, self).post(request, *args, **kwargs)
class EditView(SubmissionLogFormMixin, UpdateView):
form_class = CollectionForm
model = Collection
# you can use SubmissionLogFormMixin on any other view as well.
Note, that was very rough. Not sure if it will work perfectly. But that should give you an idea.

How to let Django's generic view use a form with initial values?

I know how to set initial values to a form from the view. But how do I go about letting a generic view set initial values to a form? I can write a wrapper view for the generic view, but I still have no access to the form object instantiation.
The specific goal I'm trying to achieve is to have a user create a new object, using the create_object generic view. However, I'd like to set a field of the object 'user' to the currently logged in user, which is only accessible as request.user. How can the form be initialized to have this field?
Edit: I came across __new__. Could this call its own constructor with some default arguments?
Many thanks.
Unfortunately, you cannot achieve this behavior through Django's create_object generic view; you will have to write your own. However, this is rather simple.
To start off, you must create a new form class, like this:
from django import forms
class MyForm(forms.ModelForm):
class Meta:
model = MyModel # model has a user field
Then you would be able to create a view like this:
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.http import HttpResponseRedirect
from django.contrib.auth.decorators import login_required
#login_required
def create_mymodel(request):
if request.method == 'POST':
# Get data from form
form = MyForm(request.POST)
# If the form is valid, create a new object and redirect to it.
if form.is_valid():
newObject = form.save()
return HttpResponseRedirect(newObject.get_absolute_url())
else:
# Fill in the field with the current user by default
form = MyForm(initial={'user': request.user})
# Render our template
return render_to_response('path/to/template.html',
{'form': form},
context_instance=RequestContext(request))
You could do this in a generic view wrapper by dynamically constructing a form class and passing it to the generic view, but that cure is probably worse than the disease. Just write your own view, and wait eagerly for this to land.
If you want all the features of the generic view then you can just create a new generic view using the original as a template.
Eg:
def create_object_with_initial(request, model=None, template_name=None,
template_loader=loader, extra_context=None, post_save_redirect=None,
login_required=False, context_processors=None, form_class=None, initial=None):
if extra_context is None: extra_context = {}
if login_required and not request.user.is_authenticated():
return redirect_to_login(request.path)
model, form_class = get_model_and_form_class(model, form_class)
if request.method == 'POST':
form = form_class(request.POST, request.FILES)
if form.is_valid():
new_object = form.save()
msg = ugettext("The %(verbose_name)s was created successfully.") %\
{"verbose_name": model._meta.verbose_name}
messages.success(request, msg, fail_silently=True)
return redirect(post_save_redirect, new_object)
else:
print "creating", form_class, " with initial data ", initial
form = form_class(initial=initial)
# Create the template, context, response
if not template_name:
template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
c = RequestContext(request, {
'form': form,
}, context_processors)
apply_extra_context(extra_context, c)
return HttpResponse(t.render(c))
This is copied from /site-packages/django/views/generic/create_update.py with only lines 3 and 21 changing to incorporate the initial data.
Then use it as you might expect:
object_info = {
'model': YourModel,
'initial': {'data' : 'Initial Value'},
'template_name': 'template.html'
}
url(r'^path/$',
login_required(create_object_with_initial),
object_info,
name='url_name'),
That should work.

Categories