Django中的ModelForm无法识别属性已填充 - NOT NULL约束失败: teretana_pretplatnik.first_name

0 投票
1 回答
24 浏览
提问于 2025-04-14 18:23

我正在制作一个Django健身应用,跟着这个教程在学 https://www.youtube.com/watch?v=Mag1n3MFDFk&list=PL2aJidc6QnyOe-fp1m4yKHjcInCRTF53N&index=3。我想写一个表单页面,让用户(登录后)可以填写,成为订阅者。在教程中,他使用了一个新的模型Enrollment来填充数据,但我有一个模型Pretplatnik(英文是Subscriber),它和Django的用户模型有一对一的关系。还有两个其他模型,它们是Pretplatnik的外键,一个是用户可以订阅的计划,另一个是用户可以选择的教练。

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 %}

他使用HTML表单来获取数据,但我也试过了,结果对于外键(FK)不太管用(或者能用,但我不知道怎么实现),所以我尝试使用ModelForm。但现在我遇到了一个错误,因为我猜测ModelForm不知道Django用户的数据(这是Pretplatnik的主键)已经填好了(用户必须登录才能填写表单)。我试着按照这里另一个问题的建议修改views文件,但结果我得到了这个错误:

在/enroll处出现AttributeError

'Pretplatnik'对象没有'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})

编辑:

我忘了加forms.py,这里补上

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

我也修改了视图

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})

但现在我在登出后收到一条消息,说“表单提交失败”。填写表单后我没有被重定向,仍然停留在/enroll页面,更重要的是数据没有保存。

1 个回答

0

这不一定是答案,但这里有些地方看起来有点奇怪:

    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()

通常情况下,我们会看到这样的模式:commit=False 是在验证之后的。我不太明白你想怎么用这个,或者你是怎么为 korisnik 字段添加一对一关系的,不过这里有一个通常会用到的例子。

    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()

撰写回答