I am getting familiar with Django more and more. I am building a planning app. Shifts, sites, users are the main ingredients. I really am excited how versatile Django is and most of all how robust everything works. Straigth forward things are going very smooth. But I want to add some intelligence to make live easier. I post here a form template together with the form logic. I have in my Shift model 2 fields ‘user’ and ‘ex_user’.
user = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE)
ex_user = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE, related_name='exuser')
When I edit a shift I want 2 things:
- Only when user is changed, copy initial user to ex_user when saving the record.
- When user is not changed do nothing.
Copy user to ex_user when user changed works fine. Only when user is not changed, ex_user is cleaned to null. I just can’t find why and how this is happening. Would be very thankful if somebody can take a look and advise me how to solve this.
The main problem I want to solve is keep the ex_user value when user is not changed
Thanks for your concern!
#My ShiftForm:
class ShiftForm(forms.ModelForm):
class Meta:
model = Shift
fields = '__all__' # 'ex_user' is not included explicitly
def __init__(self, *args, **kwargs):
super(ShiftForm, self).__init__(*args, **kwargs)
# Store the initial user if the instance already exists
if self.instance and self.instance.pk:
self._initial_user = getattr(self.instance, 'user', None)
def save(self, commit=True):
if self.instance.pk:
# Get the initial user
initial_user = getattr(self, '_initial_user', None)
# Get the current user from the cleaned data
current_user = self.cleaned_data.get('user')
# Update ex_user only if the user has been changed
if initial_user is not None and current_user != initial_user:
self.instance.ex_user = initial_user
elif 'ex_user' not in self.changed_data:
# If ex_user was not part of the changed fields, retain its original value
self.instance.ex_user = getattr(self.instance, 'ex_user', None)
if self.instance.invoiced and not self.instance.copied:
# Logic for handling invoiced and not copied instances
new_shift = Shift(**self.cleaned_data)
new_shift.shift_title += " NEW"
new_shift.source = self.instance.pk
new_shift.invoiced = None
new_shift.id = None
if commit:
new_shift.save()
self.instance.copied = self.instance.id
self.instance.save(update_fields=['copied'])
return new_shift
elif self.instance.invoiced and self.instance.copied:
raise ValueError("This record is locked and cannot be edited or copied.")
return super(ShiftForm, self).save(commit=commit)
And here my template:
# edit_shift.html:
{% extends 'base.html' %}
{% load custom_filters %}
{% load static %}
{% block additional_css %}
<link rel="stylesheet" type="text/css" href="{% static 'css/form.css' %}">
{% endblock %}
{% block content %}
<div class="edit-shift-page">
<form class="form bg" method="post">
<div class="form-buttons">
<div class="edit-id">Id: {{ shift.id }}</div>
<div class="button-container">
<a href="{% url 'new_shift' %}" class="btn btn-sm icon-color"><i
class="fa-solid fa-circle-plus fa-2xl"></i></a>
<button type="submit" class="btn btn-primary btn-sm custom-button" id="saveButton">Save</button>
<button type="button" class="btn btn-primary btn-sm custom-button"
onclick="location.href='{% url 'home' %}'">Cancel
</button>
</div>
</div>
{% csrf_token %}
<div class="form-group">
<label for="{{ form.status.id_for_label }}">{{ form.status.label }}</label>
<select class="form-select round small-size select-value form-small"
id="{{ form.status.id_for_label }}"
name="{{ form.status.html_name }}">
{% for choice in form.status.field.choices %}
<option value="{{ choice.0 }}"
{% if choice.0 == form.status.value %}selected{% endif %}>
{{ choice.1 }}
</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="{{ form.shift_title.id_for_label }}">{{ form.shift_title.label }}</label>
<input type="text" class="form-control round form-control-sm shift-title" id="shift_title"
name="shift_title"
value="{{ shift.shift_title }}">
</div>
<div class="form-group">
<label for="{{ form.job.id_for_label }}">{{ form.job.label }}</label>
<select class="form-select round form-small small-size select-value" id="job" name="job">
{% for choice in form.job.field.choices %}
<option value="{{ choice.0 }}"
{% if choice.0 == shift.job.id %}selected{% endif %}>{{ choice.1 }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="{{ form.start_date.id_for_label }}">{{ form.start_date.label }}</label>
<input type="date" class="form-control round form-control-sm small-size" id="start_date"
name="start_date"
value="{{ shift.start_date|date:'Y-m-d' }}">
</div>
<div class="form-group">
<label for="{{ form.start_time.id_for_label }}">{{ form.start_time.label }}</label>
<input type="time" class="form-control round form-control-sm small-size" id="start_time"
name="start_time"
value="{{ shift.start_time }}">
</div>
<div class="form-group">
<label for="{{ form.end_time.id_for_label }}">{{ form.end_time.label }}</label>
<input type="time" class="form-control round form-control-sm small-size" id="end_time" name="end_time"
value="{{ shift.end_time }}">
</div>
<div class="samen round">
<div class="form-group">
<label for="{{ form.site.id_for_label }}">{{ form.site.label }}</label>
<select class="form-select round select-value form-select-sm select2-enable"
id="{{ form.site.id_for_label }}"
name="{{ form.site.name }}"
data-placeholder="Selecteer locatie">
<option value="" {% if not form.site.value %}selected{% endif %}>Select an option</option>
{% for site in sites %}
<option value="{{ site.id }}"
{% if form.site.value == site.id %}selected{% endif %}>{{ site }}</option>
{% endfor %}
</select>
</div>
<div class="contactdetails">
<b>Tel:</b> <span id="siteDetails.phone">{{ shift.site.phone }}</span>
</div>
</div>
<div class="samen round">
<div class="form-group">
<label for="{{ form.user.id_for_label }}">{{ form.user.label }}</label>
<select class="form-select round select-value form-select-sm select2-enable"
id="{{ form.user.id_for_label }}"
name="{{ form.user.name }}"
data-placeholder="Selecteer zorgverlener">
<option value="" {% if not form.user.value %}selected{% endif %}>Select an option</option>
{% for user in users %}
<option value="{{ user.id }}"
{% if form.user.value == user.id %}selected{% endif %}>{{ user.user_profile }}</option>
{% endfor %}
</select>
</div>
<div class="contactdetails">
{% if shift.user %}
<b>Email:</b>
<a href="mailto:{{ shift.user.email }}" id="userDetails.email">{{ shift.user.email }}</a>
{% else %}
<b>Email:</b> <a href="#" id="userDetails.email"></a>
{% endif %}
/
<b>Tel:</b> <span id="userDetails.phone">{% if shift.user %}
{{ shift.user.user_profile.phone }}{% endif %}</span>
</div>
<div class="ex-user"><b>ex_user: </b>{{ form.instance.ex_user_profile|default_if_none:"No ex user selected." }}</div>
</div>
<div class="form-group">
<label for="{{ form.user_notes.id_for_label }}">{{ form.user_notes.label }}</label>
<textarea class="form-control form-control-sm round" id="user_notes"
name="user_notes">{{ shift.user_notes }}</textarea>
</div>
<div class="form-group">
<label for="{{ form.customer_notes.id_for_label }}">{{ form.customer_notes.label }}</label>
<textarea class="form-control form-control-sm round" id="customer_notes"
name="customer_notes">{{ shift.customer_notes }}</textarea>
</div>
<div class="form-group">
<label for="{{ form.admin_notes.id_for_label }}">{{ form.admin_notes.label }}</label>
<textarea class="form-control form-control-sm round" id="admin_notes"
name="admin_notes">{{ shift.admin_notes }}</textarea>
</div>
<div class="form-group">
<!-- Hidden input for 'invoiced' -->
<input type="hidden" id="invoiced" name="invoiced" value="{{ shift.invoiced|default_if_none:'' }}">
</div>
<div class="form-group">
<!-- Hidden input for 'copied' -->
<input type="hidden" id="copied" name="copied" value="{{ shift.copied|default_if_none:'' }}">
</div>
<div class="form-group">
<!-- Hidden input for 'source' -->
<input type="hidden" id="source" name="source" value="{{ shift.source|default_if_none:'' }}">
</div>
<div class="form-group">
<!-- Hidden input for 'update' -->
<input type="hidden" id="update" name="update" value="{{ shift.update|default_if_none:'' }}">
</div>
<!-- REFERAL ONLY INFO -->
<div class="info-row">
<div class="info-item">
<div>I: {{ form.invoiced.value|default_if_none:"" }}</div>
</div>
<div class="info-item">
<div>C: {{ form.copied.value|default_if_none:"" }}</div>
</div>
<div class="info-item">
<div>S: {{ form.source.value|default_if_none:"" }}</div>
</div>
<div class="info-item">
<div>U: {{ form.update.value|default_if_none:"" }}</div>
</div>
</div>
</form>
<script>
$(document).ready(function () {
$('.select2-enable').select2({
placeholder: "Select an option",
allowClear: true
});
});
</script>
<script>
$(document).ready(function () {
// Function to check the copied field and disable save
function checkCopiedAndDisableSave() {
var copiedValue = $('#copied').val();
if (copiedValue !== '') {
$('#saveButton').prop('disabled', true);
} else {
$('#saveButton').prop('disabled', false);
}
}
// Check on page load
checkCopiedAndDisableSave();
// Check when form changes
$('form').change(checkCopiedAndDisableSave);
});
</script>
</div>
{% endblock %}
To be complete also my view:
#shift_edit view:
def shift_edit(request, pk=None):
shift = None
if pk:
try:
shift = Shift.objects.get(pk=pk)
except Shift.DoesNotExist:
return HttpResponseNotFound('Shift not found')
# Fetching sites and users for context
sites = Site.objects.select_related('customer').all()
users = User.objects.select_related('user_profile__job').all()
if request.method == 'POST':
form = ShiftForm(request.POST, instance=shift)
if form.is_valid():
form.save() # Save the form (update or create new shift)
return redirect('home') # Always redirect to 'home' after saving
else:
form = ShiftForm(instance=shift)
context = {
'form': form,
'shift': shift,
'sites': sites,
'users': users,
}
return render(request, 'forms/edit_shift.html', context)