Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Python

Kyle Ehrmann
Kyle Ehrmann
1,168 Points

People and Dates. How to add a Person to an Event Date?

I was following the track learning Django, but using it to start my own project for tracking people who attend events. Right now, I have two models created (Person and Date). They work fine, and I can add people to a list of people, and dates to dates.

Now, I'd like to add people to a date. I created another model which uses "ForeignKey" showing me "EventAttendance", and has a boolean to mark if they were there or not. In the admin, it works fine using dropdowns (I can choose a person, choose a meeting date) and then click the boolean.

However, I'd like to do this on the HTML template. Once I click a date, I'd like it to route the URL to a slug associated with that date. From there, I'd like to populate the top half of the page with all of the people I'd invite to the event with a boolean box next to each name. Once I click off all the boxes of people I'd like to invite, I'd like only those people to show at the bottom-half of the page (people who are attending, or did attend that specific event).

Any idea how I can accomplish this easily?

Kyle Ehrmann
Kyle Ehrmann
1,168 Points
class PeopleList(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    phone_number = models.CharField(null='True', max_length=12)
    MALE = 'M'
    FEMALE = 'F'
    GENDER_CHOICES = (
        (MALE, 'Male'),
        (FEMALE, 'Female'),
        )
    gender = models.CharField(
        max_length=6,
        choices=GENDER_CHOICES,
        )

    def __str__(self):
        return "%s %s" % (self.first_name, self.last_name)


class Date(models.Model):
    HARDWARE_SHOW = 'Hardware Show'
    METAL_SHOW = 'Metal Show'
    MEETING_NAME_CHOICES = (
        (HARDWARE_SHOW, 'Hardware Show'),
        (METAL_SHOW, 'Metal Show'),
        )
    meeting_name = models.CharField(
        max_length=50,
        choices=MEETING_NAME_CHOICES,
        )
    date_of_meeting = models.DateField()


    def __str__(self):
        return "%s - %s" % (self.meeting_name, self.date_of_meeting)

class MeetingAttendance(models.Model):
    meeting_date = models.ForeignKey('Date', on_delete=models.CASCADE)
    person = models.ForeignKey('PeopleList', on_delete=models.CASCADE)
    attended = models.BooleanField()


    def __str__(self):
        return "%s - %s" % (self.person, self.meeting_date)
Kyle Ehrmann
Kyle Ehrmann
1,168 Points
def date_list(request):
    dates = Date.objects.all()
    return render(request, 'statistics/date_list.html', {'dates': dates})

def meeting_detail(request, pk):
    attended = MeetingAttendance.objects.filter(meeting_date__pk=pk)
    people = PeopleList.objects.all().order_by('last_name')
    if request.method == "POST":
        form = AttendanceForm(request.POST)
        if form.is_valid():
            people = form.cleaned_data['person']
            date = form.cleaned_data['date']
            newAttendance = MeetingAttendance(meeting_date=date, person=person)
            newAttendance.save()
            return redirect('meeting_detail')
    else:
        form = AttendanceForm()
    context = {
    'attended': attended,
    'people': people,
    'form': form,

}   
    return render(request, 'statistics/meeting_detail.html', context)

def meeting_attendance(request, pk):
    people = PeopleList.objects.all().order_by('last_name')
    date = Date.objects.filter(date_of_meeting__pk=pk)
    context = {
        'people': people,
        'date': date,
    }
    return render(request, 'statistics/meeting_attendance.html', context)

def date_new(request):
    if request.method == "POST":
        form = DateForm(request.POST)
        if form.is_valid():
            date = form.save(commit=False)
            date.save()
            return redirect('date_list')
    else:
        form = DateForm()
    return render(request, 'statistics/date_new.html', {'form': form})


def Group(request):
    people = PeopleList.objects.all().order_by('last_name')
    if request.method == "POST":
        form = MeetingForm(request.POST)
        if form.is_valid():
            people = form.save(commit=False)
            people.save()
            return redirect('Group')
    else:
        form = MeetingForm()
    context = {
        'form': form,
        'people': people,
    }
    return render(request, 'statistics/Group.html', context)
Kyle Ehrmann
Kyle Ehrmann
1,168 Points
urlpatterns = [
    url(r'^$', views.date_list, name='date_list'),
    url(r'^meeting/(?P<pk>\d+)/$', views.meeting_detail, name='meeting_detail'),
    url(r'^date/new/$', views.date_new, name='date_new'),
    url(r'^group/$', views.Group, name='Group'),
    url(r'^meetingattendance/$', views.meeting_attendance, name='meeting_attendance'),

]

2 Answers

Kenneth Love
STAFF
Kenneth Love
Treehouse Guest Teacher

If you want to show the data without it being editable, you can set readonly_fields in the Form. It should still render, but won't be a dropdown, just plain text. How you handle the "attended" or not would be outside of the form (probably send a specific value through on the submit button?) but shouldn't be too weird.

Kyle Ehrmann
Kyle Ehrmann
1,168 Points

Kenneth, thats dead on! Thanks so much! There is a box around the text in the HTML now. Any idea how to get rid of that so it's just the text thats rendering?

Kyle Ehrmann
Kyle Ehrmann
1,168 Points

I think I'm still doing something wrong on the "Save" here since it keeps throwing errors. Right now it's saying "Cannot assign "'John Smith'": "MeetingAttendance.person" must be a "Person" instance". I'm trying to save the form to an instance of model "MeetingAttendance".

<div class="container-fluid">
    <div class="directory panel panel-default">
        <div class="panel-heading">
            <h3 class="panel-title">Add a person to the meeting</h3>
        </div><!-- end panel-heading -->

        <div class="panel-body">

            <form action="." method="POST">
                {% csrf_token %}
                {{ form.management_form }}
                {% for record in form %}
                    <p>{{ record.person }}{{ record.attended }}</p>
                {% endfor %}

                <button class="btn btn-primary" type="submit" value="attendance">Save</button>
            </form>
        </div>
    </div><!-- end panel-body -->
</div><!-- end directory panel panel-default -->
class MeetingForm(forms.ModelForm):
    person = forms.CharField(widget=forms.TextInput(attrs={'readonly':'readonly'}))
    meeting_date = forms.CharField(widget=forms.HiddenInput())
def date_detail(request, slug):
    people = Person.objects.all()
    detail = Date.objects.get(slug=slug)
    attendance = MeetingAttendance.objects.all()
    MeetingFormSet = formset_factory(MeetingForm, extra=len(people)-2, max_num=len(people))
    if request.method == "POST":
        form = MeetingFormSet(request.POST)
        if form.is_valid():
            formset = form.save(commit=True)
            formset.save()
            return redirect('date_detail', slug=slug)
    else:
        initial_data = [{'person': person, 'meeting_date': detail} for person in people]
        form = MeetingFormSet(initial=initial_data)
        if form.is_valid():
            attendance = form.save(commit=False)
            attendance.save()
        context = {
        'form': form,
        'people': people,
        'attendance': attendance,
        }   
        return render(request, 'date_detail.html', context)
class MeetingAttendance(models.Model):
    meeting_date = models.ForeignKey('Date', on_delete=models.CASCADE)
    person = models.ForeignKey('Person', on_delete=models.CASCADE)
    attended = models.BooleanField()


    def __str__(self):
        return "%s - %s" % (self.person, self.meeting_date)
Kenneth Love
Kenneth Love
Treehouse Guest Teacher

So, the form is probably sending in the __str__ representation of the User instead of their ID or the actual User instance. You could probably solve this by including the ID in the form in a hidden field.

If you add

<input type="hidden" name="person_id" value="{{ record.instance.person.id }}">

to your form, and refresh the page, does the correct ID show up in the form?

Kyle Ehrmann
Kyle Ehrmann
1,168 Points

Yes, the correct form shows up. I just have boxes around the names populating those inputs. Is that something that is just solved in CSS, or should it be displaying a different way?

Kyle Ehrmann, I don't pretend that this is complete, but it could give you a place to start. I didn't start any HTML or Jinja files, but they shouldn't be too crazy for you to throw together.

Note: there could very well be bugs in this, I haven't tested it in any way.

The concept is that you have a list of forms which you would display as a forloop on the page. Each form would have it's own submit button that, on submission would save the record.

Have fun with your project! :)

views.py

from django.shortcuts import get_object_or_404
from .forms import AttendanceForm
from .models import Date, MeetingAttendance


def event_attendance(request, event_pk):
    event = get_object_or_404(Date, pk=event_pk)
    attendance_records = MeetingAttendance.objects.filter(meeting_date=event)
    attendance_forms = [AttendanceForm(instance=record) for record in attendance_records]

    if request.method == "POST":
        attendance_record = attendance_records.get(
            person=request.POST.get('person'))
        attendance_record.attended = request.POST.get('attended')
        attendance_record.save()

    return TemplateResponse(request, 'attendance.html', dict(
        event=event, attendance_forms=attendance_forms))

forms.py

from django import forms
from .models import MeetingAttendance


class AttendanceForm(forms.ModelForm):
    person = forms.CharField(widget=forms.HiddenInput())

    class Meta:
        model = MeetingAttendance
        fields = ('attended', )

Just realized that you already had a views.py and urls.py file. I never looked at those.

Kyle Ehrmann
Kyle Ehrmann
1,168 Points

Thanks!! Let me toy with what you proposed to see how it differs from mine.

Kyle Ehrmann
Kyle Ehrmann
1,168 Points

jacinator: I'm having a tough time understanding the code here (albeit, I'm new to Django :))

Can you explain a bit about what you wrote and how it processes? Additionally, maybe the URLs I have are off, which is throwing it.

Thanks!

I hope these comments help explain my thinking a bit.

views.py

from django.shortcuts import get_object_or_404
from .forms import AttendanceForm
from .models import Date, MeetingAttendance


def event_attendance(request, event_pk):
    event = get_object_or_404(Date, pk=event_pk)  # Gets the event based off of the primary key.
    attendance_records = MeetingAttendance.objects.filter(meeting_date=event)  # Gets attendance records for the event
    attendance_forms = [AttendanceForm(instance=record) for record in attendance_records]  # Creates a ModelForm for each attendance record

    if request.method == "POST":
        attendance_record = attendance_records.get(
            person=request.POST.get('person'))  # Get the attendance record that matches the person submitted in post.
        attendance_record.attended = request.POST.get('attended')  # Set the attended boolean by the POST data.
        attendance_record.save()  # Save the record

    return TemplateResponse(request, 'attendance.html', dict(
        event=event, attendance_forms=attendance_forms))  # Return a TemplateResponse

forms.py This file likely needs major modifications.

from django import forms
from .models import MeetingAttendance


class AttendanceForm(forms.ModelForm):
    person = forms.CharField(widget=forms.HiddenInput())  # Use a custom form field for person, so that it can be hidden

    class Meta:
        model = MeetingAttendance
        fields = ('attended', )
Kyle Ehrmann
Kyle Ehrmann
1,168 Points

Thanks!! After poking around a bit, it seems like this is going to give me save buttons for each person, rather than letting me just click off 'attended' next to each person and save all at the same time. Am I getting that right? Also, I'm having trouble rendering the hiddenInput of person into my HTML file.

Kyle Ehrmann
Kyle Ehrmann
1,168 Points
def date_detail(request, slug):
    people = Person.objects.all()
    detail = Date.objects.get(slug=slug)
    attendance = MeetingAttendance.objects.all()
    MeetingFormSet = formset_factory(MeetingForm, extra=len(people)-2, max_num=len(people))
    if request.method == "POST":
        form = MeetingFormSet(request.POST)
        if form.is_valid():
            formset = form.save(commit=True)
            formset.save()
            return redirect('date_detail', slug=slug)
    else:
        initial_data = [{'person': person, 'meeting_date': detail} for person in people]
        form = MeetingFormSet(initial=initial_data)
        context = {
        'form': form,
        'people': people,
        'attendance': attendance,
        }   
        return render(request, 'date_detail.html', context)
class MeetingForm(forms.ModelForm):

    class Meta:
        model = MeetingAttendance
        widgets = {
            'meeting_date': forms.HiddenInput(),

        }
        fields = ['attended', 'person', 'meeting_date',]
<div class="directory panel panel-default">
    <div class="panel-heading">
        <h3 class="panel-title">Meeting Details</h3>
    </div><!-- end paneil-heading -->

    <div class="panel-body">


        <form class="meeting-form" method="POST">

            {% csrf_token %}

            {% for hidden in form.hidden_fields %}

                {{ hidden }}

            {% endfor %}



            {% for field in form.visible_fields %}
                <div class="fieldWrapper">
                    {{ field.person }}
                    {{ field.attended }}
                </div>
            {% endfor %}

            <input type="submit" name="submit">

        </form>
    </div>
</div>
Kyle Ehrmann
Kyle Ehrmann
1,168 Points

So this is now working correctly. The last thing that I'm trying to get it to do, which it isn't yet, is just show the repopulated data without letting the user change it. Since I'm using ForeignKey, I still have the dropdown boxes allowing the user to change the person name, or meeting_date name. The only modification I want them to do is to click "attended" if they were there, and then save (which I don't believe I have 'save' done yet either).

Any ideas?