pythonjquerydjangodjango-admindjango-admin-tools

Overriding Django admin inlines formset will not save more than 1 row


I have a straight forward admin.ModelAdmin class with an inlines, of which I am overriding the form and formsets with a forms.Model and BaseInlineFormset to add a custom field. I have a custom jQuery script that gets loaded in and whenever a machine is picked from the select2 drop-down it runs an AJAX query to the REST API and grabs the items based on a foreign key value and populates the CleaningEntryInline with information. However, upon saving it is only posting a single record to the database.

class CleaningEntryInline(admin.TabularInline):
    model = CleaningEntry
    form = CleaningEntryForm
    formset = CleaningEntryFormSet
    extra = 0
    raw_id_fields = ['cleaning_item']

    fieldsets = [
        (None,{'fields':[('cleaning_item','cleaning_action', 'checked', 'na', 'notes')]})
    ]
    template = 'admin/quality/cleaningentry/edit_inline/tabular_actions.html'

class CleaningLogAdmin(admin.ModelAdmin):
    ####Save model function override to make and save QC Lab user and make uneditable.
    def save_model(self, request, obj, form, change): 
        obj.lab_user = request.user.username
        obj.save()

    list_display = ['machine_used','get_product_info','lot_num','start_time','lab_user']
    list_filter = ['machine_used']
    readonly_fields = ['lab_user', 'cleaning_users']
    search_fields = ['machine_cleaned', 'lot_num', 'recipe_cleaned__recipe_name__item_code', 'lab_user']
    autocomplete_fields = ['machine_used','recipe_cleaned']
    fieldsets = [
        ('Cleaning Info',{'fields':[('machine_used', 'recipe_cleaned', 'lot_num')]}),   
        (None,{'fields':[('start_time')]}),
        (None,{'fields':[('clean_time', 'lab_user')]})
    ]
    inlines = [CleaningUserInline, CleaningEntryInline]

    change_list_template = 'admin/quality/cleaninglog/change_list.html'
    list_per_page = 25
    form = CleaningEntryForm

    class Media:
        js = (
            'admin/js/vendor/jquery/jquery.min.js',
            'admin/js/jquery.init.js',
            'admin/js/list_filter_collaspe.js',
            'admin/js/equipaction_filter.js',
        )
        css = {'all':(
            'admin/css/vendor/select2/select2.css',
            )
        }

I've tried things like overriding save_formset() but I'm not entirely sure this is a Django issue and wondering if it's not due to namespaces?

If I append the rows manually by using the "Add another" button after the ajax call and I click "Save and Continue" or "Save" and check POST the form is submitting all the inlines but it only saves the 1st record listed and all the records I manually added.

My equipaction_filter.js:

$(document).ready(function () {
    ////Row container to append to table...
    row_container = $(
        '<tr class="form-row dynamic-log_entry row1" id="log_entry-0">'+
        '<td class="original"><input type="hidden" name="log_entry-0-id" id="id_log_entry-0-id">'+
        '<input type="hidden" name="log_entry-0-log_entry" id="id_log_entry-0-log_entry"></td>'+
        '<td class="field-cleaning_item"><input type="text" name="log_entry-0-cleaning_item" class="vForeignKeyRawIdAdminField" id="id_log_entry-0-cleaning_item">'+
        '<a href="/admin/quality/equipmentaction/?_to_field=id" class="related-lookup" id="lookup_id_log_entry-0-cleaning_item" title="Lookup"></a></td>'+
        '<td class="field-cleaning_action"><input type="text" name="log_entry-0-cleaning_action" disabled="" id="id_log_entry-0-cleaning_action" style="width: 200px;"></td>'+
        '<td class="field-checked"><input type="checkbox" name="log_entry-0-checked" id="id_log_entry-0-checked"></td>'+
        '<td class="field-na"><input type="checkbox" name="log_entry-0-na" id="id_log_entry-0-na"></td>'+
        '<td class="field-notes"><input type="text" name="log_entry-0-notes" maxlength="512" id="id_log_entry-0-notes" class="vTextField"></td>'+
        '<td class="delete"></td></tr>'
    );

    //// This binds an ".on(select)" event function to the select2 box for the machine_used that
    //// preforms an AJAX call using the machine_fk reference id to get all the EquipmentActions
    //// records for that machine using REST API.
    $("select#id_machine_used").on("select2:select", function(event) {
        machine_fk = event.params.data.id;
        var origin = window.location.origin;
        $.ajax({
        url:origin+'/pyscales/v1/quality/?machine_fk='+machine_fk,
        crossDomain:true,
        dataType:'json',
        contentType: 'application/json',
        //Upon a successful GET request, data is returned in JSON form.
        success: function(data) {
            console.log(data);
            $("#id_log_entry-TOTAL_FORMS").val(data.count);
            $(data.results).each(function (i, item) {
                // console.log(i, item);
                new_row = $(row_container[0].outerHTML.replace(/log_entry-0/,'log_entry-'+i));
                new_row[0].children[1].children[0].value = item.id;
                new_row[0].children[2].children[0].value = item.machine_fk+' | '+item.action;
                var new_table = $('table.cleaning-table');
                new_table.find('tbody').prepend(new_row);
                });
            }
        });
    });
});
})(django.jQuery);

I'd appreciate some feedback or constructive criticism. I'm still new to jQuery and I'm having a difficult time trying to understand the ins and outs. If I need to post something else please let me know. Thanks in advance.


Solution

  • After reviewing the code again and looking into the POST data, per @dirkgroten request, I noticed that on POST the .replace() regular expression I was using wasn't properly mapping the indexes to the forms:

    new_row = $(row_container[0].outerHTML.replace(/log_entry-0/,'log_entry-'+i));

    Should have been:

    new_row = $(row_container[0].outerHTML.replace(/log_entry-0/gi,'log_entry-'+i));

    When the page mapped the data instead of a record for each row within the inline like so:

    log_entry-0-cleaning_item:1
    log_entry-1-cleaning_item:2
    log_entry-2-cleaning_item:3
    

    It was mapping multiple values to the same inline object that jQuery was creating:

    log_entry-0-cleaning_item: 
    [0]:1
    [0]:2
    [0]:3