Search code examples
pythondjangoformslistviewvalidationerror

Django - Form does not save


I'm making a website that shows events using a ListView and each event has a sign up form.

Form validation errors all show up, but upon successful submission of the form, it just brings me to the success page and the form does not get saved into the database.

When I submit the form via admin and not the form, it gives me an error: NameError at /admin/home/signups/add/ global name 'fullname' is not defined with traceback to: instance.fullname = fullname in forms.py.

Another error if I completely remove that line is: NameError at /admin/home/signups/add/ global name 'request' is not defined with traceback to instance.ip = get_ip(request) in forms.py.

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 main body:

<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 is just a blank HTML page that says "success":

<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'),
]

Solution

  • Okay so after reviewing the code a fair bit, I believe there could be a lot more refactoring done here.

    Firstly, I don't see the point of mixing up the class based views and a function based view. These standard get and post requests can be done like this:

    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)
    

    Basically, I have merged your code into a single class based view that inherits from ListView (for your get requests) and FormView (for your post requests).

    As a result of this change in your views, your urls should now become:

    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'),
    ]
    

    Finally, if you have to "prod" your model variables before saving (eg. insert ip address), you should place this kind of code within the form like this:

    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