Django Forms - Relating Objects (Model Formsets?) - django

Say I have something like this:
class Product(models.Model)
name = models.CharField()
description models.TextField()
class Image(models.Model)
product = models.ForeignKey(Product, null=True, blank=True, related_name="images")
image = models.ImageField()
Suppose I'm in a form for creating a Product and in this form there is a section that allows you to upload images. These images are uploaded asynchronously. How can I have it so that:
On creation:
Product is created and images are related to it.
On editing:
Product is fetched, related Images are fetched. Product is edited, Images are edited.
Currently, I have a view that does both jobs of creating or editing Products. How can I accomplish the image related part of this product form? Model Formsets?
Edit:
#login_required
def create_or_edit_product(request, product_id=None):
# Redirect user if he has no account associated to him
try:
account = Account.objects.get(membership__user=request.user)
except:
login_url = reverse('login') + ('?next=%s') % request.path
return HttpResponseRedirect(login_url)
# Get product if product id passed
product_instance = None
if product_id is not None:
product_instance = get_object_or_404(product, id=product_id)
# Get related pictures if product exists. Get picture values (dictionary list, used for initial formset data) if pictures found.
pictures = product.pictures.all() if product_instance is not None else None
pictures_values = pictures.values() if pictures else []
PictureFormSet = formset_factory(PictureForm, formset=BasePictureFormSet, extra=0, can_delete=False, max_num=40)
if request.method == "POST":
product_form = productForm(request.POST, prefix='product', instance=product_instance)
picture_formset = PictureFormSet(request.POST, prefix='pictures', initial=pictures_values)
# If forms are valid
if product_form.is_valid() and picture_formset.is_valid():
try:
# Add account to product and save
product = product_form.save(commit=False)
if product_instance is None:
product.account = account
product.save()
# Remove picture-product relationships of current pictures
if pictures is not None:
pictures.update(product=None)
# Update each picture with the product and corresponding sort order. (The field 'id' is a picture object. The form converts the passed picture id to a picture object)
for index, form in enumerate(picture_formset):
picture = form.cleaned_data['id']
Picture.objects.filter(id=picture.id).update(product=product, sort_order=index)
except Exception, e:
# Rollback changes - something went wrong
transaction.rollback()
print "Transaction Rollback: %s" % e
else:
# Commit changes and redirect to product edit page
transaction.commit()
return HttpResponseRedirect('product_edit', product_id=product.id)
else:
product_form = productForm(prefix='product', instance=product_instance)
picture_formset = PictureFormSet(prefix='pictures')
# TODO: change add template to an add/edit one
return render_to_response('products/add.html', {
'product_form' : product_form,
'picture_formset' : picture_formset,
'pictures' : pictures,
},
context_instance=RequestContext(request))
I'm new to Python and Django but that is my somewhat working view. (I've only tried adding a new product so far)
The form that the user sees has thumbnails with hidden inputs containing each picture id. In the case that the form fails, I'd like to re display thumbnails along with the hidden inputs (which are already kept). To make that work, I'm guessing I'd have to query the picture ids, but the form is not valid so how would I go about doing that? (if that is even the right path to go about)
What do you think?

#Aamir Adnan is right that Model Formsets are an elegant way to deal with this.
Here's a full fledge example that is similar to what you need - http://stellarchariot.com/blog/2011/02/dynamically-add-form-to-formset-using-javascript-and-django/
As in the example, because the end user may need to add additional "images" (in your case) which are related to the product, you will need some javascript logic to handle the dynamic addition of forms so that the user can arbitrarily add and upload images on save.

Related

Deleting a value from many to many field in views.py in django website

I am working on a basic ecommerce django website . My product model has the following:
size=models.ManyToManyField(Siz)
and this is the size model:
class Siz(models.Model):
size=models.CharField(max_length=3,null=True,blank=True)
desc=models.CharField(max_length=110,null=True,blank=True)
I have added the sizes manually in the django administration. What I want is that Whenever a user orders something the value of the size of the product that he ordered automatically is deleted from the product sizes available. This is my orderitem model that has the field of the selected size.
class OrderItem(models.Model):
product=models.ForeignKey(Product,on_delete=models.SET_NULL,null=True)
order=models.ForeignKey(Order,on_delete=models.SET_NULL,null=True,)
size=models.ForeignKey(Siz,on_delete=models.SET_NULL,null=True)
and this is my views that checks when the order (payment for the order) is completed or not.
#csrf_exempt
def handlerequest(request, id):
order=Order.objects.get(id=id)
items=order.orderitem_set.all()
form = request.POST
verify = Checksum.verify_checksum(response_dict, MERCHANT_KEY, checksum)
if verify:
if response_dict['RESPCODE'] == '01':
order.complete=True
order.save()
else:
print(response_dict['RESPMSG'])
order.delete()
return render(request, 'handlerequest.html', {'response': response_dict,'order':order})
When the order is completed the order.complete is set to true.At that Time I want to deleted the ordered size.Please help me with a way.
Thanks
This is my order model:
class Order(models.Model):
customer=models.ForeignKey(Customer,on_delete=models.SET_NULL,null=True,blank=True)
date_ordered=models.DateTimeField(auto_now=True)
complete=models.BooleanField(default=False,null=True,blank=False)

Django saving to ManyToMany fields

I have a simple model class with 2 ManyToManyField fields like this:
models.py
class Folder(models.Model):
user = models.ManyToManyField(User)
asset = models.ManyToManyField(Asset)
In my view, I know the user ID and the asset ID. Say the user ID is 1 and the asset ID is 30, how do I inject this row? I guess I don't understand how to instantiate Folder so I can save/update the row.
views.py
def addAssetToMyFolder(request, id=None):
''' view is simplified for brevity
'''
f = Folder(
user = 1,
asset = 30,
)
f.save()
To associate a user or asset instance with a folder you need to first save the folder.
To store a many to many relationship the database creates a third table which stores the ids of the objects.
So if you want to relate a user to a folder as a many to many relationship, both of them should have their own ids before they can be related as many to many.
Say you have two users with ids 10 and 19 respectively.
You have one folder with id 4 and user 10 and user 19 are related to this folder. At the db level this how these relations will be stored
folder_id user_id
4 10
4 19
so for each many to many relation there is one row in the relations table for the two models.
Same will be valid for asset.
So the code should be changed to:
def addAssetToMyFolder(request, id=None):
''' view is simplified for brevity
'''
f = Folder()
f.save()
user = User.objects.get(id=1) # not needed if adding by id
f.user.add(user) # or f.user.add(user_id)
asset = Asset.objects.get(id=30) # not needed if adding by id
f.asset.add(asset) # or f.asset.add(asset_id)
check out : https://docs.djangoproject.com/en/1.6/topics/db/examples/many_to_many/
Because I reallllly hate redundancy, here's a another solution using a dynamic modelform. The benefits are it's neater, you don't need to fetch the User and Asset objects, you use the related pk and you only save once.
The drawback is that it's overkill for the common everyday need. So you should probably mark #zaphod100.10 answer as correct, but know that this method also exists:
Meta = type('Meta', (), {'model': Folder, 'fields': ['user', 'asset']} )
FolderForm = type('FolderForm', (forms.ModelForm, ), {'Meta': Meta})
data = {'user': ['1'], 'asset': ['30']} #the values need to be a list of strings, representing pks of related objects
f = FolderForm(data)
new_obj = f.save()

Multiple Form with Single Submit Button

I'm currently working with django project. I had to filter the data store on the database based on the user input on form (at template) as looked below.
On form user either enter value or leave it blank. So what I have to do is first find the (valid) user input and then fire appropriate query to display data as user input in the form. So final result should be displayed on table at template.
As I'm new to django, how should I have to pass the data and fire query to represent data at multiple field. As help or link related to these type problem are expected. ( I just able to filter from the database with only one form and had no concept to solve this.)
Model of my temp project is as below.
class exReporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField()
gender = models.CharField(max_length=1)
age = models.IntegerField()
label = models.IntegerField()
There are a number of approaches you can take, but here is one solution you can use that involves chaining together the appropriate filters based on the form's posted data:
*Note: To conform to Python's naming convention, rename exReporter class to ExReporter.
# views.py
def process_ex_reporter_form(request):
if request.method == "POST":
# ExReporterForm implementation details not included.
ex_reporter_form = ExReporterForm(request.POST)
if ex_reporter_form.is_valid():
# If form field has no data, cleaned data should be None.
gender = ex_reporter_form.cleaned_data['gender']
age_start = ex_reporter_form.cleaned_data['age_start']
age_end = ex_reporter_form.cleaned_data['age_end']
aggregation_group = ex_reporter_form.cleaned_data['aggregation_group']
aggregation_id = ex_reporter_form.cleaned_data['aggregation_id']
ex_reporters = ExReporter.objects.get_ex_reporters(gender, age_start,
age_end, aggregation_group, aggregation_id)
else:
# Pass back form for correction.
pass
else:
# Pass new form to user.
pass
# models.py
class ExReporterManager(models.Manager):
def get_ex_reporters(self, gender, age_start, age_end, aggregation_group,
aggregation_id):
ex_reporters = super(ExReporterManager, self).get_query_set().all()
# Even though the filters are being applied in separate statements,
# database will only be hit once.
if ex_reporters:
if gender:
ex_reporters = ex_reporters.filter(gender=gender)
if age_start:
ex_reporters = ex_reporters.filter(age__gt=age_start)
if age_end:
ex_reporters = ex_reporters.filter(age__lt=age_end)
# Apply further filter logic for aggregation types supported.
return ex_reporters

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