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

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

Related

Django unique_together with nullable ForeignKey

I'm using Django 1.8.4 in my dev machine using Sqlite and I have these models:
class ModelA(Model):
field_a = CharField(verbose_name='a', max_length=20)
field_b = CharField(verbose_name='b', max_length=20)
class Meta:
unique_together = ('field_a', 'field_b',)
class ModelB(Model):
field_c = CharField(verbose_name='c', max_length=20)
field_d = ForeignKey(ModelA, verbose_name='d', null=True, blank=True)
class Meta:
unique_together = ('field_c', 'field_d',)
I've run proper migration and registered them in the Django Admin. So, using the Admin I've done this tests:
I'm able to create ModelA records and Django prohibits me from creating duplicate records - as expected!
I'm not able to create identical ModelB records when field_b is not empty
But, I'm able to create identical ModelB records, when using field_d as empty
My question is: How do I apply unique_together for nullable ForeignKey?
The most recent answer I found for this problem has 5 year... I do think Django have evolved and the issue may not be the same.
Django 2.2 added a new constraints API which makes addressing this case much easier within the database.
You will need two constraints:
The existing tuple constraint; and
The remaining keys minus the nullable key, with a condition
If you have multiple nullable fields, I guess you will need to handle the permutations.
Here's an example with a thruple of fields that must be all unique, where only one NULL is permitted:
from django.db import models
from django.db.models import Q
from django.db.models.constraints import UniqueConstraint
class Badger(models.Model):
required = models.ForeignKey(Required, ...)
optional = models.ForeignKey(Optional, null=True, ...)
key = models.CharField(db_index=True, ...)
class Meta:
constraints = [
UniqueConstraint(fields=['required', 'optional', 'key'],
name='unique_with_optional'),
UniqueConstraint(fields=['required', 'key'],
condition=Q(optional=None),
name='unique_without_optional'),
]
UPDATE: previous version of my answer was functional but had bad design, this one takes in account some of the comments and other answers.
In SQL NULL does not equal NULL. This means if you have two objects where field_d == None and field_c == "somestring" they are not equal, so you can create both.
You can override Model.clean to add your check:
class ModelB(Model):
#...
def validate_unique(self, exclude=None):
if ModelB.objects.exclude(id=self.id).filter(field_c=self.field_c, \
field_d__isnull=True).exists():
raise ValidationError("Duplicate ModelB")
super(ModelB, self).validate_unique(exclude)
If used outside of forms you have to call full_clean or validate_unique.
Take care to handle the race condition though.
#ivan, I don't think that there's a simple way for django to manage this situation. You need to think of all creation and update operations that don't always come from a form. Also, you should think of race conditions...
And because you don't force this logic on DB level, it's possible that there actually will be doubled records and you should check it while querying results.
And about your solution, it can be good for form, but I don't expect that save method can raise ValidationError.
If it's possible then it's better to delegate this logic to DB. In this particular case, you can use two partial indexes. There's a similar question on StackOverflow - Create unique constraint with null columns
So you can create Django migration, that adds two partial indexes to your DB
Example:
# Assume that app name is just `example`
CREATE_TWO_PARTIAL_INDEX = """
CREATE UNIQUE INDEX model_b_2col_uni_idx ON example_model_b (field_c, field_d)
WHERE field_d IS NOT NULL;
CREATE UNIQUE INDEX model_b_1col_uni_idx ON example_model_b (field_c)
WHERE field_d IS NULL;
"""
DROP_TWO_PARTIAL_INDEX = """
DROP INDEX model_b_2col_uni_idx;
DROP INDEX model_b_1col_uni_idx;
"""
class Migration(migrations.Migration):
dependencies = [
('example', 'PREVIOUS MIGRATION NAME'),
]
operations = [
migrations.RunSQL(CREATE_TWO_PARTIAL_INDEX, DROP_TWO_PARTIAL_INDEX)
]
Add a clean method to your model - see below:
def clean(self):
if Variants.objects.filter("""Your filter """).exclude(pk=self.pk).exists():
raise ValidationError("This variation is duplicated.")
I think this is more clear way to do that for Django 1.2+
In forms it will be raised as non_field_error with no 500 error, in other cases, like DRF you have to check this case manual, because it will be 500 error.
But it will always check for unique_together!
class BaseModelExt(models.Model):
is_cleaned = False
def clean(self):
for field_tuple in self._meta.unique_together[:]:
unique_filter = {}
unique_fields = []
null_found = False
for field_name in field_tuple:
field_value = getattr(self, field_name)
if getattr(self, field_name) is None:
unique_filter['%s__isnull' % field_name] = True
null_found = True
else:
unique_filter['%s' % field_name] = field_value
unique_fields.append(field_name)
if null_found:
unique_queryset = self.__class__.objects.filter(**unique_filter)
if self.pk:
unique_queryset = unique_queryset.exclude(pk=self.pk)
if unique_queryset.exists():
msg = self.unique_error_message(self.__class__, tuple(unique_fields))
raise ValidationError(msg)
self.is_cleaned = True
def save(self, *args, **kwargs):
if not self.is_cleaned:
self.clean()
super().save(*args, **kwargs)
One possible workaround not mentioned yet is to create a dummy ModelA object to serve as your NULL value. Then you can rely on the database to enforce the uniqueness constraint.

update django choice field with database results

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.

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

Can't Return JSON object using MongoEngine Pymongo with Django?

So I'm trying to return a JSON object for a project. I've spent a few hours trying to get Django just returning the JSON.
Heres the view that we've been working with:
def json(request, first_name):
user = User.objects.all()
#user = User.objects.all().values()
result = simplejson.dumps(user, default=json_util.default)
return HttpResponse(result)
Here's my model:
class User(Document):
gender = StringField( choices=['male', 'female', 'Unknown'])
age = IntField()
email = EmailField()
display_name = StringField(max_length=50)
first_name = StringField(max_length=50)
last_name = StringField(max_length=50)
location = StringField(max_length=50)
status = StringField(max_length=50)
hideStatus = BooleanField()
photos = ListField(EmbeddedDocumentField('Photo'))
profile =ListField(EmbeddedDocumentField('ProfileItem'))
allProfile = ListField(EmbeddedDocumentField('ProfileItem')) #only return for your own profile
This is what it's returning:
[<User: User object>, <User: User object>] is not JSON serializable
Any thoughts on how I can just return the JSON?
With MongoEngine 0.8 or greater, objects and querysets have a to_json() method.
>>> User.objects.to_json()
simplejson.dumps() doesn't know how to "reach into" your custom objects; the default function, json_util.default must just be calling str() or repr() on your documents. (Is json_util custom code you've written? If so, showing its source here could prove my claim.)
Ultimately, your default function will need to be able to make sense of the MongoEngine documents. I can think of at least two ways that this might be implemented:
Write a custom default function that works for all MongoEngine documents by introspecting their _fields attribute (though note that the leading underscore means that this is part of the private API/implementation detail of MongoEngine and may be subject to change in future versions)
Have each of your documents implement a as_dict method which returns a dictionary representation of the object. This would work similarly to the to_mongo method provided on documents by MongoEngine, but shouldn't return the _types or _cls fields (again, these are implementation details of MongoEngine).
I'd suggest you go with option #2: the code will be cleaner and easier to read, better encapsulated, and won't require using any private APIs.
As dcrosta suggested you can do something like this, hope that will help you.
Document definition
class MyDocument(Document):
# Your document definition
def to_dict(self):
return mongo_to_dict_helper(self)
helper.py:
from mongoengine import StringField, ListField, IntField, FloatField
def mongo_to_dict_helper(obj):
return_data = []
for field_name in obj._fields:
if field_name in ("id",):
continue
data = obj._data[field_name]
if isinstance(obj._fields[field_name], StringField):
return_data.append((field_name, str(data)))
elif isinstance(obj._fields[field_name], FloatField):
return_data.append((field_name, float(data)))
elif isinstance(obj._fields[field_name], IntField):
return_data.append((field_name, int(data)))
elif isinstance(obj._fields[field_name], ListField):
return_data.append((field_name, data))
else:
# You can define your logic for returning elements
return dict(return_data)