I'm a big fan of using the internet to share meaningful life events like weddings and births and so on. But I've been growing increasingly uncomfortable with using social media platforms like Facebook or Twitter for that - those platforms aren't designed, really, for optimizing real, human connection; they're designed to monetize human attention span by selling it to advertisers.

They're meant to drive "engagement" (our time, our attention), which is the product that they sell to their clients (the advertising industry). (Incidentally, I'm reading Tim Wu's The Attention Merchants, which talks about this and is v. good.) I especially don't feel comfortable sharing stuff about kids/babies; it's not giving their individual privacy a chance, it offers them up to the advertisers at a very young age. As minors, I feel that we should be protecting them from this stuff, not normalizing it and binding their childhoods/sense of self (?) to it. There's lots of other reasons not to use these platforms.

There are some off-the-shelf tools to address this - the problem of sharing meaningful, but private, life events with family and close friends, unmediated by advertisers and a profit motive - e.g. 23snaps or TheKnot's password-protected wedding websites?

But, again, it's not much control and I'm not sure it really is a good alternative: e.g. I don't know where the data is or how it's used - whether it's ultimately sold to those data aggregation/middleman agencies or used in machine learning work.

Side note: I used to work on household surveys in India (e.g. this), and it was important that consent - to be in the study, to be interviewed by us - was made a very explicit step before any survey began. Our surveyors read the consent form in Telugu, any and all questions from the potential respondent were answered, and only when they explicitly said it was okay would we enter to begin the survey. Right now, our "consent to be researched on" (i.e. included in A/B tests, have our data used in training machine learning algorithms) is buried in Terms of Service and End User License Agreements that no one reads. These TOSs can change, and, de facto, they're legal CYA for carte blanche data use.

But I digress.

My conclusion was to make something "in house": where I pay for the server space (instead of having advertisers pay for it), I keep it password protected, and basically the onus is on me to make sure it doesn't get used for bad purposes. Plus, learning how to make a password-protected website is a useful skill! So here's a blog post about that journey.

Specs

  1. A website
  2. That is password-protected
  3. And isn't hideous
  4. And is in Python
  5. And hopefully won't get hacked by bad actors :P

So this was going to be my first foray into "real" full-stack web development. Things like this website are static: they use CSS, HTML, and a bit of JavaScript. There's no need for a "backend" - there's no users, and therefore no database.

I had a bunch of questions of how this would work: how do I set up a database backend on a remote server? How do I store passwords? (Do I need to learn more about encryption?) Where do I put it (which webhost is good and cheap and not sexist?) and how do I use a custom domain that I already bought and paid for? I was hazy on all this stuff.

Flask, I choose you!

Full-stack development in Python is dominated by two popular frameworks: Django and Flask. Django has a reputation for being big and comprehensive and coming with lots of bells and whistles, while Flask is meant to be more minimalist. Since I wasn't building an industry-level app, I figured Flask would be fine. (I also liked the Horn of Gondor logo, heh.)

Finding the right tutorial

Honestly, the hardest thing on a learning journey is usually finding a compelling project. Once you have one of those, one tutorial is about as good as another. But I poked around the metalist and was ultimately quite happy with Miguel Grinberg's "mega" tutorial, which was modular and comprehensive.

In Miguel's tutorial, the project is building a social network-style app: there are users who make posts, and you need a database to keep track of people's logins, their posts, and so on. I ignored the specifics of the project and ported stuff over to my interest: making a private family website. I really recommend Miguel's tutorial (and won't attempt to re-do it here!).

Stuff I learned along the way

The model-view-controller pattern

Although I attended a RailsBridge weekend a long time ago, it didn't really meet me where I was. A lot of time was focused on getting our environments set up and basic programming. I think it's great for women just starting out in tech, since web development is a good career path (especially for people transitioning in) and RailsBridge assumes zero programming experience. It's awesome to get your feet wet.

But I had only one big takeaway from RailsBridge, and that was that there was something called the model-view-controller (MVC) pattern out there, and this was important for web frameworks. I was still kinda hazy on what was what (is the database the controller? or the model?): working through the mega-tutorial and building my first Flask app, though, really clarified stuff. And what a beautiful pattern it is!

MVC, wiki

(source)

My current understanding of the MVC is:

  • Model: The abstractions which your web app expects, and which will be turned into data; in Flask, these were abstracted into Python classes. e.g. A User, a Post, a Status or Photo or whatever. I just needed a User.
  • Views: The actual HTML/CSS that your web app presents to users.
  • Controller: The logic which underlies how the model and views (and the user and your website) interact. For example, some Users might get certain views (HTML pages), depending on whether they're logged in or not.

For the Flask app, my MVC looked like this (as described in Miguel Grinberg's tutorial):

- README.md
- my_app.py
- config.py
- my_app.db
- my_app/
  - __init__.py
  - forms.py
  - models.py
  - routes.py
  - templates/
    - a bunch of sweet html

Where my_app.py just imports my web app (the my_app/ folder) and makes it ready for running locally/on a server, templates/ is where all my views are (a bunch of .html files for, e.g., the index, login/logout pages, private photo gallery, and other stuff), routes.py is my controller (deciding how and when certain views are shown), forms.py separates out the user forms (e.g. the login form template, etc), and models.py defines a couple Python classes (User).

The thing I like about this pattern, and Flask, is:

  • All the HTML/CSS beautifying - and I think I'll use Bootstrap to quickly make it more pretty - is separated out into its own front end/views space.
  • Separating out the logic (routes.py) from the abstractions of data your web app expects (models.py) was also helpful. Specifically, thinking about what abstractions are appropriate for your web app. I'm making a plain vanilla "my website will have users!" web app, but I can imagine other, cool extensions: a TODO list (User, Task), a movie review website (User, Movie, Actor, Director, Theater), etc.
  • Flask had a lot of nice off-the-shelf stuff for common patterns (docs): e.g. having users and logging them in via a web form. All of those steps ((1) having users, (2) logging them in, (3) via a web form) had pre-baked stuff that just needed to be tinkered with, e.g. from my models.py:
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
from datetime import datetime
from my_app import db, login

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(120), index=True, unique=True)
    password_hash = db.Column(db.String(128))
    posts = db.relationship('Post', backref='author', lazy='dynamic')

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

    def __repr__(self):
        return '<User {}>'.format(self.email)

All that stuff - the UserMixin and werkzeug.security stuff for generating secure passwords (without me, as the webdev, ever seeing them in plaintext), was very cool.

Jinja HTML templating

From the docs:

"Jinja2 is a full featured template engine for Python. It has full unicode support, an optional integrated sandboxed execution environment, widely used and BSD licensed."

For my family web app, I used Jinja to create my views - i.e. my HTML files. Jinja allows you to create HTML that dynamically responds to stuff - e.g. it can access Python variables and, depending on them, display this or that <div> tag, etc. This is distinct from, say, writing JavaScript that dynamically responds to stuff (which would be another way, I suppose, to do all this). My templates/ folder has a bunch of Jinja-fied HTML, but the important one is base.html -

<html>
    <head>
        {% if user %}
        <title>Welcome, {{ user.username }}</title>
        {% else %}
        <title>Welcome!</title>
        {% endif %}
    </head>
    <body>
    {% block content %}{% endblock %}
    </body>
</html>

The money bits above are the curly braces stuff (e.g. {% if user %} in between <head>...</head>). That's the Jinja. user is a Python variable defined elsewhere in the project, it's one of our models, and - in base.html - we're going to modify how the the <head> attribute will look, based on whether that user has signed in or not.

Another nice thing about Jinja is that, after setting up the general look and feel of your website in base.html, you can create more *.html files that "inherit" that look and feel. Rather than copy-pasting the same header, navigation bar, or CSS link, you can just stick that all in your base.html and then do something like this for your index.html:

{% extends "base.html" %}

{% block content %}
    <h1>Hi, {{ current_user.email }}! Now you're in /index - wow!</h1>
    <p>Lorem ipsem blah blah</p>
{% endblock %}

In the above, index.html inherits from base.html ({% extends "base.html" %}) and then adds its own content ({% block content }).

Like pandas or regular expressions then, Jinja has its own special, additional syntax you need to learn - on top of regular old Python. All those curly braces! Why! But building a Flask app helped a lot in terms of learning it. (FWIW Pelican uses Jinja as well. I wish I had known more about that back in November!)

How to put it on the internet?

Once I got a small Flask app running locally, but before making the views non-hideous, I wanted to figure out how to run it on a remote server. I didn't want to invest a ton of front end time before confirming that I could figure out the back end. So: how to actually put this ugly app on the internet?

Running a Flask app locally is as simple as:

export FLASK_APP=path/to/my_app/my_app.py
flask run

This launches a local server and runs your web app on some port (e.g. 8000) in localhost. You just navigate your web browser (Chrome/Firefox/Safari) to localhost:8000 and voila - you should see your app. You can just CTRL+C the bash console that's running the server to close everything.

But how does this work on the actual, real internet? This was where PythonAnywhere came in.

"PythonAnywhere is an online Integrated Development Environment (IDE) and Web hosting service based on the Python programming language.[1] It provides in-browser access to server-based Python and Bash Command-line interfaces, along with a code editor with Syntax highlighting. Program files can be transferred to and from the service using the user's browser. Web applications hosted by the service can be written using any WSGI-based application framework." (wiki)

What's nice about PythonAnywhere was that it came with a bunch of "pythonista" stuff included: you can easily launch bash consoles or Jupyter notebooks or set up MySQL databases, a bunch of Python packages are pre-installed, setting up virtual environments is super easy, you can git clone stuff into your PythonAnywhere account, you can also drag + drop files (like a favicon!), and - most easily - you can launch a Django or Flask web app pretty painlessly. And they'll host it! And it's pretty cheap ($5/month)! And you can DNS resolve it to your custom domain! So nice.

What is WSGI?

But why couldn't I just buy some server space somewhere and run flask run?

If you want to run Python web apps (Django/Flask/whatever), you need to get Python (and Flask, etc.) installed on a server. So how do you do that? This is where WSGI (Web Server Gateway Interface) comes in. This article by Full Stack Python was very informative:

"A traditional web server does not understand or have any way to run Python applications. In the late 1990s, a developer named Grisha Trubetskoy came up with an Apache module called mod_python to execute arbitrary Python code. For several years in the late 1990s and early 2000s, Apache configured with mod_python ran most Python web applications.

However, mod_python wasn't a standard specification. It was just an implementation that allowed Python code to run on a server. As mod_python's development stalled and security vulnerabilities were discovered there was recognition by the community that a consistent way to execute Python code for web applications was needed.

Therefore the Python community came up with WSGI as a standard interface that modules and containers could implement. WSGI is now the accepted approach for running Python web applications."

Incidentally, linked from the Full Stack Python post, here's the super interesting story of WSGI's predecessor, mod_python, and how it was developed, as told by its creator.

Voila!

And that's it! I've really enjoyed beginning my full stack journey, and getting a Flask app up and running. I'm not going to link it because - well - it's hideous and has no CSS or content right now. But now that I got the back end part sorted, it's time for front end!