WileECoyote
WileECoyote

Reputation: 1

ModelForm in Django doesn't know attribute is already filled - NOT NULL constraint failed: teretana_pretplatnik.first_name

I am making a Django Gym app following this tutorial https://www.youtube.com/watch?v=Mag1n3MFDFk&list=PL2aJidc6QnyOe-fp1m4yKHjcInCRTF53N&index=3 I wanted to write a form page that the user (once logged in) can fill and become a subscriber. In the tutorial he used a new model Enrollment to fill the data with but I have a model Pretplatnik (Subscriber in english) that has a OneToOne relationship with django user model. Also two other models that are FK to Pretplatnik for Plan that the user can subscribe to and a model for Trainer that the user can choose.

models.py

from django.db import models
from django.contrib.auth.models import User


# Create your models here.

class Oznaka(models.Model):
    naziv = models.CharField(max_length=200)

    def __str__(self):
        return self.naziv

class Plan(models.Model):
    naziv=models.CharField(max_length=150)
    cijena=models.IntegerField()
    max_clanova=models.IntegerField(null=True)
    oznake=models.ManyToManyField(Oznaka, default=None, blank=True, related_name='oznake')

    def __str__(self):
        return '%s' % self.naziv


class Trener(models.Model):
    ime=models.CharField(max_length=150)
    image=models.ImageField(upload_to='static/assets/img/team', default=None)

    def __str__(self):
        return self.ime

class Pretplatnik(models.Model):
    korisnik=models.OneToOneField(User, on_delete=models.CASCADE)
    trener=models.ForeignKey(Trener, on_delete=models.CASCADE, related_name='pretplatnici')
    plan=models.ForeignKey(Plan, on_delete=models.CASCADE, null=True)
    datum_r=models.DateField(blank=True,null=True)
    spol=models.CharField(max_length=25, default=None)
    adresa=models.CharField(max_length=150)

    def __str__(self):
        return str(self.korisnik)

class Pretplata(models.Model):
    pretplatnik=models.ForeignKey(Pretplatnik, on_delete=models.CASCADE, null=True)
    plan=models.ForeignKey(Plan, on_delete=models.CASCADE, null=True)

class CustomModelName(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)

from django import forms
from django.forms import ModelForm
from teretana.models import *

class PretplatnikForm(forms.ModelForm):
    plan = forms.ModelChoiceField(queryset=Plan.objects.all(),
                                    to_field_name = 'naziv',
                                    empty_label="Select Plan")
    
    trener = forms.ModelChoiceField(queryset=Trener.objects.all(),
                                    to_field_name = 'ime',
                                    empty_label="Select Trainer")

    class Meta:
        model = Pretplatnik
        fields = ['trener', 'plan', 'datum_r', 'spol', 'adresa']
        GENDER_CHOICES = (
            ('', 'Select a Gender'),
            ('Female', 'Female'),
            ('Male', 'Male'),
        )
        widgets = {
            'trener':forms.TextInput(attrs={'class':'form-control','placeholder':'Select Trainer'}),
            'plan':forms.TextInput(attrs={'class':'form-control','placeholder':'Select Plan'}),
            'datum_r':forms.DateInput(attrs={'class':'form-control','placeholder':'Enter Date of Birth'}),
            'spol':forms.Select(choices=GENDER_CHOICES,attrs={'class': 'form-control'}),
            'adresa':forms.TextInput(attrs={'class':'form-control','placeholder':'Enter Address'}),
        }
        labels={
            'plan':'Select Plan',
            'trener':'Select Trainer',
            'datum_r':'Datum_r',

        }

views.py

def enroll(request):

    if request.method == 'POST':
        form = PretplatnikForm(request.POST)
        if form.is_valid():
            form.save()


    form = PretplatnikForm()
    return render(request, 'enroll.html', {'form': form})

enroll.html

{% extends 'base.html' %} 
{% block title %} Enrollment for Gym {% endblock title%}
{% block head %}

<h2>Join The Best Gym In Rijeka</h2>
    

<div class="container mt-2">
  <div class="row">
    <div class="col-md-3"></div>

    <div class="col-md-6">
      {% for message in messages %}
      <div
        class="alert alert-{{message.tags}} alert-dismissible fade show"
        role="alert"
      >
        <strong></strong> {{message}}
        <button
          type="button"
          class="btn-close"
          data-bs-dismiss="alert"
          aria-label="Close"
        ></button>
      </div>
      {% endfor %}



      <form action="/enroll" method="post">{% csrf_token %}
        {% csrf_token %}
        <div class="form-group">
    
          <label class="form-label">{{form.trener.label_tag}}</label>
          {{form.trener}}
      </div>



      <form action="/enroll" method="post">{% csrf_token %}
        {% csrf_token %}
      <div class="form-group">
    
        <label class="form-label">{{form.plan.label_tag}}</label>
        {{form.plan}}
    </div>



  <form action="/enroll" method="post">{% csrf_token %}
    {% csrf_token %}
  <div class="form-group">

    {{form.spol}}
</div>

<br>

<form action="/enroll" method="post">{% csrf_token %}
  {% csrf_token %}
<div class="form-group">
  {{form.adresa}}
</div>

<br>

<form action="/enroll" method="post">{% csrf_token %}
  {% csrf_token %}
<div class="form-group">
  {{form.datum_r}}
</div>

<br>
        <div class="form-group">
          <input
            type="number"
            class="form-control mt-2"
            value="{{user.username}}"
            name="PhoneNumber"
            placeholder="Enter Your Number"
            readonly
            required
          />
        </div>

            </div>


        <br>
        <div class="d-grid gap-2">
          <button class="btn btn-warning" type="submit">Enroll</button>
        </div>
      </form>
    </div>

    <div class="col-md-3"></div>
  </div>
</div>

{% endblock head %}

He is using HTML form to get the data but I've tried to and it doesn't work for FK (or it does but I don't know how to implement it) so I've tried with ModelForm. But now I get this error since I presume the ModelForm doesn't know that the django user data which is PK for Pretplatnik is already filled (the user cannot fill the form unless logged in). I've tried to change the views file like I've seen on another question posted here but then I get the error: AttributeError at /enroll

'Pretplatnik' object has no attribute 'is_valid'

views.py


def enroll(request):
    if not request.user.is_authenticated:
        messages.warning(request,"Please Login and Try Again")
        return redirect('/login')


    if request.method == 'POST':
        form = PretplatnikForm(request.POST)
        form = form.save(commit=False)
        if form.is_valid():
            form.save()


    form = PretplatnikForm()
    return render(request, 'enroll.html', {'form': form})

EDIT:

I forgot to add the forms.py so here it is

forms.py

from django import forms
from django.forms import ModelForm
from teretana.models import *


class PretplatnikForm(forms.ModelForm):
    plan = forms.ModelChoiceField(queryset=Plan.objects.all(),
                                    to_field_name = 'naziv',
                                    empty_label="Select Plan")
    
    trener = forms.ModelChoiceField(queryset=Trener.objects.all(),
                                    to_field_name = 'ime',
                                    empty_label="Select Trainer")

    class Meta:
        model = Pretplatnik
        fields = ['trener', 'plan', 'datum_r', 'spol', 'adresa']
        GENDER_CHOICES = (
            ('', 'Select a Gender'),
            ('Female', 'Female'),
            ('Male', 'Male'),
        )
        widgets = {
            'trener':forms.TextInput(attrs={'class':'form-control','placeholder':'Select Trainer'}),
            'plan':forms.TextInput(attrs={'class':'form-control','placeholder':'Select Plan'}),
            'datum_r':forms.DateInput(attrs={'class':'form-control','placeholder':'Enter Date of Birth'}),
            'spol':forms.Select(choices=GENDER_CHOICES,attrs={'class': 'form-control'}),
            'adresa':forms.TextInput(attrs={'class':'form-control','placeholder':'Enter Address'}),
        }
        labels={
            'plan':'Select Plan',
            'trener':'Select Trainer',
            'datum_r':'Datum_r',

        }

    def __init__(self, *args, **kwargs):
        super(PretplatnikForm, self).__init__(*args, **kwargs)
        # Add first_name and last_name fields from the User model
        self.fields['first_name'] = forms.CharField(max_length=150, widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Enter First Name'}))
        self.fields['last_name'] = forms.CharField(max_length=150, widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Enter Last Name'}))

    def save(self, commit=True):
        # Save the User instance first
        user = super(PretplatnikForm, self).save(commit=False)
        user.first_name = self.cleaned_data['first_name']
        user.last_name = self.cleaned_data['last_name']
        if commit:
            user.save()
        # Save the Pretplatnik instance
        pretplatnik = super(PretplatnikForm, self).save(commit=False)
        pretplatnik.korisnik = user
        if commit:
            pretplatnik.save()
        return pretplatnik

I also changed the view

views.py

def enroll(request):
    if not request.user.is_authenticated:
        messages.warning(request, "Please login and try again")
        return redirect('/login')

    if request.method == 'POST':
        form = PretplatnikForm(request.POST)
        if form.is_valid():
            # Create a new Pretplatnik instance
            pretplatnik_instance = form.save(commit=False)
            pretplatnik_instance.korisnik = request.user
            # Set first_name and last_name based on the logged-in user
            pretplatnik_instance.first_name = request.user.first_name
            pretplatnik_instance.last_name = request.user.last_name
            pretplatnik_instance.save()
            
            messages.success(request, "You have successfully enrolled.")
            return redirect('/')  # Redirect to a success page or dashboard
        else:
            messages.error(request, "Form submission failed. Please check the errors.")

    else:
        form = PretplatnikForm()
        
    return render(request, 'enroll.html', {'form': form})

But now I get a message after I logout saying "Form submission failed". I don't get redirected after filling the form, it keeps me on the /enroll page and most importantly the data doesn't save.

Upvotes: 0

Views: 28

Answers (1)

Milo Persic
Milo Persic

Reputation: 1118

This is not necessarily the answer but something looks strange here:

    if request.method == 'POST':
        form = PretplatnikForm(request.POST)
        form = form.save(commit=False) # not sure why this is here
        if form.is_valid():
            form.save()

Normally one would see this pattern, where commit=False follows validation. It's not clear to me how you wanted to use this, or how you're adding the One-to-One for the korisnik field, but here's an example of something that might normally be used.

    if request.method == 'POST':
        form = PretplatnikForm(request.POST)
        if form.is_valid():
            new_sub = form.save(commit=False)
            new_sub.korisnik = korisnik_object # queried elsewhere
            new_sub.save() # no need to call form.save()

Upvotes: 0

Related Questions