Modelformset extra field pk is rendered as next instance in Django - django

I don't get why my extra fields in Modelformset are rendered with next instance pk, when form where I have already saved data is showing relevant related model instance pk.
Is this something why Django suggests to render pk field explicitly in the templates, so user to choose right related model pk? looks to me unbelievable, so please help me here...
# My views.py
from django.forms import modelformset_factory
from .models import PreImplement
# Pre Implement View
def pre_implement_view(request, pk):
moc = get_object_or_404(Moc, pk=pk)
print(moc.pk)
PreImplementFormSet = modelformset_factory(PreImplement, fields=('__all__'), can_delete=True, can_delete_extra=False)
formset = PreImplementFormSet(queryset=PreImplement.objects.filter(moc_id=moc.pk),
initial=[
{'action_item': 'go'},
{'action_item': 'nogo'},
{'action_item': 'na'},
])
formset.extra=3
queryset=PreImplement.objects.filter(moc_id=moc.pk)
print('babr', queryset)
if request.method == 'POST':
formset = PreImplementFormSet(request.POST, initial=[{'action_item': 'babrusito'},
{'action_item': 'go'},
{'action_item': 'nogo'},
{'action_item': 'na'},
])
if formset.is_valid():
formset.save()
return redirect('index')
return render(request, 'moc/moc_content_pre_implement.html', context={'moc': moc, 'formset': formset})
This is continuation of my previous SO post struggles:
Populate model instance with another model data during instance creation
Here is snapshot from my template:

Related

Create Form and update using same view in django

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})

Django filtering based on data

I'm using django to develop a web app. Right now I am trying to make a page that will initially render a blank form and all data objects of a certain model, and then once the user submits the form, will then filter the objects displayed to show those that have the desired attributes.
My form class is as follows
class AreaForm(forms.Form):
seating = forms.IntegerField(label='Seating', default=False)
whiteboards = forms.BooleanField(label='Whiteboards', default=False)
outlets = forms.IntegerField(label='Outlets', default=False)
tables = forms.IntegerField(label='Tables', default=False)
And the view for this page thus far is
def search(request):
if request.method == 'GET':
form = NameForm(request.POST)
if form.is_valid():
# do filtering logic here somehow
return render(request, 'SpotMe/search.html', {'form': form}) # ????
else:
return render(request, 'SpotMe/search.html', {}) # ????
I'm as of yet unsure how to implement the templates page. Am I headed in completely the wrong direction?
To show the form empty and do some logic when user post data, you need to pass the form to the template and it'll render empty if there is not post data.
view.py
def search(request):
form = AreaForm(request.POST or None)
if request.method == 'POST':
if form.is_valid():
# do filtering logic here somehow
return render(request, 'SpotMe/search.html', {'form': form})
search.html
.....
{{ form.as_p }}
.....
Based on your reply to my question below your original post, this is an easy solution that will show all data object of a model, and then you can use an input and submit it from a template to filter the results on the same page.
Note: Substitute MODEL for your actually model name. You don't need a form if you are looking to filter results (it is an extra, unnecessary step).
def search(request):
if request.method == 'GET':
if request.GET.get('seating_no'):
seating_no = request.GET.get('seating_no')
queryset = MODEL.objects.filter(seating=seating_no)
else:
queryset = MODEL.objects.all()
return render(request, 'SpotMe/search.html', {'queryset': queryset})
and in your SpotMe/search.html you can have a <form><input name="seating_no" /></form> and submit button that will lead to the same URL, and make the input name(s) into whatever you want to capture via request.GET.get()

Django Foreign Key to a yet-to-be created Model

I'm wondering if there's way in Django to associate a model to another, yet-to-be created model with a foreign key. Both model would be created using the same ModelForm in the same HTML page.
e.g.
class Team(models.Model):
name = forms.CharField
...
class Player(models.Model):
name = forms.CharField()
key = forms.ForeignKey(Team)
...
Basically, I'm wondering if both these models can be put in the same <form>...</form> in one HTML page.
a foreign key is a reference to the primary key of the referenced model, so the target needs to exist. you need to save the first form, and then update the reference on the second one before saving. to get a model instance from a form without saving to the db, you can use
instance = form.save(commit=False)
you then need to save the instance yourself
instance.save()
and if you are using many-to-many fields, you need to look at save_m2m
You may want to check the documentation for inlineformset, it allows to edit the related objects of a model in the same view, also see formsets.
def manage_teams(request, team_id):
team = Player.objects.get(pk=team_id)
PlayerInlineFormSet = inlineformset_factory(Player, Team)
if request.method == "POST":
formset = PlayerInlineFormSet(request.POST, request.FILES, instance=team)
if formset.is_valid():
formset.save()
# Do something.
else:
formset = PlayerInlineFormSet(instance=team)
return render_to_response("manage_teams.html", {
"formset": formset,
})
Here goes another example:
from django.forms.models import inlineformset_factory
def new_team(request):
PlayerInlineFormSet = inlineformset_factory(Team, Player)
team= Team()
if request.method == 'POST':
form_team = TeamForm(request.POST, request.FILES, instance= team, prefix= 'team')
form_player = PlayerInlineFormSet(request.POST, request.FILES, instance= team, prefix= 'players')
if form_team.is_valid() and form_player.is_valid():
form_team.save()
form_player.save()
return HttpResponseRedirect('/teams/%s/' % team.slug)
else:
form_team = TeamForm( instance= team, prefix= 'team')
form_player = PlayerInlineFormSet(instance= team, prefix= 'players')
return render_to_response('Teams/new_team.html', {'form_team': form_team, 'form_player':form_player}, context_instance=RequestContext(request))

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("/")

get_or_create for two related models and formsets

I have two models related by a ForeignKey and inline formsets:
class Balanta(models.Model):
data = models.DateField()
class Meta:
ordering=['data']
verbose_name_plural="Balante"
def __unicode__(self):
return unicode(self.data)
class Conturi(models.Model):
cont=models.PositiveIntegerField()
cont_debit=models.DecimalField(default=0, max_digits=30, decimal_places=2)
cont_credit=models.DecimalField(default=0, max_digits=30, decimal_places=2)
balanta = models.ForeignKey(Balanta)
class Meta:
#oredering=['cont']
verbose_name_plural="Conturi"
def __unicode__(self):
return unicode(self.cont)
I have formsets based on the two models. After i submit the form, if the "Balanta part of the form" exists, the form shouldn't do anything an dif it doesn't exists it should save it in the database (I managed to do this).
Now what i want to do on the "Conturi part of the form" is to see if the cont is on the database, the form should update it with the value in the input box and if it is not in the db, the form should create it and the corresponding cont_debit or cont_credit. The current state gives me: get_or_create() argument after ** must be a mapping, not list.
I think is something bigger then mt simple/newbie way to solve the problem.
Here is the view:
*LAST UPDATE*
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render_to_response
from django.template import RequestContext
from sitfin.models import Balanta, Conturi
from sitfin.forms import BalantaForm , ConturiForm
from django.forms.models import inlineformset_factory
def balanta_introducere(request):
balanta=Balanta()
ConturiInlineFormSet=inlineformset_factory(Balanta, Conturi, extra=3)
if request.method=='POST':
balanta_form=BalantaForm(request.POST, instance=balanta)
if balanta_form.is_valid():
balanta, created=Balanta.objects.get_or_create(**balanta_form.cleaned_data)
#return HttpResponseRedirect('/sitfin/balantaok')
formset=ConturiInlineFormSet(request.POST, request.FILES, instance=balanta)
if formset.is_valid():
#formset.save()
for form in formset:
data={
'cont':form.cleaned_data.get('cont'),
'cont_debit':form.cleaned_data.get('cont_debit'),
'cont_credit':form.cleaned_data.get('cont_credit'),
'balanta':form.cleaned_data.get('balanta'),
}
try:
c=Conturi()#.objects.get(cont=data['cont'])
except Conturi.DoesNotExist:
cont_complete,created=Conturi.objects.get_or_create(**data)
else:
for form in formset:
new_data={
'cont':form.cleaned_data.get('cont'),
'cont_debit':form.cleaned_data.get('cont_debit'),
'cont_credit':form.cleaned_data.get('cont_credit'),
'balanta':form.cleaned_data.get('balanta'),
}
cont_complete,created=Conturi.objects.get_or_create(**new_data)
else:
balanta_form=BalantaForm()
formset=ConturiInlineFormSet(instance=balanta)
return render_to_response('sitfin/balanta_introducere.html',{'balanta_form':balanta_form,'formset':formset}, context_instance=RequestContext(request))
formset.cleaned_data is a list of the individual forms cleaned_data. You need to iterate over formset and create a Conturi object for each:
if formset.is_valid():
for form in formset:
cont, created=Conturi.objects.get_or_create(**form.cleaned_data)
UPDATE:
To protect yourself from errors resulting from additional form fields that do not correspond to model attributes, it's better to explicitly provide the arguments to get_or_create instead of using **form.cleaned_data.
if formset.is_valid():
for form in formset:
data = {
'cont': form.cleaned_data.get('cont'),
'cont_debit': form.cleaned_data.get('cont_debit'),
'cont_credit': form.cleaned_data.get('cont_credit'),
'balanta': form.cleaned_data.get('balanta'),
}
cont, created = Conturi.objects.get_or_create(**data)
It's basically the same procedure, but now you know exactly which arguments are being passed to get_or_create.