Our Start-Up is 25% Pregnant 🎊

Anvil is expanding. I mean, yes, I’ve gained a lot of new colleagues lately, but it’s not just that. Bridget and I are developing some pretty impressive baby bumps, and our newest teammate Stu is expecting a baby too – and all three babies are due in the new year!

Naturally, this has meant a lot of nerdy tracking of how the pregnancies are going. For some reason, baby sites seem obsessed with the idea of comparing the size of your baby to fruit and veg. So I decided to make my own fruit-based pregnancy app - with a twist.

We love Emoji πŸ˜‚

Emoji are taking over the world! They’re everywhere you look, adding a dash of colour to texts, tweets and chats with friends and colleagues.

Emoji were initially invented for Japanese email and phone programs. They proved so popular the Unicode Consortium officially adopted them back in 2007 and has reserved a set of code points to represent emoji. Since then emoji have increased in number and spread around the world, replacing older ways of conveying emotion in text like :-). They’ve become a fun way to express ourselves that can cross language barriers.

Even Oxford Dictionaries joined in. They explained:

That’s right – for the first time ever, the Oxford Dictionaries Word of the Year is a pictograph: πŸ˜‚, officially called the β€˜Face with Tears of Joy’ emoji, though you may know it by other names. There were other strong contenders from a range of fields but πŸ˜‚ was chosen as the β€˜word’ that best reflected the ethos, mood, and preoccupations of 2015.

Oxford Dictionaries

Word of the Year 2015

So why not represent pregnancy with emoji too?

Python & Emoji 🐍

I built my web app with Anvil, which means I could write everything in Python! This made emoji-wrangling particularly easy - Python 3 is great with Unicode, and most web browsers now support emoji display too. I previewed how they would look for different browsers using the Unicode site.

Now it’s easy to get your 5 emoji fruit and veg a day!

Now it’s easy to get your 5 emoji fruit and veg a day!

And with Anvil, that’s all we need to worry about! This is great news for those of us who like to express ourselves with the power of 😎.

Building the app 🏠

Anvil’s online interface made it easy to start building my pregnancy tracker. I wanted a splash of colour so I chose the ‘Hello San Francisco’ theme and replaced the header image with a cute photo of a fruit-stall I found on pexels.com.

Adding labels to my Form. I chose the font ‘Pacifico’.

Adding labels to my Form. I chose the font ‘Pacifico’.

Font fun ✍

The best part was finding appropriate emoji for each week of pregnancy. There are quite a few fruits and veggies available as emoji but some creative choices had to be made!

I found it harder to pick emoji for the final weeks and really stretched the definition of “fruit or vegetable”! For example, for 37 weeks I resorted to πŸ₯—, and any time after the due date (40+ weeks) I just display ‘πŸ‘Ά’. I’d started to run out of large vegetables πŸŽƒ and most babies will make an appearance before this point.

To display all the emoji, you’ll need a fairly recent browser. I didn’t have any issues on Windows and Mac, but if you’re using Linux you might need to update your system fonts. Otherwise you’ll see funky-looking black-and-white emoji!

Data tables πŸ“‘

I decided to use Anvil’s built in database to store the emoji I chose for each week. This was easier than trying to keep track of it all in my code.

The table of emoji aliases vs weeks of pregnancy I made. 1 to 4 weeks have the same emoji, sparkles ✨ because things are so tiny at that point.

The table of emoji aliases vs weeks of pregnancy I made. 1 to 4 weeks have the same emoji, sparkles ✨ because things are so tiny at that point.

You can store emoji directly in an Anvil Data Table - all Unicode is fully supported. But for convenience I stored emoji aliases in my Data Table instead, and made a little dict to translate between the aliases and emoji to display. This made it easier to play around with which emoji corresponded to which week. And it made it easier to see what I was doing regardless of what browser I was using, as some don’t render all emoji.

EMOJI_LOOKUP = {'sparkles': '✨', 'salt': '\U0001f9c2', 'cherries': 'πŸ’', 'strawberry': 'πŸ“', 'mushroom': 'πŸ„', 'chestnut': '🌰', 'peanuts': 'πŸ₯œ', 'kiwi': ':kiwi:', 'tangerine': '🍊', 'peach': 'πŸ‘', 'lemon': 'πŸ‹', 'apple': ':apple:', 'avocado': 'πŸ₯‘', 'onion': '\U0001f9c5', 'potato': 'πŸ₯”', 'mango': '\U0001f96d', 'banana': '🍌', 'carrot': 'πŸ₯•', 'hot_pepper': '🌢', 'pear': '🍐', 'ear_of_corn': '🌽', 'broccoli': '\U0001f966', 'leafy_green': '\U0001f96c', 'burrito': '🌯', 'eggplant': 'πŸ†', 'grapes': 'πŸ‡', 'cucumber': 'πŸ₯’', 'pineapple': '🍍', 'bread': '🍞', 'sunflower': '🌻', 'bouquet': 'πŸ’', 'coconut': '\U0001f965', 'melon': '🍈', 'green_salad': 'πŸ₯—', 'jack-o-lantern': 'πŸŽƒ', 'watermelon': 'πŸ‰', 'baby': 'πŸ‘Ά'}

I generated EMOJI_LOOKUP using Python’s emoji.emojize module. Just pip install emoji locally to give it a try yourself. Here’s what I did:

import emoji

# exported from table in Anvil with Server function
alias_list = ['sparkles', 'sparkles', 'sparkles', 'sparkles', 'salt', 'cherries', 'strawberry', 'mushroom', 'chestnut', 'peanuts', 'kiwi', 'tangerine', 'peach', 'lemon', 'apple', 'avocado', 'onion', 'potato', 'mango', 'banana', 'carrot', 'hot_pepper', 'pear', 'ear_of_corn', 'broccoli', 'leafy_green', 'burrito', 'eggplant', 'grapes', 'cucumber', 'pineapple', 'bread', 'sunflower', 'bouquet', 'coconut', 'melon', 'green_salad', 'jack-o-lantern', 'watermelon', 'baby', 'baby']

if __name__ == "__main__":

    EMOJI_LOOKUP = {alias: emoji.emojize(":{}:".format(alias)) for alias in alias_list}

    print(EMOJI_LOOKUP)

I only needed to run it once. Then I popped EMOJI_LOOKUP into my app code. Notice that it doesn’t matter that some code points are represented long-form (like '\U0001f9c2') because I didn’t have the full emoji supporting fonts available in my local terminal. The Python interpreter in Anvil knows how to deal with that format, too.

Returning users 🀩

Using the User Service I created a login flow for return users, so they can keep tracking their pregnancy throughout its duration without needing to redo their due date calculation each time they visit.

Emails πŸ“§

Users of the app can optionally sign up for weekly update emails. I did this by adding the Email Service and customised it with an unsubscribe option.

Each day, my app runs a Scheduled Task to send an email to those users who signed up for emails and have reached their next gestation week that day. Users get dropped from the table and email list after 43 weeks (at which point the baby should hopefully have made an appearance!)

Unsubscribing πŸ””

For obvious reasons, I wanted to make it as easy as possible for users to unsubscribe from the weekly emails. They shouldn’t need to log in to the app to do it.

But I had to make sure that people could only unsubscribe themselves, not other users. If I just set up a generic “Unsubscribe” API, it would be too easy for someone to accidentally (or maliciously) remove other people from the mailing list.

The solution: generate a secret token for each user. If we get a request with the right token, it must be from that user, and we can unsubscribe them immediately. I used token_urlsafe() from the secrets module to generate a unique token for each user that is safe to use as part of a URL.

When the app emails a user, the message contains an unsubscribe url customised with their secret token:

"To unsubscribe, click here: {app}/_/api/unsubscribe/{unsub}".format(app=anvil.server.get_app_origin(), unsub=user['unsubscribe_token'])

I set up a custom HTTP API endpoint to handle clicks on those unsubscription links:

@anvil.server.http_endpoint("/unsubscribe/:id")
def unsubscribe_user(id):
  """
  Enable users to unsubscribe from email updates by clicking this API link.
  Check the id matches the unique unsubscribe token stored in the table against them 
  and if it does, remove that user's email subscription.
  """
  ip = anvil.server.request.remote_address
  user_to_unsubscribe = app_tables.users.search(unsubscribe_token=id)
  
  for user in user_to_unsubscribe:
    unsub_email = user['email']
    user.update(signed_up_for_emails=False)
    # Redirect user to app after unsubscribing them.
    redirect = anvil.server.HttpResponse(302, f"Unsubscribed user {unsub_email} from IP {ip}, now trying to redirect")
    redirect.headers['Location'] = "{}#unsubscribed".format(anvil.server.get_app_origin())
    return redirect

  return "Could not unsubscribe you - please check link is correct."

The corresponding code in the __init__ of the Form uses get_url_hash():

    # If user got here by clicking unsubscribe link in their email
    if get_url_hash() == "unsubscribed":
      alert("You have been unsubscribed from Fruitmoji emails.")

Users just click the link that appears in their email, leading them to a popup in the app which confirms they’ve been removed from the mailing list.

Learn more πŸ“

If you’re interested in discovering more about how the β€˜Fruitmoji’ app works, take a look at the clone link below. It might be a helpful template for collecting a mailing list of your users with an unsubscribe mechanism:


Build your own app with Anvil

If you’re new here, welcome! Anvil is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks – just build it all in Python.

Want to build an app of your own? Get started with one of our tutorials: