One of my favorite Flask extensions is Flask-SQLAlchemy. It makes working with a database a breeze. For some time I wanted to detach my birthday management from Facebook. So I started a simple Flask app. Work so far here.
I am almost sure you could use the FB API before to pull all your friends and birthdays. Not anymore :(
Back to the article subject: how do we get this data into a DB? Flask-SQLAlchemy to the rescue:
First I defined a simple model in model.py:
... class Birthday(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(120)) bday = db.Column(db.DateTime) notify = db.Column(db.Boolean) def __init__(self, name, bday, notify=False): self.name = name self.bday = bday self.notify = notify def __repr__(self): return '<Birthday %r %r %r>' % (self.name, self.bday, self.notify)
It's best to store dates as db.DateTime objects so we can easily query them (see further down).
If model.py is run as standalone script (not imported) it recreates the DB:
I use the bdays.py ics parsing code to populate the table with all birthdays. You can even strip out the names (which was useful to share printscreens here). I store all birthdays with the same year (calendar ics ranges May '17 - May '18), otherwise the date querying fails (next step):
# insert birthdays sorted for bd in sorted(get_birthdays('cal.ics'), key=lambda x: (x.bday.month, x.bday.day)): # no real names if TEST_MODE: name = get_random_name() else: name = bd.name # import all bdays with THIS_YEAR to make it easier to query later bday = bd.bday.replace(year=THIS_YEAR) bd_obj = Birthday(name, bday) db.session.add(bd_obj) db.session.commit()
The app is still very bare-bones. It has an index/home route, to get the birthdays of the next 14 days, and a route to get birthdays for each month. See app.py. Here's why you want to work with datetime objects, it makes querying dates easier:
Upcoming n days:
@app.route('/') ... start = datetime.now() end = start + timedelta(days=UPCOMING_DAYS) bdays = Birthday.query.filter(Birthday.bday <= end).filter(Birthday.bday >= start)
How to get all birthdays of month n (see SO). The SQLAlchemy query is the same:
@app.route('/<int:month>') ... _, num_days = calendar.monthrange(THIS_YEAR, month) start = date(THIS_YEAR, month, 1) end = date(THIS_YEAR, month, num_days) bdays = Birthday.query.filter(Birthday.bday <= end).filter(Birthday.bday >= start)
You can use the calendar module to get the month name for a month int:
month_name = calendar.month_name[month]
This is it for starters. In part 2 I will make the app more functional:
Implement notifications: email me one day before a birthday.
Allow setting of notify == True for individual friends so I only get the notifications I want.
Full CRUD: add/update/delete friends and/or re-import new ics download.
Second (relational) model for tracking:
add the notifications sent to this table
have a "done" flag to update when I sent Happy Birthday wishes to a particular friend.
This is a nice extension to use Flask-SQLAlchemy's db.ForeignKey.
Flask-SQLAlchemy docs is a great start.
See our code challenge 15 review for more example apps using Flask-SQLAlchemy.
For examples of standard SQLAlchemy (outside Flask), our code challenge 17 review has some examples.
To learn SQLAlchemy start with the Object Relational Tutorial.
Keep Calm and Code in Python!
Do you want to get 250+ concise and applicable Python tips in an ebook that will cost you less than 10 bucks (future updates included), check it out here.
"The discussions are succinct yet thorough enough to give you a solid grasp of the particular problem. I just wish I would have had this book when I started learning Python." - Daniel H
"Bob and Julian are the masters at aggregating these small snippets of code that can really make certain aspects of coding easier." - Jesse B
"This is now my favourite first Python go-to reference." - Anthony L
"Do you ever go on one of those cooking websites for a recipe and have to scroll for what feels like an eternity to get to the ingredients and the 4 steps the recipe actually takes? This is the opposite of that." - Sergio S