0

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.

Dan2theR
  • 159
  • 2
  • 11
  • Does your js also change the formsets [management form](https://docs.djangoproject.com/en/stable/topics/forms/formsets/#understanding-the-managementform) to update the number of forms (TOTAL_FORMS)? – dirkgroten Sep 10 '19 at 12:40
  • Yes I'm doing that with this line: $("#id_log_entry-TOTAL_FORMS").val(data.count); – Dan2theR Sep 10 '19 at 13:23
  • But that’s not taking into account manually added rows. And the index on your new forms is also just taken from data, not taking into account if other rows already exist with the same index. Check again in the browser dev tools what data is being submitted. – dirkgroten Sep 10 '19 at 13:26

1 Answers1

0

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
Dan2theR
  • 159
  • 2
  • 11