Django: Can't save a form - django

I allow users to view and edit a few fields of a database record represented by a ModelForm.
Here is a snippet of the code from the view:
def edit(request, id):
obj = get_object_or_404(Record, pk=record_id)
if request.method == 'POST':
form = forms.RecordForm(request.POST, instance=obj)
if form.is_valid():
form.save()
The problem is that because I don't pass all the fields to the template, form.is_valid() fails with a missing values error. How can I update an existing record with just the subset of record fields I display to the user?

Use the fields tuple in the form's Meta definition to make sure the form only includes the fields you need - or use exclude to remove the ones you don't want.

Related

Is it possible to get the data from a formset as a list of dictionaries before it is validated in Django?

I know that after validation, formset.cleaned_data gives a list of dictionaries of a formset. Now, I need to obtain the same list before it is validated. Is this possible?
if request.method == 'POST':
formset = SampleFormSet(request.POST, prefix='sample')
print(formset...?) #Should print all the data in the formset
if formset.is_valid():
...
else:
formset... #Modify it
It's possible.
There are two path.
First : On views you can do some like this:
def my_func(request):
if request.method == 'POST':
#your var before validate
my_var = request.POST.get("your_field")
# to do you code.
Another option is put on your method clean() in your form.
Like this.
def clean(self):
#your var before validate
your_var = self.cleaned_data["your_field"]
I just checked the documentation, and I do not think it is possible at the moment.
Source code of formsets in Django 2.0
Documentation of formsets in Django 3.2
Yes, you can get acces to row data though formset.data (returns QueryDict).

How to access individual form objects from a modelformset_factory?

I've deployed a formset using modelformset_factory. However rather than saving the entire formset, I need to loop through the forms in the formset, perform some logic on them, and save each one individually. At the moment I'm having to use the ID from each form in the formset to get the object it represents. Is there a cleaner way of doing this?
def accounts_import(request,pk):
account = get_object_or_404(Account, pk=pk)
# Create transactions queryset for use in formset
transactions = Transaction.objects.filter(account=account.monzo_account, import_type=None).order_by('-id')
FormSet = modelformset_factory(Transaction, form=TransactionsImportForm, extra=0)
if request.method == 'POST':
formset = FormSet(request.POST)
if formset.is_valid():
for form in formset:
object = Transaction.objects.get(id=form.cleaned_data['id'])
# Do some stuff on the object
object.save()
Ok looks like form.cleaned_data['id'] returns the object and not the ID, so I got what I wanted.

Adding a value to a database field that is in your model but is not in your form

I have a model with a property called authID that looks like this:
authID = models.CharField(max_length=400)
In my view, all of the fields that are in my form get put into the database as follows:
if request.method == 'POST':
form = PersonalDetailsModelForm(request.POST)
if form.is_valid():
form.save()
What I want to know how to do is that when they submit the form, I want to also put the value of authID into my database. However, authID is not a field in my form. I don't want to make it a hidden field either for security reasons.
How do you add a value to a database field that is in your model, but is not in your form?
You can override the save() method on PersonalDetailsModelForm to set this value before saving the instance:
def save(self, commit=True):
self.instance.authID = 'some_value' # Set the auth ID
return super().save(commit=commit)
If the authID is coming from your view, then you can pass that as a keyword argument to your overridden save() method.
Call the save() method of Django Form with commit=False
#views.py
if request.method == 'POST':
form = PersonalDetailsModelForm(request.POST)
if form.is_valid():
model_instance = form.save(commit=False) # 'commit=False' plays key role here
model_instance.authID = 'your auth id'
model_instance.save()
...
Reference
Django model form save() method

Form Validation Error when a required field is remained blank in django

I want to have errors as a label above a field if it is not filled.
This is my views.py:
#login_required(login_url='user_profile:login')
def NewWriting(request):
if request.method=="POST":
form=WritingForm(request.POST)
if form.is_valid():
post=form.save(commit=False)
post.author=request.user
post.save()
return redirect('user_profile:index')
else:
form = WritingForm()
subject = Subject.objects.all()
return render(request,'user_profile/writing_form.html', {'form':form , 'subject':subject})
what should I add to my code?
Thanks
Without seeing your form class ...
Option 1:
If you really want the user to be able to submit the form with empty data and then specifically show them that error using the form, set the required=False kwarg for the specific field in your WritingForm class. Then override the clean_<fieldname> (link) method and then you could do:
def clean_<fieldname>:
if self.cleaned_data['<fieldname>'].strip() == '':
raise ValidationError('This field cannot be blank!')
return self.cleaned_data['<fieldname>']
Replacing <fieldname> with whatever that fieldname is.
Option 2:
The default for any form is to make all fields required (IE: required=True kwarg on the field). So in general, if the field is required most browsers will at least move the cursor to the empty field and won't allow the form to be submitted while there is no data in the field.
You also need to return a bound form in the case where form.is_valid() returns False or you won't ever see the errors (right now you don't return anything if the form is invalid). Please see the django docs here for a common functional view pattern using forms.
You need to add another all to render if the form is not valid, and in your template, you need to make use of form.errors. Something like this should work so that form validation errors are then passed back to the UI/template for display to the user:
#login_required(login_url='user_profile:login')
def NewWriting(request):
form = None
if request.method=="POST":
form=WritingForm(request.POST)
if form.is_valid():
post=form.save(commit=False)
post.author=request.user
post.save()
return redirect('user_profile:index')
if form is None:
form = WritingForm()
subject = Subject.objects.all()
return render(request,'user_profile/writing_form.html', {'form':form , 'subject':subject})

Django - Adding initial value to a formset

I have a many-to-many relationship between two classes (Lesson and Student), with an intermediary class (Evaluation).
I am trying to set up a form which will allow me to add a lesson with students and the related evaluation data. I can get all of the fields I want to display correctly, however I also need to set an initial value behind the scenes (the current user), as it does not make sense to have it in the form.
I have tried following the docs but I think I have a syntax error in the way I am passing the data to the formset.
The error I receive is as follows:
__init__() got an unexpected keyword argument 'initial'
My actual view (with my attempt at adding the initial data removed) looks like this:
def addlesson(request):
LessonFormset = inlineformset_factory(Lesson, Evaluation, exclude=('user',), max_num=5)
if request.method == 'POST':
lesson = Lesson(user=request.user)
form = LessonForm(request.POST, instance=lesson, user = request.user)
formset = LessonFormset(request.POST, instance = lesson)
if form.is_valid() and formset.is_valid():
form.save()
formset.save()
return HttpResponseRedirect("/")
else:
form = LessonForm(user = request.user)
formset = LessonFormset()
return render_to_response("addlesson.html", {
'form': form,
'formset' : formset,
}, context_instance=RequestContext(request))
Could anyone show me to correct syntax to use to set the current user in the formset?
This is what I had before but it was giving me the error at the start of my post:
initial={'user': request.user},
Any advice appreciated
Thanks
It's not clear to me why you are using a formset when it looks like you only want to add one row. A regular form would have been how I would do it if there was only one row. But, here's how I set the default value in a formset.
I exclude the field, just like you already have in your code. Then:
if form.is_valid() and formset.is_valid():
form.save()
models = formset.save(commit=False)
for i in models:
i.user = request.user
i.save()
return HttpResponseRedirect("/")
I tried Umang's answer and it didn't work good for when you want to change a value with a specific index. When you save the formset it will change the values that was changed.
But if you change models = formset.save(commit=False) to models = formset
and then you also need to change i.user = request.user to i.instance.user = request.user
if form.is_valid() and formset.is_valid():
form.save()
# changed to formset instead of formset.save(commit=False)
models = formset
for model in models:
# changed to i.instance.user instead of i.user, except renamed i to model.
model.instance.user = request.user
model.save()
# save the formset
formset.save()
return HttpResponseRedirect("/")
Now when you want to change an index it will include all the forms, not only the ones that was changed when you save.
Example:
views.py
if form.is_valid() and formset.is_valid():
form.save()
models = formset
index = 0
# This will only change the first form in the formset.
models[index].instance.user = request.user
models.save()
formset.save()
return HttpResponseRedirect("/")