Django model form with related objects how to save and edit - django

I am developing an application which have related models like Trip can have multiple loads.
So how can I design the create form to achieve the desired functionality? I know formset can be used to achieve the functionality but I want custom functionality like:
User can create new load while creating trip, the added loads will be show in a html table within the form with edit and delete functionality being on the Trip create page.
I have achieved the functionality using two ways but I am looking for a neater and cleaner approach. What I have done so far was:
I added the Loads using ajax and retrieved the saved load's id and store in a hidden field, when submitting the main Trip's form i create object for Trip and retrieve Loads's object using id and map that Trip id in the Load's object.
I have keep Loads data in the javascript objects and allow user to perform add edit delete Loads without going to the database, once user submit the main Trip form i post Loads's data and create objects and save in the database.
Please let me know if anybody have done this type of work and have a cleaner approach

This sounds like a good use case for django-extra-views.
(install with pip install django-extra-views and add extra_views to your INSTALLED_APPS)
You can create and update models with inlines with simple class based views.
In Views
from extra_views import CreateWithInlinesView, UpdateWithInlinesView, InlineFormSetFactory
from .models import Load, Trip
class LoadInline(InlineFormsetFactory):
model = Load
fields = [<add your field names here>]
class CreateTripView(CreateWithInlinesView):
model = Trip
inlines = [LoadInline]
fields = [<add your trip model fields here>]
template = 'trips/create.html`
template:
<form method="post">
...
{{ form }}
{% for formset in inlines %}
{{ formset }}
{% endfor %}
...
<input type="submit" value="Submit" />
</form>
The update view is very similar. Here are the docs:
https://github.com/AndrewIngram/django-extra-views#createwithinlinesview-or-updatewithinlinesview

The thing that often gets overlooked is commit=False, and the ability to process more than one form or formset in a single view. So you could have a formset for trips, and a form for load, and process the information creating all the objects once all the forms validate.
Here is a restructured view function outline which I use to process multiple forms (haven't edited my app-specific names, don't want to introduce errors):
def receive_uncoated( request): #Function based view
# let's put form instantiation in one place not two, and reverse the usual test. This
# makes for a much nicer layout with actions not sandwiched by "boilerplate"
# note any([ ]) forces invocation of both .is_valid() methods
# so errors in second form get shown even in presence of errors in first
args = [request.POST, ] if request.method == "POST" else []
batchform = CreateUncWaferBatchForm( *args )
po_form = CreateUncWaferPOForm( *args, prefix='po')
if request.method != "POST" or any(
[ not batchform.is_valid(), not po_form.is_valid() ]):
return render(request, 'wafers/receive_uncoated.html', # can get this out of the way at the top
{'batchform': batchform,
'po_form': po_form,
})
#POST, everything is valid, do the work
# create and save some objects based on the validated forms ...
return redirect( 'wafers:ok' )
NB the use of any is vital. It avoids Python short-cut evaluation of a conditional and therefore makes all errors on second and subsequent forms available to the user even if the first form failed validation.
Back to this question: You would replace batch_form with a trip_form, and po_form with a ModelFormset for loads. After your form and formset both validated, you would create all the requested objects using
trip = trip_form.save( commit=False)
load_list = loads_formset.save( commit = False)
# fill in some related object in trip then save trip
foo = Foo.objects.get( ...)
trip.foo = foo
trip.save()
# link loads to trip and save them
for load in load_list:
load.trip = trip
load.save()
You probably want to run this in a transaction so that if something goes wrong (DatabaseError) with saving one of the loads, you don't get a partial trip stored in the database but rather get the whole thing rolled back.

Related

Adding a new ManyToMany relationship in a form?

Django newbie here. I keep encountering the exact same design paradigm, which seems like it should be common for everyone, yet can't find out how it's supposed to be resolved.
Picture the following ManyToMany relationships:
An organization could have many members; each person could be a member of many organizations
An organization could manage many objects. An object could be in use by multiple organizations. The same applies to the relationship between people and objects.
An organization, person, or object could have multiple media elements (photos, videos, etc) of it, and a single media element could be tagged with numerous organizations, people, or objects
Nothing unusual. But how does a site user add a new person, organization, or object? It seems that if someone is filling out an "add an organization" form, in addition to choosing from existing people, objects, media, etc there should be a button for "new member", "new object", "new photo", etc, and this should take you to the forms for creating new members, objects, media, etc. And when they're done, it should go back to the previous page - whose form filled-out form entries should persist, and the newly created entry should be listed in its respective ManyToMany field.
The problem is, I don't know how to do this. I don't know how one would add a button in the middle of a form, and can't seem to find anything to clarify how to do it. I assume it would need to be a submit button, with a different name / id or some other way so that views.py can treat it differently, via flagging an "incomplete" record in the database. And the new form will need to be passed information about what page it needs to go back to when it's submitted.
Am I thinking about this correctly? If so, then I think the only knowledge I lack is how to add a second submit button in a form and how to recognize its usage in views.py.
If I'm not thinking about this correctly, however, please suggest an alternative paradigm that you think makes more sense :) This is my first Django project, so I'm learning as I do it.
ED: I'm thinking maybe instead of using {{ form.as_p }} to display it, I need to iterate over fields and use some logic to add the extra submit button in the middle as html: What's the best way to add custom HTML in the middle of a form with many fields?
Then I'll just need to figure out a way to detect which submit button was used and put some logic behind it to handle partially-submitted forms, redirecting to a form to create the relation, and then redirecting back on submit... I can probably figure this out...
The first thing I would recommend is to define your models. Lay them all out with the attributes you require. That'll be the foundation for everything else you want to accomplish. You can do everything you mentioned with Django... it's just a matter of coding it. As far as I know you would need to create each model instance separately, and then you can refer to already created instances in the create form for the Organization model for example. I would look into the docs for generic views that help you create objects easily. Then you can link to other create forms if you wish. I don't know how you can create multiple instances of different models in one form, and I don't think it would be the best way to do things even if you can. Here's an example of a model, a create form, a create view, and corresponding url:
# models.py
class Organization(models.Model):
name = models.CharField(max_length=100, null=True, blank=True)
# forms.py
class OrganizationForm(forms.ModelForm):
class Meta:
model = Organization
fields = ('name',)
def __init__(self, *args, **kwargs):
super(OrganizationForm, self).__init__(*args, **kwargs)
self.fields['name'].required = True
def clean(self):
cleaned_data = super(OrganizationForm, self).clean()
name = cleaned_data.get('name')
# views.py
class OrganizationCreateView(CreateView): # inherits from CreateView
form_class = OrganizationForm
template_name = 'create_org.html'
success_url = 'success'
def form_valid(self, form): # validate the form and save the model instance
org = form.save(commit=False)
org.save()
return redirect(reverse('redirect_url'))
# urls.py
from Project.apps.app_name import views as app_views
app_name = 'app_name'
urlpatterns = [
url(r'^create_org/$', app_views.OrganizationCreateView.as_view(), name='create_org'), # as_view() is used for class based views
# create_org.html
<form method="post">
{% crsf_token %}
{{ form.as_p }}
<a href="{% url 'app_name:create_person' %}>Create person</a> # You can link to other create views, and just style the link as a button.
<input type="submit" value="Submit">
</form>
Hope that helps.

How to display multiple forms of a single model in Django templates?

I have this model Note:
class Note(models.Model):
category = models.ForeignKey(Category)
author = models.ForeignKey('auth.User')
title = models.CharField(max_length=40)
text = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
And I want to display this form:
class NoteEditForm(forms.ModelForm):
class Meta:
model = Note
fields = ('title', 'text')
in a template, but I want it to appear for each existing Note object in the database (it has to be that way). I've done something like that but then I hardcoded the form, pointing to the edit view URL with each object pk as a parameter; but I'm sure it has to be a clearer way, just I haven't found it. Could you guys help me with that? Thanks!
The easiest way to do this is to use a formset (also see model formsets).
For your Note model and NoteEditForm you could do something like this. You'd usually put this wherever you've defined your NoteEditForm but it can go in another file, such as views.py.
from django.forms import modelformset_factory
NoteEditFormSet = modelformset_factory(Note, form=NoteEditForm)
Using NoteEditFormSet in a view and template is almost the same as using a regular form, but there are a few differences to be aware of if you want to do anything complicated so have a look at the docs (view and template). If that's not clear enough, add a few details of what you're trying to do in your view and template and I'll try to help.
By default the formset will use Note.objects.all() as its queryset, which is what you say you want, but you can change that (details are covered in the docs).
Update:
To save an individual Note with an AJAX request I would add a second view to handle those requests. So if your formset for all Notes is served by a view at /notes/, you could add a view to handle your AJAX request at /notes/<note_id>/ (obviously just an example, adjust to fit your URL structure).
Then your JS on the /notes/ page is responsible for serializing the data for a single note and making the request to /notes/<note_id>/ (remember the CSRF token).
The HTML inputs generated for the formset have their IDs prefixed with something like id_form-<number>- and there are hidden inputs containing Note primary keys which will let you work out which ID prefix applies to each note.
I would think about doing it like this
{% for note in Notequeryset %}
<form action={% url 'url_name_to_form' pk={{note.pk}} %}>
{{form.as_p}}
</form>
{% endfor %}
Let me know what you think

Django : Formset as form field

one of the forms I need is a composite of simple fields (say "Department", "Building" and "RoomNumber"), and of dynamically generated pairs of fields (say "Name" and "Email"). Ideally, editing the contents of the simple fields and adding/removing dynamic field pairs would be done on a single form.
Code-wise, I'm wondering if trying to embed a Formset (of a form with the two dynamic fields) as a field in an ordinary form is a sensible approach or if there's another best practice to achieve what I'd like to accomplish.
Many thanks for any advice on these matters,
I'm not sure where the idea that you need to "embed a Formset as a field" comes from; this sounds like a case for the standard usage of formsets.
For example (making a whole host of assumptions about your models):
class OfficeForm(forms.Form):
department = forms.ModelChoiceField(...
room_number = forms.IntegerField(...
class StaffForm(forms.Form):
name = forms.CharField(max_length=...
email = forms.EmailField(...
from django.forms.formsets import formset_factory
StaffFormSet = formset_factory(StaffForm)
And then, for your view:
def add_office(request):
if request.method == 'POST':
form = OfficeForm(request.POST)
formset = StaffFormSet(request.POST)
if form.is_valid() && formset.is_valid():
# process form data
# redirect to success page
else:
form = OfficeForm()
formset = StaffFormSet()
# render the form template with `form` and `formset` in the context dict
Possible improvements:
Use the django-dynamic-formset jQuery plugin to get the probably-desired "add an arbitrary number of staff to an office" functionality without showing users a stack of blank forms every time.
Use model formsets instead (assuming the information you're collecting is backed by Django models), so you don't have to explicitly specify the field names or types.
Hope this helps.

How to create new (unsaved) Django model with associations?

I'm building a "preview" function into the CMS for my website which uses the existing front-end template to render a model. This model has an association:
class FeatureWork(models.Model):
name = models.CharField(max_length=100)
...
class FeatureWorkLink(models.Model):
feature_work = models.ForeignKey(FeatureWork)
In the view for the preview, I'm trying to build the model such that when the template calls feature.featureworklink_set.all it returns the associated links. Since neither model has been saved yet, all the standard Django form techniques seem to go out the window.
This is what I have so far, but it blows up when I call the add method on the manager, since the parent hasn't been saved yet:
form = FeatureWorkAdminForm(initial=request.POST)
featured = form.save(commit=False)
for link in request.POST['links'].split(","):
featured.featureworklink_set.add(FeatureWorkLink(image=link))
Why don't you just add:
preview = BooleanField()
to your models, save everything to database and don't look for hacks. This way you can have drafts for free.
You can actually save it in transaction and rollback when template is ready. It's not very efficient, but at least it will work.
featured.featureworklink_set.add(FeatureWorkLink(image=link)) will immediately attempt to create a relationship between a FeatureWork and FeatureWorkLink, which isn't going to happen because that instance of FeatureWork is not present in the database and you cannot satisfy the predicates for building the foreign key relationship.
But the great thing is that Django's Model and ModelForm instances will not validate foreign key relationships until your actually trying to commit the data. So manually constructing your FeaturedWorkLink with an uncommited, non-existing FeatureWork should satisfy any presentation of the data you need to do, much to what you'd expect:
links = []
form = FeatureWorkAdminForm(initial=request.POST)
featured = form.save(commit=False)
for link in request.POST['links'].split(","):
links.add(FeatureWorkLink(image=link, feature_work=featured))
# then somewhere in your templates, from the context
{% for link in links %}
<img src="{{ link.image }}"
title="Image for the featured work: '{{ link.feature_work.name }}'" />
{% endfor %}
So basically, during the course of collecting the data to create a FeatureWork, you'll have to maintain the FeatureWorkLink instances through subsequent requests. This is where you'd use a model form set, but provide an uncommitted FeatureWork for the feature_work property for every model form instance of the set, up until the point where all the data has been collected, where you then provide a commited FeatureWork instance, so that the model form set can satisfy referential integrity and finally be commited to the database.

How to make a "workflow" form

For my project I need many "workflow" forms. I explain myself:
The user selects a value in the first field, validates the form and new fields appear depending on the first field value. Then, depending on the others fields, new fields can appear...
How can I implement that in a generic way ?
I think the solution you are looking for is django form wizard
Basically you define separate forms for different pages and customize the next ones based on input in previous screens, at the end, you get all form's data together.
Specifically look at the process step advanced option on the form wizard.
FormWizard.process_step()
"""
Hook for modifying the wizard's internal state, given a fully validated Form object. The Form is guaranteed to have clean, valid data.
This method should not modify any of that data. Rather, it might want to set self.extra_context or dynamically alter self.form_list, based on previously submitted forms.
Note that this method is called every time a page is rendered for all submitted steps.
The function signature:
"""
def process_step(self, request, form, step):
# ...
If you need to only modify the dropdown values based on other dropdowns within the same form, you should have a look at the implemented dajaxproject
I think it depends on the scale of the problem.
You could write some generic JavaScript that shows and hides the form fields (then in the form itself you apply these css classes). This would work well for a relatively small number showing and hiding fields.
If you want to go further than that you will need to think about developing dynamic forms in Django. I would suggest you don't modify the ['field'] in the class like Ghislain suggested. There is a good post here about dynamic forms and it shows you a few approaches.
I would imagine that a good solution might be combining the dynamic forms in the post above with the django FormWizard. The FormWizard will take you through various different Forms and then allow you to save the overall data at the end.
It had a few gotchas though as you can't easily go back a step without loosing the data of the step your on. Also displaying all the forms will require a bit of a customization of the FormWizard. Some of the API isn't documented or considered public (so be wary of it changing in future versions of Django) but if you look at the source you can extend and override parts of the form wizard fairly easily to do what you need.
Finally a simpler FormWizard approach would be to have say 5 static forms and then customize the form selection in the wizard and change what forms are next and only show the relevant forms. This again would work well but it depends how much the forms change on previous choices.
Hope that helps, ask any questions if have any!
It sounds like you want an AJAXy type solution. Checkout the Taconite plugin for jQuery. I use this for populating pulldowns, etc. on forms. Works very nicely.
As for being "generic" ... you might have standard methods on your container classes that return lists of children and then have a template fragmen t that knows how to format that in some 'standard' way.
Ok, I've found a solution that does not use ajax at all and seems nice enough to me :
Create as many forms as needed and make them subclass each other. Put an Integer Hidden Field into the first one :
class Form1(forms.Form):
_nextstep = forms.IntegerField(initial = 0, widget = forms.HiddenInput())
foo11 = forms.IntegerField(label = u'First field of the first form')
foo12 = forms.IntegerField(label = u'Second field of the first form')
class Form2(Form1):
foo21 = forms.CharField(label = u'First field of the second form')
class Form3(Form2):
foo31 = forms.ChoiceField([],
label=u'A choice field which choices will be completed\
depending on the previous forms')
foo32 = forms.IntegerField(label = u'A last one')
# You can alter your fields depending on the data.
# Example follows for the foo31 choice field
def __init__(self, *args, **kwargs):
if self.data and self.data.has_key('foo12'):
self.fields['foo31'].choices = ['make','a','nice','list',
'and you can','use your models']
Ok, that was for the forms now here is the view :
def myview(request):
errors = []
# define the forms used :
steps = [Form1,Form2,Form3]
if request.method != 'POST':
# The first call will use the first form :
form = steps[0]()
else:
step = 0
if request.POST.has_key('_nextstep'):
step = int(request.POST['_nextstep'])
# Fetch the form class corresponding to this step
# and instantiate the form
klass = steps[step]
form = klass(request.POST)
if form.is_valid():
# If the form is valid, increment the step
# and use the new class to create the form
# that will be displayed
data = form.cleaned_data
data['_nextstep'] = min(step + 1, len(steps) - 1)
klass = steps[data['_nextstep']]
form = klass(data)
else:
errors.append(form.errors)
return render_to_response(
'template.html',
{'form':form,'errors':errors},
context_instance = RequestContext(request))
The only problem I saw is that if you use {{form}} in your template, it calls form.errors and so automagically validates the new form (Form2 for example) with the data of the previous one (Form1). So what I do is iterate over the items in the form and only use {{item.id}}, {{item.label}} and {{item}}. As I've already fetched the errors of the previous form in the view and passed this to the template, I add a div to display them on top of the page.