0

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:

  1. 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.

  2. 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

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Ajay saini
  • 2,352
  • 1
  • 11
  • 25
  • I did not fully understand: Should users be able to create new ```Load``` objects when creating a trip? Or should they just be able to add existing ```Load``` objects to the trip? – Chris Jan 23 '20 at 09:07
  • If you want the user to be able to create new ```Load``` objects you coud use dynamic formsets as described in this SO [post](https://stackoverflow.com/questions/501719/dynamically-adding-a-form-to-a-django-formset-with-ajax) – Chris Jan 23 '20 at 09:14
  • They can create new load object and edit them – Ajay saini Jan 23 '20 at 09:18
  • Ok but by using dynamic formset, how can i show Load objects in a tabular formate while creating and updating trup – Ajay saini Jan 23 '20 at 09:19
  • You mean in a table without any form inputs? Use javascript to update the table when form data changes and to show the form of your fomset which the user wants to edit – Chris Jan 23 '20 at 09:28
  • Actiully form for Load is very big and its not good to have inlineformset for hugh forms, i am showing the Load's forms in popup. – Ajay saini Jan 23 '20 at 10:07

2 Answers2

0

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

RHSmith159
  • 1,823
  • 9
  • 16
0

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.

nigel222
  • 7,582
  • 1
  • 14
  • 22