I have created an invoice and items in it using Modelform and inlineforset_factory.
Now I am trying to create edit form, but I am getting MultiValueDictKeyError when I try to edit those inline fields. If I just create new inlineformsets I can edit Modelform just fine. After one hour of googling I am no closer to finding any solution.
maxItems = 20
ItemFormSet = inlineformset_factory(Invoice, Item, can_delete=True, extra=maxItems)
Creating of new invoice (working perfectly)
def new_invoice(request):
if request.method == "POST":
form = InvoiceForm(request.POST)
if form.is_valid():
invoice = form.save(commit=False)
item_formset = ItemFormSet(request.POST,instance=invoice)
if item_formset.is_valid():
invoice.dateCreated = datetime.datetime.now()
invoice.save()
item_formset.save()
return redirect('list/new0')
else:
form = InvoiceForm()
item_formset = ItemFormSet(instance=Invoice())
return render(request, "form.html", {"form": form, "item_formset": item_formset })
Editing invoice (MultiValueDictKeyError)
def edit_invoice(request, invoice_id):
invoicer = get_object_or_404(Invoice, pk=invoice_id)
if request.method == "POST":
form = InvoiceForm(request.POST, instance=invoicer)
if form.is_valid():
invoice = form.save(commit=False)
item_formset = ItemFormSet(request.POST,instance=invoice)
if item_formset.is_valid():
invoice.dateCreated = datetime.datetime.now()
invoice.save()
item_formset.save()
return redirect('list/new0')
else:
form = InvoiceForm(instance=invoicer)
item_formset = ItemFormSet(instance=invoicer)
return render(request, "form.html", {"form": form, "item_formset": item_formset })
You have two variables, invoicer and invoice that I assume are different instances of the same object. Try re-writing your edit view like this:
def edit_invoice(request, invoice_id):
invoice = get_object_or_404(Invoice, pk=invoice_id)
if request.method == 'POST':
form = InvoiceForm(request.POST, instance=invoice)
formset = ItemFormSet(request.POST, instance=invoice)
if form.is_valid() and formset.is_valid():
invoice = form.save()
invoice.dateCreated = datetime.datetime.now()
invoice.save()
formset.save()
return redirect('list/new0')
else:
form = InvoiceForm(instance=invoice)
formset = ItemFormSet(instance=invoice)
context = {
'form': form,
'formset': formset,
}
return render(request, 'form.html', context)
Let me know if that works for you.
Ok, so I've found that problem was not in the views.py nor in forms.py, but in template. Because I've built the template without using {{form.as_p/table/...}} The form had some extra inputs - DELETE,ID and foreignKey... after adding them to my for loop, everything works fine :)
Related
I have a app where people can declare things, within a decla they can say who was present so they have to pay, only its not working. The edit function works but the fileDecla doesn't.
The part that doensn't work is the present people. When i print the people present (via print(request.POST))before i save the decla it gives all the people selected but then it doesnt save them, and when i print(decla.present) i get --> None.(it should be all the people present.
Does someone know a solution to this?
models.py
class Decla(models.Model):
owner = models.ForeignKey(Lid, on_delete=models.CASCADE)
event = models.ForeignKey(Event, on_delete=models.SET_NULL, null=True, blank=True)
content = models.TextField(max_length=50)
total = models.FloatField()
present = models.ManyToManyField(Lid, related_name="present_leden")
receipt = models.ImageField(
upload_to="declas/", null=True, blank=True
) ## this will need to be put back to nothing when it ends
verwerkt = models.BooleanField(default=False)
views.py
#login_required(login_url="login")
def fileDecla(request):
form = DeclaForm()
if request.method == "POST":
print(1, request.POST)
form = DeclaForm(request.POST, request.FILES)
if form.is_valid():
# print(form)
decla = form.save(commit=False)
decla.owner = request.user.lid
# i tried this line bellow but it didnt work
# decla.present.set(request.POST["present"])
decla.save()
messages.info(request, "Decla was created")
return redirect("agenda")
context = {
"form": form,
"stand": Stand.objects.get(owner_id=request.user.lid.id).amount,
}
return render(request, "finance/decla_form.html", context)
#login_required(login_url="login")
def editDecla(request, pk):
decla = Decla.objects.get(id=pk)
form = DeclaForm(instance=decla)
if request.method == "POST":
print(request.POST)
form = DeclaForm(request.POST, request.FILES, instance=decla)
if form.is_valid():
decla = form.save()
messages.info(request, "Decla was edited")
return redirect(request.GET["next"] if "next" in request.GET else "agenda")
context = {
"form": form,
"stand": Stand.objects.get(owner_id=request.user.lid.id).amount,
}
return render(request, "finance/decla_form.html", context)
forms.py
from django import forms
from django.forms import ModelForm
from django.forms.widgets import NumberInput, CheckboxSelectMultiple
from .models import Decla
class DeclaForm(ModelForm):
class Meta:
model = Decla
fields = "__all__"
exclude = ["owner", "id"]
widgets = {
"present": CheckboxSelectMultiple(),
}
def __init__(self, *args, **kwargs):
super(DeclaForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
if not name in ["verwerkt", "present"]:
field.widget.attrs.update({"class": "input"})
# also tried this
# elif name == "present":
# field.widget.attrs.update({"class": "CheckboxSelectMultiple"})
else:
field.widget.attrs.update({"class": "checkbox"})
Here is something from the docs:
Another side effect of using commit=False is seen when your model has
a many-to-many relation with another model. If your model has a
many-to-many relation and you specify commit=False when you save a
form, Django cannot immediately save the form data for the
many-to-many relation. This is because it isn’t possible to save
many-to-many data for an instance until the instance exists in the
database.
To work around this problem, every time you save a form using
commit=False, Django adds a save_m2m() method to your ModelForm
subclass. After you’ve manually saved the instance produced by the
form, you can invoke save_m2m() to save the many-to-many form data.
According to this, adding save_m2m() after calling decla.save() would resolve your issue:
#login_required(login_url="login")
def fileDecla(request):
form = DeclaForm()
if request.method == "POST":
print(1, request.POST)
form = DeclaForm(request.POST, request.FILES)
if form.is_valid():
# print(form)
decla = form.save(commit=False)
decla.owner = request.user.lid
# i tried this line bellow but it didnt work
# decla.present.set(request.POST["present"])
decla.save()
form.save_m2m()
messages.info(request, "Decla was created")
return redirect("agenda")
context = {
"form": form,
"stand": Stand.objects.get(owner_id=request.user.lid.id).amount,
}
return render(request, "finance/decla_form.html", context)
However, this seems like a messy solution. See this antipattern for more info.
My suggestion is to do this:
#login_required(login_url="login")
def fileDecla(request):
form = DeclaForm()
if request.method == "POST":
print(1, request.POST)
form = DeclaForm(request.POST, request.FILES)
if form.is_valid():
# This seems like a much cleaner solution and it should resolve your problem
form.instance.owner = request.user.lid
decla = form.save()
messages.info(request, "Decla was created")
return redirect("agenda")
context = {
"form": form,
"stand": Stand.objects.get(owner_id=request.user.lid.id).amount,
}
return render(request, "finance/decla_form.html", context)
Disclaimer: the code is untested. Let me know if you have any bugs.
how do I not save the form data until the transaction is done which is in a different URL, if the shipping form and the payment options were to be in the same URL then there wouldn't be this problem but it's not so how do I go about this? thx!
views.py
def checkout(request):
if request.method == 'POST':
form = ShippingForm(request.POST)
if form.is_valid():
new_shipping = form.save(commit=False)
new_shipping.customer = customer
new_shipping.order = order
#how do I not save the data until the transaction is successful
new_shipping.save()
return redirect('store:checkout_shipping')
else:
form = ShippingForm()
else:
form = ShippingForm()
context = {"form": form}
return render(request, 'shop/checkout.html', context)
def checkout_payment(request):
return render(request, 'shop/checkout_payment.html', context)
urls.py
path('checkout', views.checkout, name="checkout"),
path('checkout_payment', views.checkout_payment, name="checkout_payment"),
forms.py
class ShippingForm(forms.ModelForm):
address_one = forms.CharField(max_length=200)
address_two = forms.CharField(max_length=200)
I think what might help is to use sessions. Django will store this temporary data using session cookies. Here's the idea:
from django.forms.models import model_to_dict
def checkout(request):
form = ShippingForm(request.POST or None)
if form.is_valid():
new_shipping = form.save(commit=False)
new_shipping.customer = customer
new_shipping.order = order
request.session['partial-data'] = model_to_dict(new_shipping)
return redirect('store:checkout_shipping')
context = {"form": form}
return render(request, 'shop/checkout.html', context)
def checkout_payment(request):
# I'm guessing here is where the rest of the data
# is to be filled in. The data of the previous view
# is already here stored in the cookie
full-form-data = request.session['partial-data']
full-form-data['extra-field-1'] = 'something'
full-form-data['extra-field-2'} = 'something else'
form = ShippingForm(full-form-data or None)
if form.is_valid():
form.save()
context = {
'form': form
}
return render(request, 'shop/checkout_payment.html', context)
im using a non-model based form django.
once i get the data,i create a model object.
but when im trying to edit my post(a blog/quote based app),im not able to create a form object using the model object for a specific post.
these are my codes:
views.py:
def quote_form(request):
if request.method=='POST':
form=Quote(request.POST)
if form.is_valid():
quote=form.cleaned_data['quote']
author=form.cleaned_data['author']
popularity=form.cleaned_data['popularity']
category=form.cleaned_data['category']
p=Quote1(quote=quote, author=author, popularity=popularity, category=category)
p.save()
return redirect("quote_list")
else:
form=Quote()
return render(request,'quote/form.html',{'form':form})
def quote_edit(request, pk):
q = get_object_or_404(Quote1, pk=pk)
if request.method == "POST":
form = Quote(request.POST,instance=q)
if form.is_valid():
q = form.save(commit=False)
q.author = request.user
q.save()
return redirect('quote_detail', pk=q.pk)
#return render(request,"blog/post_detail.html",{'post':post})
else:
form = Quote(instance=q)
return render(request, 'quote/quote_edit.html', {'form': form})
models.py:
class Quote1(models.Model):
quote=models.CharField(max_length=200)
author=models.CharField(max_length=200)
popularity=models.IntegerField()
category=models.CharField(max_length=40)
forms.py:
class Quote(forms.Form):
quote=forms.CharField()
author=forms.CharField()
popularity=forms.IntegerField()
category=forms.ChoiceField(choices=[('life','life'),('happiness','happiness'),('love','love'),('truth','truth'),
('inspiration','inspiration'),('humor','humor'),('philosophy','philosophy'),('science','science')])
Try this:
def quote_edit(request, pk):
q = get_object_or_404(Quote1, pk=pk)
if request.method == "POST":
form = Quote(request.POST)
if form.is_valid():
quote=form.cleaned_data['quote']
author=form.cleaned_data['author']
popularity=form.cleaned_data['popularity']
category=form.cleaned_data['category']
q.quote=quote
q.author=author
q.popularity=popularity
q.category=category
q.save()
else:
form = Quote(initial=reauest.POST.copy())
return render(request, 'quote/quote_edit.html', {'form': form})
P.S:
Using ModelForm would have been better approach. If you can switch to ModelForm i can help there as well.
how to add an error message to be displayed if the user tried to add an entry that is already on the table
forms.py
class AddCatForm(ModelForm):
class Meta:
model = Categories
fields = ['category_name']
labels = {
'category_name': ('إسم الفئة الجديدة')
}
error_messages = {
'category_name': {
'unique': ('الفئة موجودة بالفعل')
}
}
views.py
def add_cat(request):
if request.method == "POST":
form = AddCatForm(request.POST)
if form.is_valid():
model_instance = form.save(commit=False)
model_instance.save()
return redirect('/')
else:
form = AddCatForm()
return render(request, "add_cat.html", {'form': form})
When i add an entry that is already there , it just does nothing , i want it to view an error
You might be getting an error which you will not see because of your indentation. Amend it to:
def add_cat(request):
if request.method == "POST":
form = AddCatForm(request.POST)
if form.is_valid():
model_instance = form.save(commit=False)
model_instance.save()
return redirect('/')
else:
form = AddCatForm()
return render(request, "add_cat.html", {'form': form})
I had to do something like this:
def createpost(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
titlee = form.cleaned_data['title']
contentt = form.cleaned_data['content']
post = Post(title=titlee,content=contentt,created='2012-01-06',modified='2012-01-06')
post.save()
return render_to_response('createpost.html', {"form": form}, context_instance=RequestContext(request))
Is there any way to get form value like this post = Post(form) ; post.save() I don't want to get value individually like this: titlee = form.cleaned_data['title']
Any idea?
have a look at model forms
I suggest you use model forms. To read about it, go here: https://docs.djangoproject.com/en/dev/topics/forms/modelforms/
After you create a ModelForm, your function should look similar to this:
def createpost(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/redirectsomewhereelse/')
else:
form = PostForm()
return render_to_response('createpost.html', {"form": form}, context_instance=RequestContext(request))
You can just call form.save() method if all data are valid.