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 trialKyle Ehrmann
1,168 PointsPeople 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
1,168 Pointsdef 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
1,168 Pointsurlpatterns = [
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
Treehouse Guest TeacherIf 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
1,168 PointsKenneth, 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
1,168 PointsI 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
Treehouse Guest TeacherSo, 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
1,168 PointsYes, 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?
jacinator
11,936 PointsKyle 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', )
jacinator
11,936 PointsJust realized that you already had a views.py and urls.py file. I never looked at those.
Kyle Ehrmann
1,168 PointsThanks!! Let me toy with what you proposed to see how it differs from mine.
Kyle Ehrmann
1,168 Pointsjacinator: 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!
jacinator
11,936 PointsI 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
1,168 PointsThanks!! 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
1,168 Pointsdef 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
1,168 PointsSo 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?
Kyle Ehrmann
1,168 PointsKyle Ehrmann
1,168 Points