django ModelForm save() method issue - python

I have a model form:
class SnippetForm(ModelForm):
class Meta:
model = Snippet
exclude = ['author', 'slug']
and I want to be able to edit a particular instance by using this:
def edit_snippet(request, snippet_id):
#look up for that snippet
snippet = get_object_or_404(Snippet, pk=snippet_id)
if request.user.id != snippet.author.id:
return HttpResponseForbidden()
if request.method == 'POST':
form = SnippetForm(data=request.POST, instance=snippet)
if form.is_valid():
form.save()
return HttpResponseRedirect(snippet.get_absolute_url())
else:
form = SnippetForm(instance=snippet)
return render_to_response(SNIPPET_EDIT_TEMPLATE,
{'form':form, 'add':False, 'user':request.user},
RequestContext(request))
Notice that at the line
form = SnippetForm(data=request.POST, instance=snippet)
, I created a form that use the data supplied from the user, and bound it with the instance found using the primary key (received from the url). According to django documentation, when I call save() the existing instance should be updated with POSTED data. Instead, what I see is a new object is created and saved into the database. What went wrong? Thanks a lot.
[Edit] This is really embarrassed. The code indeed has nothing wrong with it. The only thing that messed up the whole thing was the action I put in the template (as I use a same template for add and edit a snippet)....Thanks a lot for your help, really appreciate that.

I don't see why it would happen. What version of django is it?
In any case, you can manually force update passing the corresponding argument.
form = SnippetForm(data=request.POST, instance=snippet, force_update=True)

Related

Django file field update causing error even though not required

I have an app that serves to update certain fields of a model. There are 4 possible fields that could be updated: resolution, upload4, upload5, and upload6. The upload fields are NOT required. If I do not include the request.FILES line, the uploaded file will not be saved to the database, but it seems like because I've included it, I need to always upload the 3 files, even though they are not required. The exception I am getting is "MultiValueDictKeyError" on the POST. How can I fix this? I want the option to add 3 files, but I don't want to have to every time. I understand how to make a field not required, I don't know how to code the request.FILES to understand that it is not required.
views.py
#login_required(login_url='/login/')
def report(request, case_id):
form = ReportForm()
case = get_object_or_404(Incident, pk=case_id)
# if this is a POST request we need to process the form data
if request.POST:
# create a form instance and populate it with the data from the request:
form = ReportForm(request.POST)
if form.is_valid():
resolution = (form.cleaned_data['resolution']) # grabbing action_taken from user input
case.resolution = resolution
case.upload4 = request.FILES['upload4']
case.upload5 = request.FILES['upload5']
case.upload6 = request.FILES['upload6']
case.status = Status.objects.get(status='closed')
case.save(update_fields=['resolution', 'status', 'upload4', 'upload5', 'upload6'])
context = { 'case': case,
'form': form}
return HttpResponseRedirect(reverse('dashboard_app:dashboard'))
template = "report.html"
#form = CaseForm()
context = { 'case': case,
'form': form}
return render(request, template, context)
The point is that you are ignoring the validation that form does, and going straight back to the data from the request. So, yes, that will break if the forms are not there. But this is exactly why we use forms.
case.upload4 = form.cleaned_data['upload4']
etc.
It would be even easier if you used a ModelForm; then you could pass case as the instance argument of the form, and just do form.save(), replacing almost all the code inside your is_valid block.
This will solve the problem.
case.upload4 = request.FILES.get('upload4')
case.upload5 = request.FILES.get('upload5')
case.upload6 = request.FILES.get('upload6')

Django form returns is_valid() = False and no errors

I have simple view in django app, which I want to show only when one of the forms is valid. I have something like:
#login_required
#require_role('admin')
def new_package(request):
invoicing_data_form = InvoicingDataForm(instance=request.user.account.company.invoicingdata)
if invoicing_data_form.is_valid():
# all here
return HttpResponse('Form valid')
else:
logger.info("Form invalid")
return HttpResponse(json.dumps(invoicing_data_form.errors)
I always get log info message that form is invalid, however, I get nothing in
invoicing_data_form.errors
It is very strange, because I am validating this form in other view using user input data and it works just fine. Any idea?
EDIT:
Just for clarification.
I am not requesting any data from user in this form.
I am using this form to validate some model instance (this form is subclassing from ModelForm).
That's because you're not "feeding" your form.
Do this:
invoicing_data_form = InvoicingDataForm(instance=invoice, data=request.POST or None)
You have an unbound form.
https://docs.djangoproject.com/en/1.7/ref/forms/api/#bound-and-unbound-forms
A Form instance is either bound to a set of data, or unbound.
If it’s bound to a set of data, it’s capable of validating that data and rendering the form as HTML with the data displayed in the HTML.
If it’s unbound, it cannot do validation (because there’s no data to validate!), but it can still render the blank form as HTML.
To bind data to a form, pass the data as a dictionary as the first parameter to your Form class constructor:
invoicing_data_form = InvoicingDataForm(request.POST or None, instance=invoice)
If you're already giving request.POST to your form using request.POST or None, but it's still invalid without errors, check that there isn't any redirect going on. A redirect loses your POST data and your form will be invalid with no errors because it's unbound.
I got this for AuthenticationForm which needs AuthenticationForm(None, request.POST) see Using AuthenticationForm in Django
I want to expand on the answer by #yuji-tomita-tomita
I typically use a CBV approach in Django, and how I'm handling forms:
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
# do things
Reading the source code I noticed that self.get_form() using get_form_kwargs(self) to populate the form with request.POST, thus getting bound to data. So if you're overloading it like I did:
def get_form_kwargs(self):
company = self.get_company()
return {"company": company}
Make sure to call the super(), and it will finally work:
def get_form_kwargs(self):
company = self.get_company()
kwargs = super().get_form_kwargs()
kwargs.update({"company": company})
return kwargs

flask-wtf editing a model using wtform Form constructor: pre-filling the form

I am reading the Flask Web Development book and came across this:
def edit_profile():
form = EditProfileForm()
if form.validate_on_submit():
current_user.name = form.name.data
current_user.location = form.location.data
current_user.about_me = form.about_me.data
db.session.add(user)
flash('Your profile has been updated.')
return redirect(url_for('.user', username=current_user.username))
form.name.data = current_user.name
form.location.data = current_user.location
form.about_me.data = current_user.about_me
return render_template('edit_profile.html', form=form)
Basically, when the form is not posted or doesn't validate, this copies over the data from the current user. Now reading up on wtforms, I read this about the init method on a form:
obj – If formdata is empty or not provided, this object is checked for attributes
matching form field names, which will be used for field values.
So I guess that means that we could write this (the sample below is my own):
def edit_post(post_id):
post = Post.query.get_or_404(post_id)
if current_user != post.author:
abort(403)
# Below is the line I am concerned about
form = PostForm(formdata=request.form, obj=post)
if form.validate_on_submit():
form.populate_obj(post)
db.session.commit()
return redirect(url_for('user', username=current_user.username))
return render_template('post_form.html', form=form)
I figure that this should fill the form instance from the database model on GET, and from POST data after post. Testing this, it seems to work..
Now my question: is this way of writing an edit view correct? Or should I copy everything over field by field, like in the book?
Loading in a POST MultiDict is certainly the accepted way to map key/value pairs to your WTForms instance. Even more so, if you are using the Flask-WTF extension, this is automatically done for you, it is one of the perks that this extension brings you.
If you would crack open the code of Flask-WTF you would see that it inherits the SecureForm class of WTForms and tries to load in the Werkzeug POST MultiDict (called formdata) by default (if it is present). So loading in your form in your view like this:
form = PostForm(obj=post)
Should be sufficient (if using Flask-WTF) to also fill the fields with POSTed data.
The way it is done in your book example is certainly not wrong, but creates a lot of unnecessary code and is error prone / redundant - one could forget to mention a field in the view that is declared in the WTForms instance.

Retrieve Current User in Django Outside Template

Currently I have a form that was built using the form wizard, the form is processed using a separate script that I wrote. I need to pass the current user (user currently logged in) to this script so that I can run an insert query to my database. Any suggestions on how to do this?
class QuestionWizard(SessionWizardView):
def done(self, form_list, **kwargs):
import process_form
userID = request.user.id
result = process_form.main(form_list,userID)
return render_to_response('done.html', {
#'form_data': [form.cleaned_data for form in form_list],
'data_return': result[0],
})
I believe the form wizard done function only accepts two variables.
It turns out it was a lot simpler than I anticipated. Inside of the done function I added the following:
user_id = self.request.user.id
And then passed the user_id into the process_form function.
Annoying that I overlooked it, but glad that I figured it out.

Multiple Instances of a django app, does django support this

I have written a simple feedback application in django. It's not particulairly complex, basically it allows authenticated users to write a shot message with a subject line and submit that message via a form. I then allows who are in a selected group to view user submitted feedback. In the future I may add more functionality but for now it does what I want.
Here comes my question, the site I'm building has multiple places where I would like to use the feedback app, for example I have a "what do you think of the site?" kind of page at /dev/feedback/ I also have one for customer support feedback at "/support/feedback/" Currently I have just copied the code from my mysite.apps.dev.feedback over to mysite.apps.support.feedback.
The problem is that this has now created two separate copies of the same code. Despite having just written the app the two versions are already starting to diverge which is annoying. My question is simply how do I create multiple instances of the same app in a django site with distinct database models?
Some resources I've found related but not helpful are https://docs.djangoproject.com/en/dev/topics/http/urls/ and Reversing namespaced URLs in Django: multiple instances of the same app The first page does not offer much on the issue and the second page provides somewhat cludgey and impractical solutions that seem to be both unrelated and more work than their worth. Is there a proper way to implement multiple instances of the same django app?
Single model approach
I'd personally try to keep this as one app and have a view that can handle being posted from multiple locations / tag them appropriately.
As S.Lott says, this is the way to go. I am providing alternatives if you're curious about methods to keep your code in one place in other situations.
For example, you could add a category field to your model, set up a single url conf which accepts an argument in the URL such as /(?P<category>\w+/feedback/$ and have the view simply tag the feedback with the appropriate category.
class MyForm(forms.ModelForm):
class Meta:
model = Feedback
def my_view(request, category):
form = MyForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
feedback = form.save(commit=False)
feedback.category = category
feedback.save()
return http.HttpResponse("Thanks for posting!")
return render(request, "mytemplate.html", {'form': form})
# urls.py
(r'^(?P<category>\w+)/feedback/$', 'my_view')
# user can visit dev/feedback or support/feedback and the feedback will be tagged appropriately
Abstract base class
Another solution is to build an abstract base class, then create subclasses for your distinct tables. That should solve the issue with your code getting out of sync.
You'd have a single abstract model (which has no tables) from which your "real" models in your separate apps would be based on.
Dynamically generated views
If you must have separate models, you could potentially write a dynamically constructed view.
def view_generator(model_class):
class MyForm(forms.ModelForm):
class Meta:
model = model_class
def my_view(request):
form = MyForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
form.save()
return http.HttpResponse("Thanks for posting!")
return render(request, "mytemplate.html", {'form': form})
return my_view
# urls.py
from foo import view_generator
(r'^my_first_feedback_form', view_generator(Model1))
(r'^my_second_feedback_form', view_generator(Model2l))
how do I create multiple instances of the same app in a django site with distinct database models?
You shouldn't.
You simply use the feedback app model in the other two apps with a simple from feedback.models import Feedback.
Then your support app can create, retrieve, update and delete Feedback objects.
Your dev app, similarly, can create, retrieve, update and delete Feedback objects because it imported the model.
That's all that's required: import.
Thanks Yuji Tomita for a very thorough answer, my final solution is derived very closely from his suggestion, but is different enough that I thought I would post it as another option if someone else runs into the same situation that I am in.
Firstly in my mysite.apps.feedback.models file I put
class Feedback( models.Model ):
subject = models.TextField( max_length=100 )
body = models.TextField( max_length=100 )
# Some other stuff here...
# Finally I used the suggestion above and created a field which I
# use to label each entry as belonging to a specific instance of the app.
instance_name = models.TextField( max_length=20 )
In my mysite.apps.feedback.views file I put
def save_message( request, instance_name ):
if request.method == 'POST':
form = FeedbackFrom( request.POST )
if form.is_valid():
form.instance.instance_name = instance_name
form.save()
return render("feedback/thanks.html")
else:
return render("feedback/submit.html", {'form':form })
else:
return render("feedback/submit.html",{'form':FeedbackForm()})
#user_passes_test( is_staff )
def all_messages( request, instance_name ):
messages = Feedback.objects.filter( instance_name = instance_name )
return render("feedback/view_all.html",{'feedback':messages} )
In my mysite.apps.dev.urls file I put
url(r'^feedback/', include('mysite.apps.feedback.urls'),
{'instance_name':'dev'}),
In my mysite.apps.support.urls file I put
url(r'^feedback/', include('mysite.apps.feedback.urls'),
{'instance_name':'support'}),
This will separate feedback messages by app instance. Note that my actual code is more complex but this should be good enough for anyone with a similar problem to get a solution up and running pretty quickly. Hope this is useful to anyone in a similar situation. Thanks again to Yuji Tomita for the suggestions upon which this solution is based.

Categories