Prepopulating dynamically added formsets in edit view - django

I have different models in my app, the main model having multiple instances of some other models.
models.py:
class Person(models.Model):
...
class Pet(models.Model):
owner = models.ForeignKey(Person)
...
forms.py:
class PersonForm(forms.ModelForm):
class Meta:
model = Person
PetFormSet = inlineformset_factory(Person, Pet, extra = 1)
views.py:
def add_template(request):
person_form = PersonForm(prefix = 'person_form')
pet_form = PetFormSet(instance = Person(), prefix = 'pet_form')
... # check is_valid(), render when no POST data is present, etc.
The point is the addition works perfectly, storing each instance in the corresponding database table, etc. I use jquery.formset-1.2.js to manage the dynamical addition-deletion of forms in the "add.html".
But I later want to edit the stored info through a view, i.e., load the data from the object I pass in the request, and render the formsets with the data obtained from the database (if there are 3 pets related to the person being editted, display 3 form instances of "Pet" with their value displayed).
I would like to also add new Pets and remove existing ones, as well as changing existing field values.
I have tried with querysets in the FormSet creation, but it doesn't display anything.
Any idea how to ease this issue? Maybe using an app for an easier formset management?
Thank you

I'm not sure if I understand your problem correctly. If you want to display all Pets that belong to the Person:
def show_pets(request, person_id=None):
person = get_object_or_404(Person.objects.select_related(), id=person_id)
if request.method == 'POST':
person_form = PersonForm(request.POST, instance=person)
pet_formset = PetFormSet(request.POST, instance=person)
# Rest of your code here
else:
person_form = PersonForm(instance=person)
pet_formset = PetFormSet(instance=person)
return render_to_response('your_template.html', {'person_form': person_form,
'pet_formset': pet_formset)})
Then you just need to render both forms in your template and add add and delete functionality

Related

Django - modelform + model property

I am trying to solve one issue about saving data in db.
This is an example how I think of it:
class MyModel(models.Model):
id = models.AutoField(primary_key=True)
fieldX = models.SomeFieldType()
#property:
def foo(self):
return self._foo
#foo.setter
def foo(self, var):
self._foo=var
class MyModelForm(models.Modelform):
class Meta:
model = models.MyModel
fields = '__all__'
The thing is I have dict that I am passing to this form (so I am not using view or any provided interaction with user directly. In dict I have some fields and what I want to do is one field that is passed to use it as model property but I do not want it to be saved in db.
So I tried something like:
form = MyModelForm(data_dict)
if form.is_valid():
form.foo = data_dict['data_for_property_not_db']
form.save()
Form does not know that my model has this property.
So basiclly what I want is to write some parts of my data_dict normaly to form and db as always ->works fine
and then I want some data_info pass to that property and use it somewhere in save() as needed without saving it to db itself.
Can you help me with this?

Django - multiple models in one page

I have a model that looks like this:
models.py
class BHA_List(models.Model):
well = models.ForeignKey(WellInfo, 'CASCADE', related_name='bha_list')
bha_number = models.CharField(max_length=100)
class BHA_Drill_Bit(models.Model):
bha_number = models.ForeignKey(BHA_List, 'CASCADE', related_name='bha_drill_bit')
bit_type = models.CharField(max_length=111)
class BHA_overall(models.Model):
bha_number = models.ForeignKey(BHA_List, 'CASCADE', related_name='bha_overall')
drill_str_name = models.CharField(max_length=111)
class BHA_Motor(models.Model):
bha_number = models.ForeignKey(BHA_List, 'CASCADE', related_name='bha_drill_bit')
motor_type = models.CharField(max_length=111)
BHA_List is a parent model, and the rest are child models related by ForeignKey. The screenshot is the page I want to create
So, I want to generate a base page using one of the instances in model = BHA_List. In this page, I want to edit model instances that are related to BHA_List by ForeignKey relationship.
I currently have a view that looks like this, but its wrong:
class BHA_UpdateView(UpdateView):
model = BHA_List
pk_url_kwarg = 'pk_alt'
form_class = BHA_overall_Form
By setting model = BHA_List, I was able to get one of the instances in BHA_List, and generate url from it. Right now my views correctly return one of the instances in BHA_List: BHA 1
I attempted to edit child models by setting form_class = BHA_overall_Form. But this doesn't do anything, though it displayed form fields on the user side. After editing and clicking Submit button, the changes are not saved in DB. Someone pointed out that this is because my model in UpdateView and form does not match, as I set model = BHA_List, but form_class = BHA_overall_form.
How can I resolve this issue? Someone else also pointed out using multiple views, but I don't really know how to do it, as I'm very new to Django. Any help will be greatly appreciated. Thanks!
Just so that you know. UpdateView can be used if you want to update a single row in one table. When you set model = BHA_LIST you are saying Django. Hey, Django I want to update this model so render me a form with the fields from this table. You can do this by just setting fields attr on the model or use a form like you did which will customize which fields are shown. Now the good thing about allowing to set our own form is. Though we create a modelForm we can also add extra fields inside it. Now your BHAOverallForm should look like this to accommodate all the fields you need.
forms.py
class BHAOverallForm(forms.ModelForm):
well = models.ForeignKey(WellInfo, 'CASCADE', related_name='bha_list')
bha_number = models.CharField(max_length=100)
bit_type = models.CharField(max_length=111
drill_str_name = models.CharField(max_length=111)
motor_type = models.CharField(max_length=111)
class Meta:
model = BHAList
you can use this form inside your form like you do now. You can also add clean_field to add validations. Now coming to the update part. your views should look like this
views.py
class BHAUpdateView(UpdateView):
model = BHAList
form_class = BHAOverallForm
def form_valid(self, form):
super(BHAUpdateView, self).form_valid(form) # save BHAList to the DB
bha_list = form.instance
bha_drill_bit = bha_list.bhadrillbit_set.first() # assuming you have only one drill_bit per list, if you need more modify your question accordingly.
bha_drill_bit.bit_type = form.cleaned_data.get("bit_type)
bha_drill_bit.save()
# you can do the same for other models as well.

How can I achieve one single form, pre-populated with data from two different Profile instances?

I have a model, Pair, and another model Profile.
An instance of the model Pair will be made with a form that draws from two different Profile instances. So, how do I pre-populate a single form with bits of information from two Profiles?
Two models: Profile & Pair:
class Profile(models.Model):
...
favorites = models.CharField(max_length=150)
class Pair(models.Model):
requester = models.ForeignKey(Profile)
accepter = models.ForeignKey(Profile)
requester_favorite = models.CharField(max_length=50)
accepter_favorite = models.CharField(max_length=50)
The form so far:
class PairRequestForm(forms.Form):
your_favorites = forms.CharField(max_length=50)
partners_favorites = forms.CharField(max_length=50)
Code Explanation: The way it works is, a user(requester) will request the initiation of a pair with the PairRequestForm to the potential accepter.
The form should be pre-populated with the "favorite" of each individual user.
I'm not sure how to wire up the views.py since I need to obtain two objects.
class PairRequestView(FormView):
form_class = PairRequestForm
template_name = 'profile/pair_request.html'
success_url = "/"
def is_valid(self, form):
return
note: The pair form must be pre-populated with current information from the Profile. However, the form will not update any old information(will not save() any Profiles)--it will simply create a new Pair instance.
Assuming that the accepters page is visited at something like /profiles/2, you can capture the id of the accepter from the url, as you normally would.
Couple of things - you don't need to save the favourites into the Pair model, since for any use of the Pair model, you can just access them by doing
`p = Pair.objects.get(id=1) #or whatever query
p.requester.favourites
> Some of my favourites
p.accepter.favourites
> Some of your favourites.
If you, the proposer, are id=1, and visiting mine, the accepter's page (id=2), then you can populate the form - (I've done this in a very long manner for clarity)
accepterobj = Profile.objects.get(id=id_from_url_for_page)
proposerobj = Profile.objects.get(id=request.user.id)
form = PairRequestForm(accepter=accepterobj,
proposer=proposerobj,
accepter_favourites=accepterobj.favourites,
proposerobj_favourites=proposerobj.favourites)
in your CBV, you can do the queries above by overriding the get_initial method on your PairRequestView.
def get_initial(self):
"""
Returns the initial data to use for forms on this view.
"""
initial = super(PairRequestView, self).get_initial()
initial['proposer'] = self.request.user
...etc
return initial

Django model form and objects database id

I have a complex django object, which has properties of other class types. This gets like this:
class Order:
contractor - type Person
some other fields....
In my form I'd like to be able to either choose existing Person object from the dropdown or add a new one with a form. I've managed to create forms and appropriate workflow, but the problem is with saving the Order itself, I simply can't get the id of a saved Person instance. I'm doing sth like this:
def make_order(request):
if request.method == 'POST':
parameters = copy.copy(request.POST)
contractor_form = ContractorForm(parameters)
if contractor_form.is_valid():
contractor_form.save()
parameters['contractor'] = ???
form = OrderForm(parameters)
if form.is_valid():
form.save()
return HttpResponseRedirect('/orders/')
else:
form = OrderForm()
contractor_form = ContractorForm()
return render_to_response('orders/make_order.html', {'order_form' : form, 'contractor_form' : contractor_form})
So, if POST request reaches this method I first check if ContractorForm have been filled - I assume that if the form is valid, it is meant to be used. If yes, than I save it and would like to assign the saved object's database id to appropriate field for OrderForm to find it.
All my forms are ModelForms.
The questions are:
Are there better ways to do this? (choose from dropdown or add in place) - better or more pythonic ;-)
How can I get saved objects id when using ModelForms?
Edited
My ContractorForm is:
class ContractorForm(ModelForm):
class Meta:
model = Contractor
Nothing fancy.
save() should return the newly created instance.
if contractor_form.is_valid():
instance = contractor_form.save()
parameters['contractor'] = instance
where id would be instance.id, or even better instance.pk.
pk vs. id:
Regardless of whether you define a
primary key field yourself, or let
Django supply one for you, each model
will have a property called pk. It
behaves like a normal attribute on the
model, but is actually an alias for
whichever attribute is the primary key
field for the model. You can read and
set this value, just as you would for
any other attribute, and it will
update the correct field in the model.
Follow-up on comment:
Well it does work by default, so there must be something else wrong.
models.py
class Category(models.Model):
name = models.CharField(max_length=70)
slug = models.SlugField()
forms.py
from django import forms
from models import Category
class MyModelForm(forms.ModelForm):
class Meta:
model = Category
Test in shell:
In [3]: from katalog.forms import MyModelForm
In [4]: data = {'name':'Test', 'slug':'test'}
In [5]: form = MyModelForm(data)
In [6]: instance = form.save()
In [7]: instance
Out[7]: <Category: Test>
In [8]: instance.id
Out[8]: 5L

How to change multiple select field to multiple input fields for a many-to-many case?

When I display the ToolBoxEditForm it uses a multiple select field.
But what I want is a form that lets the user edit each tool he has in the toolbox as a text field. I cant figure out how to do this with the many-to-many field.
class Tool(models.Model):
tool_name = models.CharField(unique=True, max_length=200)
......
class ToolBox(models.Model):
tools = models.ManyToManyField(Tool,max_length=300)
class ToolBoxEditForm (ModelForm):
tools = ???
class Meta:
model = ToolBox
exclude = ('user', 'popularity',)
Sexiest Solution
You could use one of the jquery autocommplete tools described here: Facebook style JQuery autocomplete plugin
Then in the form:
class ToolBoxEditForm (ModelForm):
tools = forms.CharField(widget=forms.Textarea, required=False)
def clean_tools(self):
tool_data = self.cleaned_data.get('tools',None)
tools = []
#here, a comma is used a delim, so it's not allowed in the tool name.
for td in tool_data.split(','):
t, _ = Tool.objects.get_or_create(name=td)
tools.append(t)
return tools
class Meta:
model = ToolBox
exclude = ('user', 'popularity',)
You'd have to figure out how to modify the JavaScript so that new items could be entered (i.e. not just ones already in the database).
Alternative Solution
This is sort of what the inline formsets were created for, so Narendra's solution will work.
Something like:
from django.forms.models import inlineformset_factory
def manage_toolbox(request, toolbox_id):
toolbox = Toolbox.objects.get(pk=toolbox_id)
ToolInlineFormSet = inlineformset_factory(Toolbox, Tool)
if request.method == "POST":
formset = ToolInlineFormSet(request.POST, request.FILES, instance=toolbox)
if formset.is_valid():
formset.save()
# Do something.
else:
formset = ToolInlineFormSet(instance=toolbox)
return render_to_response("manage_toolbox.html", {
"formset": formset,
})
Not that this form is only for editing the items within the toolbox. If you want the user to be able to edit other aspects of the Toolbox -- say, its name or description -- you would create a separate form and output both of them inside the same <form></form> tags.
I'm not sure, since not tested it, but here is the logic goes.
Create formset for ToolBoxEditForm via formset_factory
Change tool_name field type to CharField
Set number of rows in formset to precisely the number of Tool objects available in db
Pass in initials to formset constructor to fill in tool_name textboxes.
# TODO: following data must be generated dynamically
initial_data=[{'tool_name': u'first_tool_name', },
{'tool_name': u'second_tool_name',}]
formset = ToolBoxFormSet(extra=0, initial=initial_data)
Not sure about the validation part. Here we are placing tool_name as value for textbox. During validation, Form may expect ID (because it is supposed to be listbox). But, you can handle that also.
for more info about formset refer: http://docs.djangoproject.com/en/dev/topics/forms/formsets/