Building a Simple Birthday App with Flask-SQLAlchemy

By on 11 May 2017

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.

FB birthday data

I am almost sure you could use the FB API before to pull all your friends and birthdays. Not anymore 🙁

Luckily I found a way to export them and parse them into a useful format – see bdays.py.

Starting Flask-SQLAlchemy

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 '' % (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:

    db.drop_all()
    db.create_all()
    

    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('/')
      ...
      _, 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)
      

Resulting App

bday app upcoming

bday app for a particular month

You can use the calendar module to get the month name for a month int:

month_name = calendar.month_name[month]

See app.py.

TODO

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.

Resources


Keep Calm and Code in Python!

— Bob

Want a career as a Python Developer but not sure where to start?