【问题标题】:Django - Form does not saveDjango - 表单不保存
【发布时间】:2017-04-18 17:03:30
【问题描述】:

我正在制作一个使用 ListView 显示事件的网站,每个事件都有一个注册表单。

表单验证错误都会出现,但是在成功提交表单后,它只是将我带到成功页面并且表单没有保存到数据库中。

当我通过管理员而不是表单提交表单时,它给了我一个错误: /admin/home/signups/add/ 处的名称错误 全局名称“全名”未定义回溯到:instance.fullname = forms.py 中的全名。

如果我完全删除该行,另一个错误是: /admin/home/signups/add/ 处的名称错误 全局名称 'request' 未在 forms.py 中使用对 instance.ip = get_ip(request) 的回溯定义。

forms.py:

from django import forms
from .models import SignUps, Hours, Events
import datetime
from ipware.ip import get_ip

class SignUpForm(forms.ModelForm):
    fullname = forms.CharField(label="Full name", widget=forms.TextInput(attrs={'placeholder': 'Full name', 'class': 'form-control'}))

    class Meta:
        model = SignUps
        fields = ['eventname','fullname','ip']

    def clean_fullname(self):
        fullname = self.cleaned_data.get('fullname').title()
        eventname = self.cleaned_data.get('eventname')
        try:
            name = Hours.objects.get(fullname=fullname)
        except Hours.DoesNotExist:
            raise forms.ValidationError("Please enter a valid Key Club member's full name as displayed in the hours page.")
        try:
            name = SignUps.objects.get(fullname=fullname)
        except SignUps.DoesNotExist:
            try:
                numOfSignUps = SignUps.objects.filter(eventname=eventname).count()
            except SignUps.DoesNotExist:
                numOfSignUps = 0
            try:
                event = Events.objects.get(name=eventname)
            except Events.DoesNotExist:
                raise forms.ValidationError("Something went wrong. This event does not exist.")
            try:
                ifFull = Events.objects.filter(name=eventname).get(maximum__gt=numOfSignUps)
            except Events.DoesNotExist:
                raise forms.ValidationError("The maximum number of attendees has already been reached.")
            try:
                date = Events.objects.filter(name=eventname).get(date=datetime.date.today())
            except Events.DoesNotExist:
                return fullname
            raise forms.ValidationError("It is too late to sign up for this event.")
        raise forms.ValidationError("This member is already signed up.")

    def save(self, commit=True):
        instance =  super(SignUpForm, self).save(commit=False)
        instance.fullname = fullname
        instance.ip = get_ip(request)
        if commit:
            instance.save()
        return instance

events/index.html 主体:

<div class="container" style="margin-top:75px;">

    {% block content %}

    {% for announcements in announcement %}
    <div class="alert alert-info" role="alert">
      <h4 class="alert-heading">{{ announcements.announcementname }}</h4>
      <p style="margin:0 0;">{{ announcements.announcement | safe | linebreaksbr | urlize }}</p>
    </div>
    {% endfor %}

    {% for events in events_list %}
    <div id="checkIn" class="modal fade">
      <div class="modal-dialog" role="document">
        <div class="modal-content">
          <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
              <span aria-hidden="true">&times;</span>
            </button>
            <h4 class="modal-title">OCC Check-Ins</h4>
          </div>
          <div class="modal-body">
            <form method="POST">
              <div class="form-group">
                <input type="password" class="form-control" id="passcode" placeholder="Passcode" maxlength="4" autocomplete="off">
              </div>
              <div class="form-group"><button type="submit" class="btn btn-primary btn-block">Sign in</button></div>
            </form>
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
          </div>
        </div><!-- /.modal-content -->
      </div><!-- /.modal-dialog -->
    </div><!-- /.modal -->

    <div class="card card-block col-sm-6" style="display:inline-block;">
      <div class="dropdown" style="float:right;">
        <a href="#" style="color:black;" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><h5><i class="fa fa-caret-down" aria-hidden="true"></i></h5></a>
        <div class="dropdown-menu dropdown-menu-left" aria-labelledby="dropdownMenuButton" style="right:0;left:auto;">
          <a class="dropdown-item" href="#">View</a>
        </div>
      </div>
      <h4 class="card-title">{{ events.name }}</h4>
      <p class="card-text">Description: <span class="text-muted">{{ events.description }}</span><br>Where: <a href="https://www.google.com/maps/place/{{ events.location }}" class="text-muted">{{ events.location }}</a><br>When: <span class="text-muted">{{ events.date|date:"D, M d, Y" }}</span><br>Time: <span class="text-muted">{{ events.time|time:"P" }}</span><br>Max: <span class="text-muted">{{ events.maximum }}</span><br>Hours: <span class="text-muted">{{ events.hours }}</span><br>Check in with: <span class="text-muted">{{ events.occ }}</span></p>
      <h4 class="card-text" style="margin-top:-6px;margin-bottom:8px;"><a href="#" style="color:black; text-decoration:none;" data-toggle="modal" data-target="#checkIn"><i class="fa fa-sign-in" aria-hidden="true" style="margin-bottom:10px;"></i> OCC Check-Ins</a></h4>
      <form action="/events/" class="form" method="POST">{% csrf_token %}
        <div class="form-group">
          <input id="id_eventname" maxlength="125" name="eventname" type="hidden" value="{{ events.name }}">
          {{ form.fullname }}
          {{ form.fullname.errors }}
        </div>
        <button class="btn btn-primary btn-block" type="submit">Sign up</button>
      </form>
    </div>
    {% endfor %}
    {% endblock %}

  </div>

success.html 只是一个显示“成功”的空白 HTML 页面:

<div class="container" style="margin-top:75px;">
    Success
  </div>

views.py:

from django.shortcuts import render
from django.views.generic import ListView, FormView
from django.views.generic.detail import SingleObjectMixin
from home.models import Events, Announcement, Hours, SignUps
from django import forms
from .forms import SignUpForm
from django.http import HttpResponseForbidden
from django.urls import reverse
from django.views import View
from ipware.ip import get_ip
from django.views.generic.edit import FormView

# Create your views here.
def index(request):
    return render(request, 'home/index.html')

def about(request):
    return render(request, 'about/index.html')

def success(request):
    return render(request, 'events/success.html')

class EventsDisplay(ListView, FormView):
    template_name='events/index.html'
    context_object_name = "events_list"
    queryset = Events.objects.all().order_by("date")
    form_class = SignUpForm
    success_url = "/events/success"

    def get_context_data(self, **kwargs):
        self.object_list = self.get_queryset()
        context = super(EventsDisplay, self).get_context_data(**kwargs)
        context['announcement'] = Announcement.objects.all().order_by("-datetime")
        context['signup'] = SignUps.objects.all().order_by("fullname")
        return context

class HoursList(ListView):
    template_name = 'hours/index.html'
    context_object_name = "hours_list"
    queryset = Hours.objects.all().order_by("fullname")

    def get_context_data(self, **kwargs):
        context = super(HoursList, self).get_context_data(**kwargs)
        context['announcement'] = Announcement.objects.all().order_by("-datetime")
        return context

urls.py:

from django.conf.urls import url, include
from views import EventsDisplay, HoursList
from . import views

urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^events/$', EventsDisplay.as_view()),
    url(r'^events/success$', views.success, name='success'),
    url(r'^hours/$', HoursList.as_view()),
    url(r'^about/$', views.about, name='about'),
]

【问题讨论】:

    标签: python django forms listview validationerror


    【解决方案1】:

    好的,在仔细检查了代码之后,我相信这里可以进行更多的重构。

    首先,我看不出将基于类的视图和基于函数的视图混为一谈有什么意义。这些标准的getpost 请求可以这样完成:

    from django.shortcuts import render
    from django.views.generic import ListView, FormView
    from django.views.generic.detail import SingleObjectMixin
    from home.models import Events, Announcement, Hours, SignUps
    from django import forms
    from .forms import SignUpForm
    from django.http import HttpResponseForbidden
    from django.urls import reverse
    from django.views import View
    from ipware.ip import get_ip
    from django.views.generic.edit import FormView
    
    class EventsDisplay(ListView, FormView):
        template_name='events/index.html'
        context_object_name = "events_list"
        queryset = Events.objects.all().order_by("date")
        form_class = SignUpForm 
        success_url = "events/success.html"
    
        def get_context_data(self, **kwargs):
            context = super(EventsDisplay, self).get_context_data(**kwargs)
            context['announcement'] = Announcement.objects.all().order_by("-datetime")
            # No need to insert 'form' as Django  takes care of that
            # by utilising self.form_class
            return context
    
        def form_valid(self, form): 
            instance = form.save(commit=False) instance.save() 
            return super(EventsDisplay, self).form_valid(form)
    

    基本上,我已将您的代码合并到一个基于类的视图中,该视图继承自ListView(用于您的get 请求)和FormView(用于您的post 请求)。

    由于您的视图发生了这种变化,您的 urls 现在应该变成:

    from django.conf.urls import url, include
    from views import EventsList, HoursList
    from . import views
    
    urlpatterns = [
        url(r'^$', views.index, name='index'),
        url(r'^events/$', EventsDisplay.as_view()),
        url(r'^hours/$', HoursList.as_view()),
        url(r'^about/$', views.about, name='about'),
    ]
    

    最后,如果你必须在保存之前“刺激”你的模型变量(例如,插入 ip 地址),你应该将这种代码放在这样的表单中:

    from django import forms
    from .models import SignUps, Hours, Events
    import datetime
    from ipware.ip import get_ip
    class SignUpForm(forms.ModelForm):
        fullname = forms.CharField(label="Full name", widget=forms.TextInput(attrs={'placeholder': 'Full name', 'class': 'form-control'}))
    
        class Meta:
            model = SignUps
            fields = ['eventname','fullname','ip']
    
        def __init__(self, *args, **kwargs):
            self.request = kwargs.pop('request', None)
            super(SignUpForm, self).__init__(*args, **kwargs)
    
        def clean_fullname(self):
            ...
    
        def save(self, commit=True):
            instance =  super(SignUpForm, self).save(commit=False)
            instance.fullname = fullname
            instance.ip = get_ip(self.request)
            if commit:
                instance.save()
            return instance
    

    【讨论】:

    • 当我提交表单时,它给了我一个错误:/events/ 'EventsDisplay' 对象的 AttributeError 没有属性 'object_list'。回溯到:views.py中的context = super(EventsDisplay, self).get_context_data(**kwargs)
    • 不要在你的 html 代码中使用object_list,现在使用events_listcontext_object_name 允许您在模板中使用更具体的名称,而不是使用像 object_list 这样的通用且无意义的名称。
    • 我已通过将 self.object_list = self.get_queryset() 添加到 get_context_data 来修复它。表单验证有效并且出现错误;但是,成功后,这些值不会保存到数据库中;它只会把我带到成功页面。
    • 请查看已编辑的答案 - 我在保存时的退货声明中犯了一个小错误。应该是 return instance 而不是 return commit
    • 同样的事情仍然发生。表单验证有效,但成功提交后,它仍然不会将其保存到数据库中。我已经更新了我的原始问题以反映我当前的代码和文件。
    【解决方案2】:

    当您在 SignUpForm 中遇到验证错误时,您的 SignUp(request) 函数确实会在 form 对象中提取错误,但您返回的页面 (events/success.html) 与您的 GET 返回的页面不同(events/index.html)。

    我假设events/success.html 的 html 代码与您提供的似乎用于 events/index.html 的 html 代码不同。

    您的SignUp(request) 函数应如下所示:

    def SignUp(request):
        if request.method=='POST':
            form = SignUpForm(request.POST or None)
            if form.is_valid():
                instance = form.save(commit=False)
                fullname = form.cleaned_data.get("fullname")
                instance.fullname = fullname
                instance.ip = get_ip(request)
                instance.save()
            else: # INVALID FORM - WE HAVE VALIDATION ERRORS SO RETURN SAME PAGE.
                return render(request, "events/index.html", {"form": form})
    
        else:
            form = SignUpForm
        context = {
            "form": form,
        }
        return render(request, "events/success.html", context)
    

    【讨论】:

    • 我只是将其更改为 events/index.html 但是当我这样做时,ListView 的元素不会显示,所以我的 index.html 基本上只显示导航栏和页脚。
    • 你能提供你的索引和成功的html文件吗?
    • 我刚刚更新了问题以包含 index.html 的完整正文,不包括导航栏和页脚。我的 success.html 页面是空白的,只显示“成功”。
    • 请查看更新 - 我只是说只有在出现验证错误时才返回相同的页面 events/index.html
    • 我已添加它,但 {% block content %} 和 {% for %} 循环中的所有内容仍未显示。它加载事件页面,但仅使用导航栏和页脚。
    猜你喜欢
    • 2014-05-19
    • 2014-02-01
    • 1970-01-01
    • 2017-06-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-10
    • 2021-03-14
    相关资源
    最近更新 更多