Issue
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)
Solution
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.
Answered By - Proud Wadhwa
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.