首席技术官
一个由Django支持的开源,响应式婚礼网站和邀请+来宾管理系统,具有250多个提交,单元测试等。 (An open-source, responsive, Django-powered wedding website and invitation + guest management system with 250+ commits, unit tests, and more.)
When my wife and I got married in the summer of I decided I wanted to make our own wedding website from scratch.
当我和妻子于夏天结婚时,我决定要从零开始建立自己的婚礼网站 。
Why not just use something like Wedding Wire or Squarespace you ask?
为什么不只使用您要求的婚戒或方形空间之类的东西呢?
Mostly because I thought it would be a fun little side project. As a seasoned web developer (at the time I was CTO of Dimagi) I like building things and this seemed like a good opportunity to spend a couple days throwing together some HTML and CSS and giving it a truly personal touch.
主要是因为我认为这将是一个有趣的小项目。 作为一名经验丰富的Web开发人员(当时我是Dimagi的首席技术官),我喜欢构建东西,这似乎是一个很好的机会,可以花几天的时间将一些HTML和CSS拼凑在一起,并赋予它真正的个人风格。
Of course, faster than you can say “scope creep” one thing led to another and soon I had built a rather significant application that included:
当然,一件事导致另一件事的速度比您所说的“范围蠕变”快,我很快就构建了一个相当重要的应用程序,其中包括:
A responsive, single-page traditional wedding website一个响应式,单页的传统婚礼网站 A complete guest management application完整的访客管理应用程序 Email framework for sending save the dates用于发送保存日期的电子邮件框架 Email framework for invitations and built in RSVP system用于邀请的电子邮件框架,并内置RSVP系统 A live guest dashboard实时访客仪表板
18 months and 250 commits later I thought it would be fun to write a bit about the project and share it with the world.
在18个月和250次提交之后,我认为写一点有关该项目并与全世界分享它会很有趣。
If you just want to see the code you can grab it on github.
如果您只想查看代码,可以在github上获取它 。
静态婚礼网站 (The Static Wedding Website)
At first the plan was just to make your standard, run-of-the-mill wedding website. You know the standard “here’s our engagement story” and “here’s the event information” type of stuff.
起初,计划只是建立您标准的,普通的婚礼网站。 您知道标准的“这里是我们的参与故事”和“这里是活动信息”类型的东西。
At the time I had a lot of experience making Django sites and not a lot of experience with other frameworks so I decided to just make the site in Django even though I wasn’t sure I needed a backend. If I wasonlymaking a static wedding website a better choice would probably have been a static site generator, but in the back of my mind I think I knew I wanted to do more with the project.
当时,我在创建Django网站方面有很多经验,而在其他框架方面则没有很多经验,因此即使我不确定是否需要后端,我还是决定只使用Django创建该网站。 如果我仅创建一个静态的婚礼网站,一个更好的选择可能是静态网站生成器 ,但是在我的内心深处,我想我知道我想对该项目做更多的事情。
For the site, I knew I wanted it to be visually appealing — at least as visually appealing as a design impaired backend developer like myself could muster — and mobile friendly. I also knew I wanted a single page app because those were all the rage. I ultimately decided to use Twitter Bootstrap and modified a free theme to meet my liking.
对于该网站,我知道我希望它在视觉上具有吸引力-至少与像我这样会受到设计障碍的后端开发人员所吸引那样在视觉上具有吸引力-并且对移动设备友好。 我也知道我想要一个单页应用程序,因为这些应用程序非常流行。 我最终决定使用Twitter Bootstrap并修改了一个免费主题以满足我的喜好。
This was all pretty standard HTML/CSS/JS/Bootstrap3 stuff so I won’t bore you with the details. If you want to see the finished product you can do that on our live site.
这些都是非常标准HTML / CSS / JS / Bootstrap3内容,因此我不会为您带来任何细节。 如果您想看成品,可以在我们的实时网站上进行 。
特征蠕变如何开始 (How Feature Creep Starts)
When we started thinking about our save-the-dates the first decision we made was to send them electronically. This seemed more efficient, more cost effective, more eco-friendly, and way, way easier than trying to get all of our friends’ and family’s addresses and mailing things by hand. (Especially from our new home in South Africa!)
当我们开始考虑保存日期时,我们做出的第一个决定就是以电子方式发送它们。 与尝试获取我们所有朋友和家人的地址并用手邮寄邮件相比,这似乎更有效,更具成本效益,更环保,而且方式也更容易。 (特别是从我们在南非的新家!)
Like many possibly bad decisions, this one started with five very simple words: “wouldn’t it be cool if…?”
像许多可能的错误决定一样,这一决定以五个非常简单的词开始:“如果……会不会很酷?”
In our case the phrase was “wouldn’t it be cool if instead of using something like evite or paperless post we sent out the save the dates ourselves? Then we could do something much more personalized and fun, like sending different versions to different people.”
在我们的案例中,这句话是“如果我们自己发送保存日期而不是使用类似evite或无纸化的帖子,这会很酷吗? 然后,我们可以做一些更加个性化和有趣的事情,例如向不同的人发送不同的版本。”
Hmm, I thought. That sounds pretty simple. We just need to make a few email templates, and then all we need is a list of all our guests and their email addresses lists and then we could just — either randomly or explicitly — assign them to a template and send it off.
嗯,我想。听起来很简单。我们只需要制作一些电子邮件模板,然后我们需要的是所有来宾的列表以及他们的电子邮件地址列表,然后我们可以(随机或显式)将其分配给模板并发送出去。
Sounds like a super simple model with a name, and email and then some other tracking properties. I bet I can whip that up in an hour.
听起来像是一个具有名称,电子邮件和其他一些跟踪属性的超级简单模型。我敢打赌,我可以在一小时内将其提高。
输入访客模型 (Enter the Guest Model)
If we were going to be sending emails to our guests we’d need to keep track of them. Fortunately, since I had chosen to use Django this was as simple as making a database model.
如果我们要向客人发送电子邮件,则需要跟踪他们。 幸运的是,由于我选择使用Django,所以这与创建数据库模型一样简单。
Here’s my first crack at theGuest
model.
这是我对Guest
模型的第一次了解。
Name, email, two fields to track whether we had sent the save the date and whether they’d opened it (a feature that was built later). Also a field to track whether they were officially invited — at the beginning we had some people who were on the fence (sorry crazy Aunties!) — and whether they were attending. Unlike the others, attending used aNullBooleanField
instead of aBooleanField
so we could represent the absence of information withNone
.
名称,电子邮件,两个字段,用于跟踪我们是否发送了保存日期以及是否打开了该日期(此功能是稍后创建的)。 还是跟踪他们是否被正式邀请的领域-最初我们有一些人在栅栏上(对不起疯狂的阿姨!)-他们是否参加。 与其他人不同,出席会议使用NullBooleanField
而不是BooleanField
因此我们可以使用None
来表示缺少信息。
Easy-peasy.
十分简单。
额外的蠕变:是聚会时间 (Additional Creep: It’s Party Time)
TheGuest
model was an ok start, but soon we had a problem: what about people whose emails we didn't have? Or people who didn't even have email like my brothers' kids? We needed a way to represent these people but we couldn't track their responses individually. Thus came theParty
model—to help represent a collection ofGuests
.
Guest
模式是一个不错的开始,但是很快我们遇到了一个问题:那些没有电子邮件的人呢? 还是像我兄弟的孩子一样没有电子邮件的人? 我们需要一种代表这些人的方法,但是我们无法单独跟踪他们的React。Party
模型就这样诞生了,以帮助代表Guests
的集合。
I bumped most of the metadata up from theGuest
model to theParty
and just leftname
,email
, andis_attending
on theGuests
. I also decided to split out first and last name at this stage—I think mostly to match the format we had in our guest spreadsheet. We also decided to add a "type" to the party field to represent whether our guests were "formal" or "fun"—since we wanted to send different save the dates to each crowd.
我碰到的最向上的元数据从Guest
模式向Party
和刚出name
,email
,以及is_attending
的Guests
。 我还决定在此阶段拆分名字和姓氏-我认为主要是为了与我们在来宾电子表格中使用的格式相匹配。 我们还决定在“派对”字段中添加一个“类型”以表示我们的客人是“正式”还是“有趣”,因为我们想向每个人群发送不同的保存日期。
Here’s the updated versions of theGuest
andParty
models:
这是Guest
和Party
模型的更新版本:
来宾进口商 (Guest Importer)
We had previously been tracking our guest list in a Google Sheet, and there was no way we were going to re-enter all those guests by hand.We needed an importer.
以前,我们一直在Google表格中跟踪我们的来宾列表,因此我们无法手动重新输入所有这些来宾。我们需要一个进口商。
I ended up making a pretty basic and dumb CSV processor, though one important thing about it was that it was idempotent — meaning it could be run safely multiple times in a row. We were still modifying our guest list in Google Sheets needed to be able to continually import it on top of the existing database. Since it seemed important for this part to be error-free I also decided to add some unit tests using a set of sample guests from Game of Thrones.
我最终制作了一个非常基本且笨拙的CSV处理器 ,尽管关于它的重要一点是它是幂等的 ,这意味着它可以连续安全地运行多次。 我们仍在修改Google表格中的来宾列表,以便能够将其连续导入到现有数据库的顶部。 由于确保这部分无误很重要,因此我还决定使用《权力的游戏》中的一组样本来添加一些单元测试。
终于准备好开始保存日期了 (Finally Ready to Start on Save the Dates)
After getting the models to a state where they could support everything we wanted to do it was finally time to start working on our save the dates.
在使模型达到可以支持我们所有功能的状态之后,终于可以开始保存日期了。
The first step involved finding an email template that would work across clients — a problem that is notoriously painful. My wife (also a former web developer) found one on and customized it to our needs.
第一步涉及找到适用于所有客户的电子邮件模板,这是一个非常痛苦的问题。 我的妻子(也是一名前Web开发人员)在上找到了一个,并根据我们的需求对其进行了自定义。
Since we wanted to send different templates to different guests we had to extract out the pieces of the template we wanted to customize. Eventually we ended up with a single template with a few customizable pieces: a header image, a main image, a background color, and a text color. It’s quite a lot of messy compatibility code so I won’t paste it here, but all the templates are on github here.
由于我们想将不同的模板发送给不同的来宾,因此我们必须提取我们想要自定义的模板片段。 最终,我们得到了一个模板,其中包含一些可定制的部分:标题图像,主图像,背景颜色和文本颜色。 它有很多混乱的兼容性代码,所以我不会在这里粘贴它,但是所有模板都在github上 。
We brainstormed a few different template ideas together and eventually came up with six — two “standard” ones with nice photos of us, 3 that were more silly/fun and one that was an inside joke that would only be relevant to people who worked for our company.
我们一起集思广益,讨论了几个不同的模板构想,最终提出了六个-两个“标准”构想,上面有我们的漂亮照片,三个更傻/很有趣,另一个是内心的笑话,只与那些为之工作的人有关我们公司。
Once we had this infrastructure in place, we just added all our save the dates to a list containing the various configurable properties, and wrote a bit of code to insert these values into a shared template:
一旦有了适当的基础架构,我们就将所有保存日期添加到包含各种可配置属性的列表中,并编写了一些代码将这些值插入共享模板中:
SAVE_THE_DATE_CONTEXT_MAP = {'lions-head': {'title': "Lion's Head",'header_filename': 'hearts.png','main_image': 'lions-head.jpg','main_color': '#fff3e8','font_color': '#666666',},'canada': {'title': 'Canada!','header_filename': 'maple-leaf.png','main_image': 'canada-cartoon-resized.jpg','main_color': '#ea2e2e','font_color': '#e5ddd9',},# other templates omitted for brevity}
Then we wrote a simple function to choose a template semi-randomly based on our defined criteria (which you can see in the comments):
然后,我们编写了一个简单的函数,根据定义的标准(您可以在注释中看到)半随机地选择模板:
As a final step before sending we decided to also make the landing page of our site a randomly selected save-the-date (since everyone would only be getting one template and we wanted people to see the others). We eventually moved this page here (if you refresh you will get a new one).
作为发送前的最后一步,我们决定也将站点的目标页面设置为随机选择的保存日期(因为每个人只会得到一个模板,而我们希望人们看到其他模板)。 我们最终将此页面移到了这里 (如果刷新,您将获得一个新页面)。
You can see the final set of save the dates below.
您可以在下面看到最后一组保存日期。
Finally we wrote a management command to handle the actual sending which called a function that looks something like this:
最后,我们编写了一个管理命令来处理实际的发送,该命令称为一个类似于以下内容的函数:
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
).
经过大量测试,我们确信一切都很好,现在是时候按下可怕的“发送”按钮了(在这种情况下,请运行./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:
在经历了数月的停机时间之后,应该发送邀请的三个月截止期限开始临近。 我们对保存日期的体验总体感觉良好,因此我们再次决定自己进行邀请和RSVP。 这增加了很多其他范围。 特别是,我们决定要:
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 finalParty
andGuest
models after the invitations were done:
所有这些额外的功能意味着另一个重大的模型更新。 邀请完成后,这是最终的Party
和Guest
模型:
The most important change was the addition of theinvitation_id
to theParty
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). Theinvitation_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.
最重要的更改是在Party
模型中增加了invitation_id
。 这是一个32个字符的GUID,我们在邀请URL中使用了它,以防止人们猜测其他人的邀请链接(如果使用标准整数,这将很容易)。invitation_id
还解决了允许访客无需登录即可进行RSVP的问题。 每一方将仅具有唯一的邀请ID,只有他们知道该ID才能用于RSVP。
In addition to theinvitation_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.
除了invitation_id
,各方还获得了有关发送和打开邀请的时间的一些元数据,以及供我们的客人发表祝福的注释字段。
MeanwhileGuests
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.
同时,Guests
获得了与RSVP相关的两个字段:一个用于选择餐点,另一个用于表示他们是否是孩子。 我们必须添加最后一个字段,因为我们不想为孩子提供餐食选择,这也帮助我们计划了最终的人头数和成本。
邀请电子邮件和公开跟踪 (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.
我们本来打算在邀请电子邮件中实现一个跟踪像素 ,以查看它们是否被打开,但最终还是得到了一种更简单的方法。 相反,我们决定将实际的邀请电子邮件作为“筋急转弯”,将您链接到“真实”邀请-这是您的个性化链接和RSVP页面。 然后,由于我们的邀请已经是每个方的个性化链接,因此我们决定将该链接的打开视为邀请的打开。
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.
一旦决定,在第一次打开页面时,只需添加一些代码即可更新跟踪字段,这非常容易。
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.
对于电子邮件本身,我们最终使用的是与保存日期完全相同的模板,只是这一次电子邮件必须聪明地根据将其发送给谁将正确的链接注入到邮件中,这是Django的一个简单过程模板渲染。
邀请和RSVP页面 (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).
邀请/ RSVP页面( 此处为示例 )非常简单(我是后端开发人员,所以我必须在UI方面保持相当简单)。
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:
基本上,它包括我们制作的简短的嵌入式视频,一些事件详细信息以及一个RSVP表单。 最棘手的是验证和进餐逻辑,如下所示:
You must RSVP for all guests in your party您必须为聚会中的所有客人进行RSVP 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:
我为UI和大多数javascript逻辑使用了出色的Bootstrap 3验证程序库 ,并且只需要添加一段手动JavaScript即可连接出勤/进餐流程:
Here’s what this part looked like:
这是这部分的样子:
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.
最后,在您提交表格(当然会相应地更新您的出勤和就餐偏好)之后,我们将您带到自定义的确认页面,其中包含感谢信息和其他一些信息。
发送邀请 (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 actuallyzero bugsencountered by our guests during the RSVP process, a fact I am quite proud of.
再次经过全面的测试并经过多次空运行,一切都变得异常顺利,然后才发出真正的邀请。 在RSVP流程中,我们的客人实际上遇到了零个错误,这一点我感到非常自豪。
On the flip side, at this point I had put in well over 40 hours of time on nights and weekends building the site.
另一方面,到目前为止,我在晚上和周末花费了40多个小时来构建该网站。
最后一件:现场来宾仪表板 (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.
一旦邀请函发出,RSVP进入,我们想要的最后一件事就是仪表板,以跟踪所有进度。 最终,结果非常简单-仅有一些总数和未答复或未看到邀请的人员列表,因此我们可以直接跟进他们。
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.
实际上,我们经常使用仪表板! 在这里,您可以查看我们计划的出勤人数,用餐次数以及我们需要跟进以获得RSVP的人员名单。 这是仪表板视图代码的链接。
“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.
如果您走到了这一步,那么您就已经学到了一些有关Django的知识,希望能够学到构建这样的东西的类型,以及如何将范围快速地蔓延到最简单的东西。
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.
如果您有兴趣将项目用于自己的婚礼,请随时与我们联系! 一切都在Github上 ,我很高兴与人们合作以使其更加开源友好。
Thanks for reading!
谢谢阅读!
— Cory
—科里
关于我 (About Me)
I’m the former CTO of Dimagi, where I led the team that makes CommCare HQ — one of the larger Django codebases I’m aware of — for 6 years. I’ve been developing Django apps since .
我是Dimagi的前CTO,领导了CommCare HQ (我所知道的较大的Django代码库之一)开发团队6年。 自以来,我一直在开发Django应用程序。
I’m currently working on a Django SaaS template, along with other side projects, including an app to make printable place cards online and a Chrome extension to personalize your new tab screen with your own photos.
我目前正在使用Django SaaS模板以及其他辅助项目,包括一个用于在线制作可打印位置卡片的应用程序和一个Chrome扩展程序,以使用自己的照片个性化您的新标签页 。
I originally published this on my blog at www.placecard.me.
我最初将此内容发布在我的博客www.placecard.me上 。
翻译自: /news/when-a-cto-decides-to-make-a-wedding-website-256061b64e7f/
首席技术官
如果觉得《首席技术官_当首席技术官决定建立一个婚礼网站时》对你有帮助,请点赞、收藏,并留下你的观点哦!