update django choice field with database results - django

I am developing an application using django where the UI needs to be updated when user interacts with it. For instance I have a Drop down field where the user selects a drink and submits it then based on that a dropdown with the places that drink is available, price and quantity at each place needs to be displayed. The user will then further submit the form for second process.
From my understanding the Forms in django are pre-defined and I am not able to think of a way using which I could achieve this.
What I could come up was defining a regular form class
class dform(forms.Form):
SOURCES_CHOICES = (
(A, 'A'),
(E, 'E'),
)
drink = forms.ChoiceField(choices = SOURCES_CHOICES)
location = forms.ChoiceField(choices = **GET THIS FROM DATABASE**)
quantity = forms.ChoiceField(choices = **GET THIS FROM DATABASE**)
.
.
.
My view is like,
def getdrink():
if request.method == 'POST':
#code for handling form
drink = dform.cleaned_data['drink']
#code to get values from database
I have no idea how to generate or populate or append the values i get from the database to the choicefield in my form. I did try looking up on SO but none of the solutions here explained properly how to do it. Also, due to certain requirements I am not using the models. So my database is not at all related to the models.
I am at a total loss Please help me out

class MyForm(forms.Form):
my_choice_field = forms.ChoiceField(choices=MY_CHOICES)
So if you want the values to be dynamic(or dependent of some logic) you can simply modify your code to something like this:
either
def get_my_choices():
# you place some logic here
return choices_list
class MyForm(forms.Form):
my_choice_field = forms.ChoiceField(choices=get_my_choices())
or
User_list = [ #place logic here]
class MyForm(forms.Form):
my_choice_field = forms.ChoiceField(choices=get_my_choices())
but once database value is updated, new data value will be popoulated only on restart of server.
So write a function like this in forms:
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['my_choice_field'] = forms.ChoiceField( choices=get_my_choices() )
or in place of the get_my_choices u can ad the USER_LIST too.

If you have models for location and quantity, a ModelChoiceField should work:
class dform(forms.Form):
location = forms.ModelChoiceField(queryset = Location.objects.all())
Otherwise, you'll need to query the database directly, for example:
class dform(forms.Form):
location = forms.ChoiceField(choices = get_location_choices())
# elsewhere
from django.db import connection
def get_location_choices():
cursor = connection.cursor()
cursor.execute("select location_id, name from location_table")
return cursor.fetchall()
The SQL query to use here depends on your database engine and table schema.

I think that, based on my understanding of your question, the best solution would be to include JSON objects with your form and load these using jQuery instead of submitting the form over and over. Included in your form, you should add something like:
class MyForm(forms.Form):
CHOICE_DICT = {
'choice_1': [
'option_1',
'option_2',
],
etc...
Then you should include form.CHOICE_DICT in your context, load that with jQuery, and render it depending on changes to other fields.

Related

Show fields based on the value of another field in flask admin

I have database tables with a 'TYPE' column and many other fields. In many cases, certain column values are null based on the value of 'TYPE'.
E.g.
if I have a product table , with TYPE having either 'car' or 'helicopter'. The columns are:
vertical speed, horizontal speed, and horn amplitude.
In the case of 'car' types, vertical speed should always be null , and in the case of 'helicopter' , horn amplitude should always be null.
In flask admin, is there any way to hide the fields from being submitted based on the currently selected TYPE's value?
It is fine if it is a UI level change (i.e. no backend validation is required for security/consistency purposes).
In my real life scenario, there are over 10 columns with 5+ being null in cases, so it would be very helpful if those fields can be removed in the UI (since it makes the form very long and prone to errors).
I am using flask sqlalchemy as the backend for my flask admin.
Fun question. Here is a working solution. So basically you have product types, and each type has certain valid attributes (e.g. car and honking loudness). You can also have general attributes irregardless of the type, e.g. the name of each product.
On the form_prefill, you check what fields are valid for the product type. Then you throw away the invalid fields from the form, and return the form again. It's actually pretty straightforward.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import flask_admin as admin
from flask_admin.contrib import sqla
app = Flask(__name__)
app.secret_key = 'arstt'
db = SQLAlchemy(app)
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
type = db.Column(db.String)
name = db.Column(db.String)
vertical_speed = db.Column(db.Integer)
rotor_rpm = db.Column(db.Integer)
honking_loudness = db.Column(db.Integer)
def __str__(self):
return "{}".format(self.name)
class ProductView(sqla.ModelView):
general_product_attributes = ['name']
product_attributes_per_product_type_dict = {
'driving': ['honking_loudness'],
'flying': ['rotor_rpm', 'vertical_speed']
}
def on_form_prefill(self, form, id):
product = self.get_one(id)
form_attributes = self.general_product_attributes + self.product_attributes_per_product_type_dict[product.type]
for field in list(form):
if field.name not in form_attributes:
delattr(form, field.name)
return form
db.create_all()
admin = admin.Admin(app, name='Example: SQLAlchemy', template_mode='bootstrap3')
admin.add_view(ProductView(Product, db.session))
helicopter = Product(type='flying', name='helicopter1', vertical_speed=99)
car = Product(type='driving', name='car2', honking_loudness=33)
db.session.add(helicopter)
db.session.add(car)
db.session.commit()
Note that this only works for the edit form, all attribtutes are still being displayed on the create form because it is not certain yet what type an product will be.
You can override create_form for create form and on_prefill_form for edit form.
in this functions you can pass some parameters to fields using form_widget_args
def create_form(self):
form = super(JobsView, self).create_form()
# kw = decide which fields to show or hide
# kw["vertical_speed"]["class"] = "hide"
self.form_widget_args = kw
return form
def on_form_prefill(self, form, id):
# kw = decide which fields to show or hide
# kw["vertical_speed"]["class"] = "hide"
self.form_widget_args = kw

How do I accept multiple choices for a Charfield from CheckboxSelectMultiple widget in Django?

I'm trying to build a form in Django 1.11, where I have a set of checkboxes on a watch list to allow a user to set multiple alerts on an item they want to receive notifications on later. However, I'm not sure how to represent multiple options on a field like this.
Here's my model code:
class Watchlist(models.Model):
CLOSES_IN_2_WEEKS, CLOSES_IN_1_WEEKS, CLOSES_IN_3_DAYS, CLOSES_TOMORROW = (
"CLOSES_IN_2_WEEKS",
"CLOSES_IN_1_WEEKS",
"CLOSES_IN_3_DAYS",
"CLOSES_TOMORROW"
)
ALERT_OPTIONS = (
(CLOSES_IN_2_WEEKS, "Closes in 2 weeks",),
(CLOSES_IN_1_WEEKS, "Closes in 1 weeks",),
(CLOSES_IN_3_DAYS, "Closes in 3 days",),
(CLOSES_TOMORROW, "Closes tomorrow",),
)
# I want to store more than one option
alert_options = models.CharField(max_length=255, blank=True)
def save(self):
"""
If this is submitted create 1 alert:
"CLOSES_IN_1_WEEKS"
If this submitted, create 3 alerts:
"CLOSES_IN_2_WEEKS",
"CLOSES_IN_1_WEEKS",
"CLOSES_IN_3_DAYS",
"""
# split the submitted text values, to create them
# yes, this should probably be a formset. I wasn't sure how I'd
# handle the logic of showing 4 optional alerts, and only creating models
# on the form submission, and remembering to delete them when a user
# unchecked the choices in the form below
And here's the form I'm using, below. I'm hooking into the __init__ method to pre-populate the form with possible choices.
class WatchlistForm(forms.ModelForm):
alert_options = forms.ChoiceField(
choices=[],
label="Alert Options",
required=False,
widget=forms.CheckboxSelectMultiple(),
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["alert_options"].choices = WatchlistForm.ALERT_OPTIONS
def clean_alert_options(self):
# I'm dropping into ipython to look around here
import ipdb; ipdb.set_trace()
return data["alert_options"]
class Meta:
model = WatchlistForm
fields = ("alert_options")
This currently works fine for a single checked checkbox, but once I have more than one, only the last choice is selected, and I can't quite figure out how to access it.
How can I capture all the choices here, rather than just the one?
I'm aware I probably should be using a formset here. The thing is it wasn't obvious to me how to create a set of pre-populated formset options to represent some active and some inactive alert choices.
Using a test to show what I'm going for
If it helps, I'm trying to save the information so it would be stored like so - I've added some pseudo code based on me using pytest in a test suite.
def test_form_accepts_multiple_alert_values(self, db, verified_user):
form_data = {
"user_id": verified_user.id,
"alert_options": "CLOSES_IN_2_WEEKS CLOSES_IN_3_DAYS",
}
submission = forms.WatchlistForm(form_data)
instance = submission.save()
assert instance.alert_options == "CLOSES_IN_2_WEEKS CLOSES_IN_3_DAYS",

django model search form

Firstly, I did my homework and looked around before posting! My question seems like a very basic thing that must’ve been covered before.
I'm now looking at Django-filter as a potential solution, but would like some advice on if this is the right way to go and if there any other solutions.
I have a Django app wit 10 models, each model has a few fields. Most fields are ChoiceField that users populate using forms with the default select widget. There is a separate form for each model.
I want to create a separate form for each model (in separate views) that users will use to search the database. The search form will contain only drop-down boxes (the select widgets) with the same choices as the forms used to populate the database with the addition of the “any” option.
I know how to use .object.filter(), however the “any” option would correspond to not include specific fields in the filter and I'm not sure how to add model fields to the filter based on users’ selection
I briefly looked at Haystack as an option but it seems to be made for full text search rather than “model filed search” I'm after.
Sample model (simplified):
class Property():
TYPE_CHOICES = (‘apartment’, ‘house’, ‘flat’)
type = charfield(choices=TYPE_CHOICES)
LOC_CHOICES = (‘Brussels’, ‘London’, ‘Dublin’, ‘Paris’)
location = charfield(choices=LOC_CHOICES)
price = PostivieInteger()
Users can select only “type”, only “location” or both (not making selection is equal to ANY) in which case I end up with 3 different filters:
Property.objects.filter(type=’apartment’)
Property.objects.filter(location=’Dublin’)
Property.objects.filter(type=’apartment’, location=’Dublin’)
The main question: django-filter the best option?
Question 1: what’s the best option of accomplishing this overall?
Question 2: how do I add model fields to the filter based on user’s form selection?
Question 3: how do I do the filter based on user selection? (I know how to use .filter(price_lt=).exclude(price_gt=) but again how do I do it dynamically based on selection as “ANY” would mean this is not included in the query)
I had a similar case like yours (real estate project), I ended up with the following approach, you can refine this to your needs...I removed select_related and prefetch_related models for easier reading
properties/forms.py:
class SearchPropertyForm(forms.Form):
property_type = forms.ModelChoiceField(label=_("Property Type"), queryset=HouseType.objects.all(),widget=forms.Select(attrs={'class':'form-control input-sm'}))
location = forms.ModelChoiceField(label=_('Location'), queryset=HouseLocation.objects.all(), widget=forms.Select(attrs={'class':'form-control input-sm'}))
Then in the properties/views.py
# Create a Mixin to inject the search form in our context
class SeachPropertyMixin(object):
def get_context_data(self, **kwargs):
context = super(SeachPropertyMixin, self).get_context_data(**kwargs)
context['search_property_form'] = SearchPropertyForm()
return context
In your actual view (I apply the search form as a sidebar element in my detailview only:
# Use Class Based views, saves you a great deal of repeating code...
class PropertyView(SeachPropertyMixin,DetailView):
template_name = 'properties/view.html'
context_object_name = 'house'
...
queryset = HouseModel.objects.select_related(...).prefetch_related(...).filter(flag_active=True, flag_status='a')
Finally your search result view (this is performed as GET request, since we are not altering any data in our DB, we stick to the GET method):
# Search results should return a ListView, here is how we implement it:
class PropertySearchResultView(ListView):
template_name = "properties/propertysearchresults.html"
context_object_name = 'houses'
paginate_by = 6
queryset = HouseModel.objects.select_related(...).prefetch_related(...).order_by('-sale_price').filter(flag_active=True, flag_status='a')
def get_queryset(self):
qs = super(PropertySearchResultView,self).get_queryset()
property_type = self.request.GET.get('property_type')
location = self.request.GET.get('location')
'''
Start Chaining the filters based on the input, this way if the user has not
selected a filter it wont be used.
'''
if property_type != '' and property_type is not None:
qs = qs.filter(housetype=property_type)
if location != '' and location is not None:
qs = qs.filter(location=location)
return qs
def get_context_data(self, **kwargs):
context = super(PropertySearchResultView, self).get_context_data()
'''
Add the current request to the context
'''
context['current_request'] = self.request.META['QUERY_STRING']
return context
Your solution works. I've modified it and I'm not using ModelChoiceField but the standard form.ChoiceField. The reason for that is that I wanted to add option "Any". My "if" statements look like:
if locality != 'Any Locality':
qs = qs.filter(locality=locality)
if property_type != 'Any Type':
qs = qs.filter(property_type=property_type)
if int(price_min) != 0:
qs = qs.filter(price__gte=price_min)
if int(price_max) != 0:
qs = qs.filter(price__lte=price_max)
if bedrooms != 'Any Number':
qs = qs.filter(bedrooms=bedrooms)
And so on....
This does the job, however it seems like an ugly and hacky solution to a simple problem. I would think is a common use case. I feel there should be a cleaner solution...
I've tried the django-filter. It is close to doing what I want but I couldn't add the "Any" choice and it filters inline rather than returning. It should do with some modifications.
Cheers

django forms: editing multiple sets of related objects in a single form

I'm trying to do something that should be very common: add/edit a bunch of related models in a single form. For example:
Visitor Details:
Select destinations and activities:
Miami [] - swimming [], clubbing [], sunbathing[]
Cancun [] - swimming [], clubbing [], sunbathing[]
My models are Visitor, Destination and Activity, with Visitor having a ManyToMany field into Destination through an intermediary model, VisitorDestination, which has the details of the activities to be done on the destination (in itself a ManyToMany field into Activity).
Visitor ---->(M2M though VisitorDestination) -------------> Destination
|
activities ---->(M2M)---> Activity
Note that I don't want to enter new destination / activity values, just choose from those available in the db (but that's a perfectly legit use of M2M fields right?)
To me this looks like an extremely common situation (a many to many relation with additional details which are a FK or M2M field into some other model), and this looks like the most sensible modelling, but please correct me if I'm wrong.
I've spent a few days searching Django docs / SO / googling but haven't been able to work out how to deal with this. I tried several approaches:
Custom Model form for Visitor, where I add multiple choice fields for Destination and Activity. That works ok if Destination and Activity could be selected independently, but here they are correlated, ie I want to choose one or several activities for each destination
Using inlineformset_factory to generate the set of destination / activities forms, with inlineformset_factory(Destination, Visitor). This breaks, because Visitor has a M2M relation to Destination, rather than a FK.
Customizing a plain formset, using formset_factory, eg DestinationActivityFormSet = formset_factory(DestinationActivityForm, extra=2). But how to design DestinationActivityForm? I haven't explored this enough, but it doesn't look very promising: I don't want to type in the destination and a list of activities, I want a list of checkboxes with the labels set to the destination / activities I want to select, but the formset_factory would return a list of forms with identical labels.
I'm a complete newbie with django so maybe the solution is obvious, but I find that the documentation in this area is very weak - if anyone has some pointers to examples of use for forms / formsets that would be also helpful
thanks!
In the end I opted for processing multiple forms within the same view, a Visitor model form for the visitor details, then a list of custom forms for each of the destinations.
Processing multiple forms in the same view turned out to be simple enough (at least in this case, where there were no cross-field validation issues).
I'm still surprised there is no built-in support for many to many relationships with an intermediary model, and looking around in the web I found no direct reference to it. I'll post the code in case it helps anyone.
First the custom forms:
class VisitorForm(ModelForm):
class Meta:
model = Visitor
exclude = ['destinations']
class VisitorDestinationForm(Form):
visited = forms.BooleanField(required=False)
activities = forms.MultipleChoiceField(choices = [(obj.pk, obj.name) for obj in Activity.objects.all()], required=False,
widget = CheckboxSelectMultipleInline(attrs={'style' : 'display:inline'}))
def __init__(self, visitor, destination, visited, *args, **kwargs):
super(VisitorDestinationForm, self).__init__(*args, **kwargs)
self.destination = destination
self.fields['visited'].initial = visited
self.fields['visited'].label= destination.destination
# load initial choices for activities
activities_initial = []
try:
visitorDestination_entry = VisitorDestination.objects.get(visitor=visitor, destination=destination)
activities = visitorDestination_entry.activities.all()
for activity in Activity.objects.all():
if activity in activities:
activities_initial.append(activity.pk)
except VisitorDestination.DoesNotExist:
pass
self.fields['activities'].initial = activities_initial
I customize each form by passing a Visitor and Destination objects (and a 'visited' flag which is calculated outside for convenience)
I use a boolean field to allow the user to select each destination. The field is called 'visited', however I set the label to the destination so it gets nicely displayed.
The activities get handled by the usual MultipleChoiceField (I used I customized widget to get the checkboxes to display on a table, pretty simple but can post it if somebody needs that)
Then the view code:
def edit_visitor(request, pk):
visitor_obj = Visitor.objects.get(pk=pk)
visitorDestinations = visitor_obj.destinations.all()
if request.method == 'POST':
visitorForm = VisitorForm(request.POST, instance=visitor_obj)
# set up the visitor destination forms
destinationForms = []
for destination in Destination.objects.all():
visited = destination in visitorDestinations
destinationForms.append(VisitorDestinationForm(visitor_obj, destination, visited, request.POST, prefix=destination.destination))
if visitorForm.is_valid() and all([form.is_valid() for form in destinationForms]):
visitor_obj = visitorForm.save()
# clear any existing entries,
visitor_obj.destinations.clear()
for form in destinationForms:
if form.cleaned_data['visited']:
visitorDestination_entry = VisitorDestination(visitor = visitor_obj, destination=form.destination)
visitorDestination_entry.save()
for activity_pk in form.cleaned_data['activities']:
activity = Activity.objects.get(pk=activity_pk)
visitorDestination_entry.activities.add(activity)
print 'activities: %s' % visitorDestination_entry.activities.all()
visitorDestination_entry.save()
success_url = reverse('visitor_detail', kwargs={'pk' : visitor_obj.pk})
return HttpResponseRedirect(success_url)
else:
visitorForm = VisitorForm(instance=visitor_obj)
# set up the visitor destination forms
destinationForms = []
for destination in Destination.objects.all():
visited = destination in visitorDestinations
destinationForms.append(VisitorDestinationForm(visitor_obj, destination, visited, prefix=destination.destination))
return render_to_response('testapp/edit_visitor.html', {'form': visitorForm, 'destinationForms' : destinationForms, 'visitor' : visitor_obj}, context_instance= RequestContext(request))
I simply collect my destination forms in a list and pass this list to my template, so that it can iterate over them and display them. It works well as long as you don't forget to pass a different prefix for each one in the constructor
I'll leave the question open for a few days in case some one has a cleaner method.
Thanks!
So, as you've seen, one of the things about inlineformset_factory is that it expects two models - a parent, and child, which has a foreign key relationship to the parent. How do you pass extra data on the fly to the form, for extra data in the intermediary model?
How I do this is by using curry:
from django.utils.functional import curry
from my_app.models import ParentModel, ChildModel, SomeOtherModel
def some_view(request, child_id, extra_object_id):
instance = ChildModel.objects.get(pk=child_id)
some_extra_model = SomeOtherModel.objects.get(pk=extra_object_id)
MyFormset = inlineformset_factory(ParentModel, ChildModel, form=ChildModelForm)
#This is where the object "some_extra_model" gets passed to each form via the
#static method
MyFormset.form = staticmethod(curry(ChildModelForm,
some_extra_model=some_extra_model))
formset = MyFormset(request.POST or None, request.FILES or None,
queryset=SomeObject.objects.filter(something=something), instance=instance)
The form class "ChildModelForm" would need to have an init override that adds the "some_extra_model" object from the arguments:
def ChildModelForm(forms.ModelForm):
class Meta:
model = ChildModel
def __init__(self, some_extra_model, *args, **kwargs):
super(ChildModelForm, self).__init__(*args, **kwargs)
#do something with "some_extra_model" here
Hope that helps get you on the right track.
From django 1.9, there is a support for passing custom parameters to formset forms :
https://docs.djangoproject.com/en/1.9/topics/forms/formsets/#passing-custom-parameters-to-formset-forms
Just add form_kwargs to your FormSet init like this :
from my_app.models import ParentModel, ChildModel, SomeOtherModel
def some_view(request, child_id, extra_object_id):
instance = ChildModel.objects.get(pk=child_id)
some_extra_model = SomeOtherModel.objects.get(pk=extra_object_id)
MyFormset = inlineformset_factory(ParentModel, ChildModel, form=ChildModelForm)
formset = MyFormset(request.POST or None, request.FILES or None,
queryset=SomeObject.objects.filter(something=something), instance=instance,
form_kwargs={"some_extra_model": some_extra_model})

django: building a formset from a given custom form and a queryset [duplicate]

I'm trying to do something that should be very common: add/edit a bunch of related models in a single form. For example:
Visitor Details:
Select destinations and activities:
Miami [] - swimming [], clubbing [], sunbathing[]
Cancun [] - swimming [], clubbing [], sunbathing[]
My models are Visitor, Destination and Activity, with Visitor having a ManyToMany field into Destination through an intermediary model, VisitorDestination, which has the details of the activities to be done on the destination (in itself a ManyToMany field into Activity).
Visitor ---->(M2M though VisitorDestination) -------------> Destination
|
activities ---->(M2M)---> Activity
Note that I don't want to enter new destination / activity values, just choose from those available in the db (but that's a perfectly legit use of M2M fields right?)
To me this looks like an extremely common situation (a many to many relation with additional details which are a FK or M2M field into some other model), and this looks like the most sensible modelling, but please correct me if I'm wrong.
I've spent a few days searching Django docs / SO / googling but haven't been able to work out how to deal with this. I tried several approaches:
Custom Model form for Visitor, where I add multiple choice fields for Destination and Activity. That works ok if Destination and Activity could be selected independently, but here they are correlated, ie I want to choose one or several activities for each destination
Using inlineformset_factory to generate the set of destination / activities forms, with inlineformset_factory(Destination, Visitor). This breaks, because Visitor has a M2M relation to Destination, rather than a FK.
Customizing a plain formset, using formset_factory, eg DestinationActivityFormSet = formset_factory(DestinationActivityForm, extra=2). But how to design DestinationActivityForm? I haven't explored this enough, but it doesn't look very promising: I don't want to type in the destination and a list of activities, I want a list of checkboxes with the labels set to the destination / activities I want to select, but the formset_factory would return a list of forms with identical labels.
I'm a complete newbie with django so maybe the solution is obvious, but I find that the documentation in this area is very weak - if anyone has some pointers to examples of use for forms / formsets that would be also helpful
thanks!
In the end I opted for processing multiple forms within the same view, a Visitor model form for the visitor details, then a list of custom forms for each of the destinations.
Processing multiple forms in the same view turned out to be simple enough (at least in this case, where there were no cross-field validation issues).
I'm still surprised there is no built-in support for many to many relationships with an intermediary model, and looking around in the web I found no direct reference to it. I'll post the code in case it helps anyone.
First the custom forms:
class VisitorForm(ModelForm):
class Meta:
model = Visitor
exclude = ['destinations']
class VisitorDestinationForm(Form):
visited = forms.BooleanField(required=False)
activities = forms.MultipleChoiceField(choices = [(obj.pk, obj.name) for obj in Activity.objects.all()], required=False,
widget = CheckboxSelectMultipleInline(attrs={'style' : 'display:inline'}))
def __init__(self, visitor, destination, visited, *args, **kwargs):
super(VisitorDestinationForm, self).__init__(*args, **kwargs)
self.destination = destination
self.fields['visited'].initial = visited
self.fields['visited'].label= destination.destination
# load initial choices for activities
activities_initial = []
try:
visitorDestination_entry = VisitorDestination.objects.get(visitor=visitor, destination=destination)
activities = visitorDestination_entry.activities.all()
for activity in Activity.objects.all():
if activity in activities:
activities_initial.append(activity.pk)
except VisitorDestination.DoesNotExist:
pass
self.fields['activities'].initial = activities_initial
I customize each form by passing a Visitor and Destination objects (and a 'visited' flag which is calculated outside for convenience)
I use a boolean field to allow the user to select each destination. The field is called 'visited', however I set the label to the destination so it gets nicely displayed.
The activities get handled by the usual MultipleChoiceField (I used I customized widget to get the checkboxes to display on a table, pretty simple but can post it if somebody needs that)
Then the view code:
def edit_visitor(request, pk):
visitor_obj = Visitor.objects.get(pk=pk)
visitorDestinations = visitor_obj.destinations.all()
if request.method == 'POST':
visitorForm = VisitorForm(request.POST, instance=visitor_obj)
# set up the visitor destination forms
destinationForms = []
for destination in Destination.objects.all():
visited = destination in visitorDestinations
destinationForms.append(VisitorDestinationForm(visitor_obj, destination, visited, request.POST, prefix=destination.destination))
if visitorForm.is_valid() and all([form.is_valid() for form in destinationForms]):
visitor_obj = visitorForm.save()
# clear any existing entries,
visitor_obj.destinations.clear()
for form in destinationForms:
if form.cleaned_data['visited']:
visitorDestination_entry = VisitorDestination(visitor = visitor_obj, destination=form.destination)
visitorDestination_entry.save()
for activity_pk in form.cleaned_data['activities']:
activity = Activity.objects.get(pk=activity_pk)
visitorDestination_entry.activities.add(activity)
print 'activities: %s' % visitorDestination_entry.activities.all()
visitorDestination_entry.save()
success_url = reverse('visitor_detail', kwargs={'pk' : visitor_obj.pk})
return HttpResponseRedirect(success_url)
else:
visitorForm = VisitorForm(instance=visitor_obj)
# set up the visitor destination forms
destinationForms = []
for destination in Destination.objects.all():
visited = destination in visitorDestinations
destinationForms.append(VisitorDestinationForm(visitor_obj, destination, visited, prefix=destination.destination))
return render_to_response('testapp/edit_visitor.html', {'form': visitorForm, 'destinationForms' : destinationForms, 'visitor' : visitor_obj}, context_instance= RequestContext(request))
I simply collect my destination forms in a list and pass this list to my template, so that it can iterate over them and display them. It works well as long as you don't forget to pass a different prefix for each one in the constructor
I'll leave the question open for a few days in case some one has a cleaner method.
Thanks!
So, as you've seen, one of the things about inlineformset_factory is that it expects two models - a parent, and child, which has a foreign key relationship to the parent. How do you pass extra data on the fly to the form, for extra data in the intermediary model?
How I do this is by using curry:
from django.utils.functional import curry
from my_app.models import ParentModel, ChildModel, SomeOtherModel
def some_view(request, child_id, extra_object_id):
instance = ChildModel.objects.get(pk=child_id)
some_extra_model = SomeOtherModel.objects.get(pk=extra_object_id)
MyFormset = inlineformset_factory(ParentModel, ChildModel, form=ChildModelForm)
#This is where the object "some_extra_model" gets passed to each form via the
#static method
MyFormset.form = staticmethod(curry(ChildModelForm,
some_extra_model=some_extra_model))
formset = MyFormset(request.POST or None, request.FILES or None,
queryset=SomeObject.objects.filter(something=something), instance=instance)
The form class "ChildModelForm" would need to have an init override that adds the "some_extra_model" object from the arguments:
def ChildModelForm(forms.ModelForm):
class Meta:
model = ChildModel
def __init__(self, some_extra_model, *args, **kwargs):
super(ChildModelForm, self).__init__(*args, **kwargs)
#do something with "some_extra_model" here
Hope that helps get you on the right track.
From django 1.9, there is a support for passing custom parameters to formset forms :
https://docs.djangoproject.com/en/1.9/topics/forms/formsets/#passing-custom-parameters-to-formset-forms
Just add form_kwargs to your FormSet init like this :
from my_app.models import ParentModel, ChildModel, SomeOtherModel
def some_view(request, child_id, extra_object_id):
instance = ChildModel.objects.get(pk=child_id)
some_extra_model = SomeOtherModel.objects.get(pk=extra_object_id)
MyFormset = inlineformset_factory(ParentModel, ChildModel, form=ChildModelForm)
formset = MyFormset(request.POST or None, request.FILES or None,
queryset=SomeObject.objects.filter(something=something), instance=instance,
form_kwargs={"some_extra_model": some_extra_model})