Learn coding with #freecodecamp 

Finally we wrote a management command to handle the actual sending which called a function that looks something like this:
def save_the_date_random(request):
template_id = random.choice(SAVE_THE_DATE_CONTEXT_MAP.keys())
context = get_save_the_date_context(template_id)
context[’email_mode’] = False
return render(request, SAVE_THE_DATE_TEMPLATE, context=context)
Function to send the save the dates with some additional testing options.
After a lot of testing we were confident everything was good to go and it was time to push the big scary “send” button (or in this case run ./manage.py send_save_the_dates –send –mark-sent).
Almost immediately we started getting some fun responses from our friends talking about the save the dates and it was fun watching them get confused about describing different templates.
All in all, thanks to thorough testing and preparation it was a great success!
Though I should note that, probably not surprisingly, the whole thing took quite a bit longer than expected. And we were just getting started!
Invitations
After a couple months of much appreciated down time, the three-month-out deadline when you are supposed to send out invitations started approaching. Feeling overall good about the save the date experience we decided once again to do the invitations and RSVPs ourselves. This added quite a lot of additional scope. In particular we decided we wanted to:
Be able to track invitation opens
Let people RSVP on our website
Not require people to login or enter their emails
Automatically populate the guest names from the invitation link
Let people select meals and specify attendance for each guest
Updating the Models
All this extra functionality meant another significant model update. Here are the final Party and Guest models after the invitations were done:
class Party(models.Model):
“””
A party consists of one or more guests.
“””
name = models.TextField()
type = models.CharField(max_length=10, choices=ALLOWED_TYPES)
category = models.CharField(max_length=20, null=True,
blank=True)
save_the_date_sent = models.DateTimeField(
null=True, blank=True, default=None)
save_the_date_opened = models.DateTimeField(
null=True, blank=True, default=None)
invitation_id = models.CharField(max_length=32, db_index=True,
default=_random_uuid, unique=True)
invitation_sent = models.DateTimeField(
null=True, blank=True, default=None)
invitation_opened = models.DateTimeField(
null=True, blank=True, default=None)
is_invited = models.BooleanField(default=False)
rehearsal_dinner = models.BooleanField(default=False)
is_attending = models.NullBooleanField(default=None)
comments = models.TextField(null=True, blank=True)
class Guest(models.Model):
“””
A single guest
“””
party = models.ForeignKey(Party)
first_name = models.TextField()
last_name = models.TextField(null=True, blank=True)
email = models.TextField(null=True, blank=True)
is_attending = models.NullBooleanField(default=None)
meal = models.CharField(max_length=20, choices=MEALS,
null=True, blank=True)
is_child = models.BooleanField(default=False)
The final Party and Guest models that were used.
The most important change was the addition of the invitation_id to the Party model. This was a 32-character GUID that we used in our invitation URLS to prevent people from being able to guess others’ invite links (which would have been easy if we had used standard integers). The invitation_id also solved the problem with allowing guests to RSVP without requiring logins; each party would simply have a unique invitation ID that only they would know that could be used to RSVP.
In addition to the invitation_id, Parties also got some metadata about when the invitations were sent and opened and a comments field for our guests to put in well-wishes.
Meanwhile Guests got two more fields related to the RSVPs—one for meal selection, and another to represent whether they were a child or not. We had to add this last field because we didn’t want to offer meal selection to children, and it also helped us plan our final head counts and costs better.
Invitation Email and Open Tracking
We were originally going to implement a tracking pixel in the invitation emails to see whether they were being opened or not, but eventually ended up with a simpler approach. We decided instead to make our actual invitation email a “teaser” that linked you to the “real” invitation — which was your personalized link and RSVP page. Then, since our invitations already were individualized links per-party, we just decided to treat the opening of that link as the opening of the invitation.
Once this was decided it was quite easy to just add a bit of code to update the tracking field when the page was opened for the first time.
def invitation(request, invite_id):
party = guess_party_by_invite_id_or_404(invite_id)
if party.invitation_opened is None:
# update if this is the first time the invitation was opened
party.invitation_opened = datetime.utcnow()
party.save()
Simple tracking when a Party views the invitation page.
For the email itself, we ended up using basically the exact same template as the save the dates only this time the email had to be smart about injecting the right link into the mail based on who it was being sent to — a straightforward process with Django’s template rendering.

Our invitation email. We found a graphic online that kind of looked like us for $1.
The Invitation and RSVP Page
The invitation/RSVP page (example here) was pretty straightforward (I’m more of a backend developer, so I had to keep things quite simple on the UI side).
Basically it consists of a short embedded video that we made, some event details, and an RSVP form. The trickiest bit was the validation and meal logic, which was as follows:
You must RSVP for all guests in your party
You are only allowed to select a meal if you RSVP “yes”
Kids don’t have meals
I used the excellent Validator for Bootstrap 3 library for the UI and most of the javascript logic, and just had to add a manual bit of JavaScript to hook up the attendance/meal workflow:
// enable/disable meal choices based on attendance
$(“input.attending-radio”).change(function (e) {
var target = $(e.target);
if (target.closest(‘.form-group’).data(‘is-child’) === “True”) {
// don’t attempt to update meals for children,
// who don’t have them.
return;
}
var value = target.attr(‘value’);
var mealButtonContainer = target.closest(
‘.form-group’).next(‘.form-group’);
var mealButtons = mealButtonContainer.find(‘[type=radio]’);
if (value === ‘yes’) {
mealButtonContainer.removeClass(‘text-muted’);
mealButtons.each(function (index, button) {
button.disabled = false;
button.required = true;
});
} else if (value === “no”) {
mealButtonContainer.addClass(‘text-muted’);
mealButtons.each(function (index, button) {
button.checked = false;
button.disabled = true;
button.required = false;
});
}
// reload validation
$(document.forms[0]).validator(‘destroy’);
$(document.forms[0]).validator();
All the client-side validation code that was needed.
Here’s what this part looked like:

Nice UI and auto enable/disable of submit button courtesy of Validator for Bootstrap 3.
Finally, after you submit your form — which, of course updates your attending and meal preferences accordingly — we took you to a customized confirmation page with a thank you and a few other bits of information.

What you see after you RSVP
Sending the Invitations
Once again everything went incredibly smoothly thanks to thorough testing and running through several dry runs before actually sending the real invitations. There were actually zero bugs encountered by our guests during the RSVP process, a fact I am quite proud of.
On the flip side, at this point I had put in well over 40 hours of time on nights and weekends building the site.
The Final Piece: a Live Guest Dashboard
Once we had the invitations going out and the RSVPs coming in, the final thing we wanted was a dashboard to track the progress everything. This ended up being quite simple — just a few aggregate numbers and lists of people who hadn’t responded or seen the invitation so we could follow up with them directly.

The guest dashboard view.
We actually used the dashboard a lot! It was a great place to see our planned attendance numbers, meal counts, and the lists of people we needed to follow up with to get RSVPs. Here’s a link the dashboard view code.
“I found it super, super helpful to know the max possible # of attendees since we need to provide the venue those numbers and there are significant cost implications.”
— My wife, Rowena
Conclusion
If you made it this far you’ve hopefully learned a few things about Django, the type of work that goes into building something like this, and how scope quickly creeps into even the simplest of things.
If you’re interested in using the project for your own wedding, don’t hesitate to get in touch! Everything is on Github and I’m happy to work with people to make it more open-source friendly.
Thanks for reading!
— Cory

About Me

Advertisements

Published by

Engineer IP Dev Crash Browser

Dad #engineering automatic 🚀🚀🚀🚀🚀🚀🚀🚀🇲🇦 https://www.freecodecamp.org/barkinet

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s