Updating a dataset with ModelForm won't save...

So here it is, my daily Problem;-)

let’s take this model:

class Employments(models.Model):

    employee = models.ForeignKey(Employees, on_delete=models.CASCADE)
    status = models.CharField(max_length=50)
    work_time_model = models.CharField(max_length=50)
    weekly_hours = models.FloatField(default=41)
    percent = models.FloatField(default=100)
    duty_begin = models.DateField()
    duty_end = models.DateField(null=True, blank=True)
    keep_data = models.BooleanField(default='0')

which can be populated by this form:

class EmploymentsForm(ModelForm):

    class Meta:
        model = Employments
        fields = '__all__'

    employee = forms.CharField(widget=forms.HiddenInput)

    status = forms.ModelChoiceField(queryset=Status.objects.all(),
                                    empty_label="Status",
                                    required=False,
                                    label='Status',
                                    widget=forms.Select(attrs={
                                     'class': "form-select border border-1 border-black text-center"
                                              "fw-bold m-1",
                                     'required': 'True',
                                     }))

    work_time_model = forms.ModelChoiceField(queryset=ArbeitszeitModell.objects.all(),
                                             # empty_label="Arbeitszeitmodell",
                                             label='Arbeitszeitmodell',
                                             required=False,
                                             widget=forms.Select(attrs={
                                                'class': "form-select border border-1 border-black "
                                                         "text-center fw-bold m-1",
                                                'required': 'True',
                                             }))

    weekly_hours = forms.FloatField(label='Wochenarbeitszeit(h)', required=False, widget=forms.NumberInput(attrs={
        'class': "form-control border border-1 border-black fw-bold text-center m-1",
        'placeholder': 'Wochenarbeitszeit(h)',
        'required': 'True',
    }))

    percent = forms.FloatField(label='Prozent(%)', required=False, widget=forms.NumberInput(attrs={
        'class': "form-control border border-1 border-black fw-bold text-center m-1",
        'placeholder': 'Prozent',
        'required': 'True',
    }))

    duty_begin = forms.DateField(label='Diensteintritt', required=False, widget=forms.DateInput(attrs={
        'class': "form-control border border-1 border-black text-center fw-bold m-1",
        'placeholder': 'Diensteintritt',
        'title': 'Diensteintritt',
        'type': 'text',
        'onfocus': "(this.type = 'date')",
        'required': 'True',
    }))

    duty_end = forms.DateField(label='Dienstende', required=False, widget=forms.DateInput(attrs={
        'class': "form-control border border-1 border-black text-center fw-bold m-1",
        'placeholder': 'Dienstende',
        'title': 'Dienstende',
        'type': 'text',
        'onfocus': "(this.type = 'date')",
    }))

    KEEP_DATA_CHOICES = (
        ["True", "Ja"],
        ["False", "Nein"],
    )

    keep_data = forms.BooleanField(label='Daten behalten?', required=False,
                                   widget=forms.RadioSelect(choices=KEEP_DATA_CHOICES, attrs={
                                       'class': 'border border-black border-1 rounded-2 bg-secondary-subtle m-1',
                                       'required': 'True',
                                   }))

    def clean_duty_end(self):
        duty_end = self.cleaned_data['duty_end'] if self.cleaned_data['duty_end'] else None
        return duty_end

Code for creating and altering dataset is here:

...
        if 'save_form' in request.POST:
            id_alter = request.POST['id']
            employee = Employees.objects.get(pk=id_alter)

            if 'save_work' in request.POST:
                form = EmploymentsForm(request.POST)
                if form.is_valid():
                    form.save()
                    context['success'] = 'Dataset succesfully created'
                else:
                    context['error'] = 'error'

            if 'update_work' in request.POST:
                form = EmploymentsForm(request.POST, instance=employee)
                if form.is_valid():
                    form.save()
                    context['success'] = 'Dataset updated succesfully'
                else:
                    context['error'] = 'error'
...

so “save_work” creates a dataset successfully, but “update_work” doesn’t…but delivers the “success” message, so “if form.is_valid” has been passed without error message. But the data is simply not updated in database.

If i print the form.cleaned_data of “update_work” it looks ok to me…:


{'employee': '1', 'status': <Status: Soldat (BS)>, 'work_time_model': <ArbeitszeitModell: Vollzeit>, 'weekly_hours': 12.0, 'percent': 12.0, 'duty_begin': datetime.date(2023, 8, 24), 'duty_end': datetime.date(2023, 8, 31), 'keep_data': True}

This doesn’t look like a valid dict element, right? Base on your model, the status accept string value, so I’d think it should be quoted.

'status': <Status: Soldat (BS)>,

Sounds legit…i will check this and where it comes from! Thx

Hm…if i strip all specifications besides the “classMeta” from the Employment form to get rid of potentially disturbing settings in my customized form, I get a optically ugly form with the right prefilled data and THIS printed form.clean_data:

{'employee': <Employees: Alekhov>, 'status': 'Soldat (BS)', 'work_time_model': 'Vollzeit', 'weekly_hours': 12.0, 'percent': 12.0, 'duty_begin': datetime.date(2023, 8, 24), 'duty_end': datetime.date(2023, 8, 31), 'keep_data': True}

But the data get’s still not saved to database without error message…

As “employee” is the last tuple left in the cleaned_data, i think THERE is somehow the problem, but how can i get rid of it ?

So part of the problem is the mismatch between the form fields and their related model fields.

For example, your Employments model has defined:

But your form defines:

Which defines that the form is going to return an instance of the identified model.

If those model fields are supposed to be instances of those identified models, then your Employment model should defined those fields as ForeignKey fields and not as CharFields.

Side note: By common convention, database tables - and therefore Django models - should be named in the singular and not plural. I would suggest you consider renaming your model to Employment.

Yeah, I know of this convention…or I should say I know this convention from other programming languages like Java…so for a “clean code”, You are absolutely right. As this project (if it will ever do) is going to run on an intranet Network with me and two other guys as admin, i decided to ignore the convention because to myself, it gives me more logic to use plural terms for data-tables, because there is in most cases more than one dataset stored in a table :wink: But besides of that, you are generally right, of course.

Back to topic:
I understand Your remarks concerning different field-types in model and form…but you can’t set model items to be ChoiceFields…on the other hand I can not understand, why i should set the mentioned fields to Foreign key, as they are no foreign keys in database structure ???

If the values that are valid in Employment.status and Employment.work_time_model are only the values in Status and ArbeitszeitModell respectively, then those fields in Employment should be foreign keys in the database.

It’s a data integrity issue - with those fields being arbitrary text fields, nothing ensures that the entries in those fields are represented by data in those other models.

Side note:

You can create the choices of a ChoiceField from a queryset, but from a data-modelling perspective, that’s really a sub-optimal decision.

So, i found out what the problem was. Problem (like always) was in front of keyboard…

the data was not updating, because

form = EmploymentsForm(request.POST, instance=employee)

was sending data from an instance of the model “Employees”:

employee = Employees.objects.get(pk=id_alter)

not of the model “Employments”…after correcting this, everything is fine :slight_smile:

work = Employments.objects.get(employee_id=id_alter)
form = EmploymentsUpdateForm(request.POST, instance=work)

BUT, i still have trouble with two forms that are designed as MultipleChoiceCheckboxes.
They behave the same (get a "is sucessfully saved message, caused by valid form.data) but won’t save.
I bet, there is also the “instance” part of the code the problem. These two models have “only” auto-generated “through” tables.

Right now i just can’t manage to adress the instance for the

form =(request.Post, instance=???)

properly, I’m afraid.

So if one has this model with M2M-Relationship:

class Functions(models.Model):
    function = models.CharField(max_length=100, unique=True)
    employee = models.ManyToManyField(Employees)

    def __str__(self):
        return self.function

Django generates a through_table automatically, in this case named: app_name_functions_employee

If i try to implement an update/altering process for this part of database, i generelly act like in other cases:

providing a form:

class FunctionsForm(ModelForm):

    class Meta:
        model = Functions
        fields = '__all__'

    employee = forms.ModelChoiceField(queryset=Employees.objects.all(), widget=forms.HiddenInput)
    function = forms.ModelMultipleChoiceField(queryset=Functions.objects.all(), label='',
                                              required=False, widget=forms.CheckboxSelectMultiple(attrs={
                                                  'class': "fw-bold",
                                              }))

calling the form with prefilled data looks like:

        elif 'alterFunctions' in request.POST:
            id_alter = request.POST['alterFunctions']
            form = FunctionsForm({'employee': id_alter,
                                  'function': Functions.objects.filter(employee=id_alter)})
            context['switch'] = 'functions'

After doing this, I get exactly what I want: a page with Checkbox-fields for all entries, precheckd for entries that have been already made!

But If I alter the entries and want to save it to database, I also get an success message, because the data is valid, but in the end, no changes are saved. Like in the Above case in beginning the Thread, the instance=??? part is in my opinion the problem. But how can i tell Django to do

function = Functions.objects.get("proper term for employee_id in auto-generated through-field"=id_alter)

?

I’d need to see the complete view where this is happening.

There are two things I’d be looking for:

If you’re using multiple forms on a page, you must use the prefix option for “all-but-1” form. (I tend to use a prefix for all forms in that situation.)

If you’re using commit=False, there’s a “gotcha” to be aware of - see the docs at Creating forms from models | Django documentation | Django

But in the general case, this has always been a “it just works” situation for me.

Hi again,

concerning commit=False, I am aware of the problem with saving data sequentially and using save_m2m(), because of I feel like having read nearly all Stack-Overflow threads near this topic up to now :stuck_out_tongue: …but I am not using commit=False :wink:

And I am dealing with several forms in the same view, but never simultanously in templates. The user is only filling/saving one form at a time, so I only save one form at a time…

Update: Don’t know if it helps, but if i try it with a record, which has no entries yet in functions and therefore it is no updating but an entering data process, i get (after performing FunctionsForm(request.POST) without instance= line) an "Employees" object is not iterable error when reaching form.save()

If I generate the “naked” Django form for model Functions without any specifications, I get exactly the opposite of what I wan’t:

I get a queryset of all function-values displayed in one field and a multi-select box, where i can assign the whole queryset to several employees. But I wan’t to assign no or one or several functions to ONE employee. So, the definition for the M2M-Relationship is in model Functions…I think the solution could be to revert/inverse the way, django handles the form-generation. I have found some Stack-Overflow postings that semm to deal with similar problems. but either I#m too silly to convert the provided solutions for my problem or the solutions are outdated and won’t work for actual Django-Versions…

So the way Django is generating form without spcifications:

class FunctionsForm(ModelForm):

    class Meta:
        model = Functions
        fields = '__all__'

leads to:
1

And with specifications I get the form like I need it but the functionality behind does not change just by editing the form, am I right?

class FunctionsForm(ModelForm):

    class Meta:
        model = Functions
        fields = '__all__'

    employee = forms.ModelChoiceField(queryset=Employees.objects.all(), widget=forms.HiddenInput)
    function = forms.ModelMultipleChoiceField(queryset=Functions.objects.all(), label='',
                                              required=False, widget=forms.CheckboxSelectMultiple(attrs={
                                                  'class': "fw-bold",
                                              }))

leads to:

I’m sorry, I’m lost here among all the different fragments of things here.

The last clear statement I see that describes the current issue is:

Is this still the issue looking to be addressed?

If so, then:

Regarding this:

The issue exists around rendering and submitting forms. If you render more than one form on a page (regardless of the number of templates being used), you must use the prefix attribute to prevent conflicts. This is true even if you’re only planning to save data from one of the forms on that page.

I totally understand, that it can be hard and maybe impossible to help here without overview about used code. I will check this up and provide a set of the used .py files that I am running right now.

So, let’s start from scratch:

Here are my models in models.py:

Model Employees:

class Employees(models.Model):

    salutation = models.CharField(max_length=50)
    title_rank = models.CharField(max_length=50, null=True, blank=True)
    rank_short = models.CharField(max_length=20, null=True, blank=True)
    rank_group = models.CharField(max_length=50, null=True, blank=True)
    last_name = models.CharField(max_length=100)
    first_name = models.CharField(max_length=100)
    prefix_title = models.CharField(max_length=50, null=True, blank=True)
    suffix_title = models.CharField(max_length=50, null=True, blank=True)
    street = models.CharField(max_length=100)
    building_number = models.CharField(max_length=10)
    address_supplement = models.CharField(max_length=100, null=True, blank=True)
    postal_code = models.CharField(max_length=10)
    city = models.CharField(max_length=100)
    country = models.CharField(max_length=100, default="Deutschland")
    employee_number = models.CharField(max_length=10, unique=True)
    date_of_birth = models.DateField(null=True, blank=True)
    cellphone = models.CharField(max_length=25)
    phone = models.CharField(max_length=25, null=True, blank=True)
    email = models.CharField(max_length=50)
    key_number = models.CharField(max_length=15, null=True, blank=True)
    chip_number = models.CharField(max_length=10, null=True, blank=True)
    locker_room = models.CharField(max_length=10, null=True, blank=True)
    locker_number = models.CharField(max_length=10, null=True, blank=True)
    office = models.CharField(max_length=10, null=True, blank=True)
    computer = models.CharField(max_length=20, null=True, blank=True)
    company_email = models.CharField(max_length=100, null=True, blank=True)
    company_phone = models.CharField(max_length=10, null=True, blank=True)
    company_cellphone = models.CharField(max_length=25, null=True, blank=True)

    def __str__(self):
        return self.last_name

    # Berechnung Alter
    def age(self):
        return int((datetime.now().date() - self.date_of_birth).days / 365.25)

    class Meta:
        verbose_name_plural = 'Mitarbeiter'
        ordering = ['last_name']

and model Functions:

class Functions(models.Model):

    function = models.CharField(max_length=100, unique=True)
    employee = models.ManyToManyField(Employees)

    def __str__(self):
        return self.function

    class Meta:
        verbose_name_plural = 'Funktionen'
        ordering = ['function']

Now, the whole thing is about to assign ‘None’, one or several functions to one employee!

my form for the Funtionsmodel is

in forms.py:

class FunctionsForm(ModelForm):

    class Meta:
        model = Functions
        fields = '__all__'

    employee = forms.ModelChoiceField(queryset=Employees.objects.all(), widget=forms.HiddenInput)
    function = forms.ModelMultipleChoiceField(queryset=Functions.objects.all(), label='',
                                              required=False, widget=forms.CheckboxSelectMultiple(attrs={
                                                  'class': "fw-bold",
                                              }))

And in views.py it is handled like:

def alter(request):

    context = {} 
    id_alter = ''
    form = ''

    if request.method == 'GET':
        return render('home.html', context)

    else:

       ...(no interaction with problem)...

        if 'alterFunctions' in request.POST:
            id_alter = request.POST['alterFunctions']
            context['switch'] = 'functions'
            form = FunctionsForm({'employee': id_alter,
                                  'function': Functions.objects.filter(employee=id_alter)}

        context['special_header'] = employee_header(id_alter)

    context['form'] = form
    return render(request, 'alter.html', context)
def show(request):

    current_user = request.user 
    employee_queryset = Employees.objects.all().order_by('last_name').values().distinct()
    today = datetime.date.today()
    filters = Q()  # Initialisierung Filter-Dictionary
    context = {}  # Initialisierung context
...
            if 'save_function' in request.POST:
                # reset = Functions.objects.filter(employee=id_alter)
                # reset.delete()
                form = FunctionsForm(request.POST)
                if form.is_valid():
                    form.save()
                    context['success'] = 'Funktionen wurden aktualisiert'
                else:
                    print(form.cleaned_data)
                    context['error'] = 'Formulardaten fehlerhaft'
...

    return render(request, 'show.html', context)

Ok, I think I’m close, but I’m still a little unclear of what you want this form to present.

Do you want a form to assign one function to a number of employees, or are you looking to assign a number of functions to one employee?

Because if the latter, you want to turn your perspective around and make this a form based upon the Employee model and not the Function model.

That’s not to say that this would be a model form, if you’re looking to make the employee field selectable.

But I would create this as a regular form (not a model form) with the fields as you have them shown in your existing form.

The view would then get the employee object and assign the selected function to that employee.

Yeah, that’s why i wrote the post 6 hours ago… exactly what I am thinking at the moment.But I don’ t want to have the foreign/M2M key in the model Employees for several reasons. According to Django documentation, it “doesn’t matter” where the M2M relation is located. but even if it doesn’t matter, i have to handle it different than I am doing at them moent…and i don’t know how :frowning:

Accessing an M2M is symmetrical from both sides, as shown in the docs and examples at Many-to-many relationships | Django documentation | Django. Notice how the examples demonstrate accessing Articles from Publications and the reverse, in essentially the same way. It’s effectively the same method as accessing a reverse-foreign key relationship.

Ok, i found a way to achieve my goal, it works now as intended. but i have another question related to the former one: If i pick several “Functions” in the form, as soon as the sum of chars of the values reach the max_length value of the field on the model definition, i get an automatetd warning by django. This doesn’t matter, because of: the way the data is processed afterwards makes the problem non-existant…but can i somehow supress this warning? help_text=None is not sufficient :wink:

I’m not following what you’re trying to describe here, nor can I think of any reason why the max_length value of a field would affect (or be affected by) the number of entries in a many-to-many selection.

Can you provide more details about the situation along with the relevant code?