I have implemented an auto save feature in my Django 4 web application using JavaScript and Django views.py function as the end point. I got it to save the form / model instances to the database, bypassing the Django forms validation. The problem now is that it keeps saving new instances at every interval.

The correct behavior is to create the first instance and at subsequent intervals only update the database with new data if there has been changes since the last auto save.

The problem is I don't know how to store the id of the model instance that was just created, so that at every call of the auto save JavaScript I can check if this auto save request a new request or existing request. I have not tried anything yet at this point. Getting it to this point where I can save the unchecked form/model instance already took a lot of time for me.

Below is the JavaScript code:

$(document).ready(function() {
  var lastSavedData = $('#dormForm').serialize(); // Serialize the initial form data

  function autoSave() {
      var currentData = $('#dormForm').serialize(); // Serialize the current form data

      // Check if the current data is different from the last saved data
      if (currentData !== lastSavedData) {
          $.ajax({
              url: '/autosave/',
              type: 'POST',
              data: currentData,
              success: function(response) {
                  if (response.status === 'success') {
                      console.log('Data saved');
                      lastSavedData = currentData; // Update lastSavedData with the new data
                  } else {
                      console.log('Error during auto save.');
                  }
              },
              error: function() {
                  console.log('Error during auto save.');
              }
          });
      } else {
          console.log('No changes to save.');
      }
  }

  setInterval(autoSave, 30000); // Trigger the autoSave function every 30 seconds
});

And this is the Django 4 views.py function:

@require_POST
def auto_save(request):
    # Get the last saved instance ID from the session
    last_saved_instance_id = request.session.get('last_saved_instance_id')

    responses = {}

    # Check if last_saved_instance_id exists and retrieve the instance
    if last_saved_instance_id:
        instance = get_object_or_404(LossEventPage1, pk=last_saved_instance_id)
    else:
        instance = LossEventPage1()  # Create a new instance

    # Instantiate LossEventPage1Form with the POST data and the retrieved instance
    form_one = LossEventPage1Form(request.POST, request.FILES, instance=instance)

    if request.method == "POST":
        for field_name in form_one.fields:
            field = form_one.fields[field_name]
            try:
                if field_name in request.POST or field_name in request.FILES:
                    # For files, use FILES, for other data, use POST
                    field_value = request.FILES.get(field_name) if isinstance(field, forms.FileField) else request.POST.get(field_name)
                    if field_value is not None:
                        # Clean and save the value for the field
                        field_value = field.clean(field_value)
                        setattr(instance, field_name, field_value)
                elif field.required:
                    # If the field is required and not in POST, return an error response
                    responses['form_one'] = {
                        'status': 'error',
                        'reason': f'Missing required field: {field_name}'
                    }
                    continue  # Skip saving this instance
            except ValidationError as e:
                # If validation fails, return an error response
                responses['form_one'] = {
                    'status': 'error',
                    'reason': f'Validation error on field {field_name}: {e.messages}'
                }
                continue  # Skip saving this instance

        instance.is_draft = True  # Set draft flag
        instance.save()  # Save the instance without calling form.save()

        # You might need to handle the setting of ManyToMany fields here, after saving the instance

        if 'form_one' not in responses:
            responses['form_one'] = {'status': 'success'}
    else:
        responses['form_one'] = {'status': 'skipped', 'reason': 'No data to save for form_one'}

    # Query related models and update their instances if they exist
    related_models = [LossEventPage2, LossEventPage4, LossEventPage5]

    for related_model in related_models:
        # Query the related model's one-to-one field to find the associated instance
        related_instance = related_model.objects.filter(lossEventPage1=instance).first()

        # Instantiate the related model's form with the POST data and the retrieved related instance
        form = related_model.form_class(request.POST, request.FILES, instance=related_instance)

        if request.method == "POST":
            for field_name in form.fields:
                field = form.fields[field_name]
                try:
                    if field_name in request.POST or field_name in request.FILES:
                        # For files, use FILES, for other data, use POST
                        field_value = request.FILES.get(field_name) if isinstance(field, forms.FileField) else request.POST.get(field_name)
                        if field_value is not None:
                            # Clean and save the value for the field
                            field_value = field.clean(field_value)
                            setattr(related_instance, field_name, field_value)
                    elif field.required:
                        # If the field is required and not in POST, return an error response
                        responses[related_model.__name__] = {
                            'status': 'error',
                            'reason': f'Missing required field: {field_name}'
                        }
                        continue  # Skip saving this instance
                except ValidationError as e:
                    # If validation fails, return an error response
                    responses[related_model.__name__] = {
                        'status': 'error',
                        'reason': f'Validation error on field {field_name}: {e.messages}'
                    }
                    continue  # Skip saving this instance

            if related_instance:
                related_instance.save()  # Save the related instance

            if related_model.__name__ not in responses:
                responses[related_model.__name__] = {'status': 'success'}
        else:
            responses[related_model.__name__] = {'status': 'skipped', 'reason': f'No data to save for {related_model.__name__}'}

    return JsonResponse(responses)
1

There are 1 best solutions below

2
Proud Wadhwa On BEST ANSWER

To achieve the behavior you described, where you only want to create a new instance if there are changes since the last auto-save, you can follow these steps:

1. Store the ID of the Last Saved Instance: When you initially create the instance, you should store the ID of the created instance in a session variable, a cookie, or in a JavaScript variable on the client side. Let's use a session variable in this example.

In your Django view where you first save the instance:

request.session['last_saved_instance_id'] = instance.id

2. Modify the JavaScript to Check for Changes: In your JavaScript, you'll need to compare the form data with the data you last saved to determine if changes have been made. If changes have been made, then you send an auto-save request, otherwise, you skip it.

Here's a modified JavaScript code to get you started:

$(document).ready(function() {
    var lastSavedData = $('#dormForm').serialize(); // Serialize the initial form data

    function autoSave() {
        var currentData = $('#dormForm').serialize(); // Serialize the current form data

        // Check if the current data is different from the last saved data
        if (currentData !== lastSavedData) {
            $.ajax({
                url: '/autosave/',
                type: 'POST',
                data: currentData,
                success: function(response) {
                    if (response.status === 'success') {
                        console.log('Data saved');
                        lastSavedData = currentData; // Update lastSavedData with the new data
                    } else {
                        console.log('Error during auto save.');
                    }
                },
                error: function() {
                    console.log('Error during auto save.');
                }
            });
        } else {
            console.log('No changes to save.');
        }
    }

    setInterval(autoSave, 30000); // Trigger the autoSave function every 30 seconds
});

3. Modify the Django View to Handle Auto-Save: In your Django view, you should check if the posted data is different from what's already stored in the database. If there are changes, update the existing instance, otherwise, do nothing.

Your view should look something like this:

@require_POST
def auto_save(request):
    # Get the last saved instance ID from the session
    last_saved_instance_id = request.session.get('last_saved_instance_id')

    responses = {}

    # Check if last_saved_instance_id exists and retrieve the instance
    if last_saved_instance_id:
        instance = get_object_or_404(YourModel, pk=last_saved_instance_id)
    else:
        instance = YourModel()  # Create a new instance

    # ... (your existing code for populating and saving fields)

    instance.save()  # Save the instance without calling form.save()

    # ... (your code for handling ManyToMany fields, if needed)

    responses['status'] = 'success'

    return JsonResponse(responses)

This approach should help you achieve your desired behavior, where you only create a new instance if there have been changes since the last auto-save.