Python - 301 - Flask
  • 00 - Appealing API's
  • 01- A Smooth Start
  • 02 - Chief Configuration
  • 03 - Example Endpoint and an Adornable App
  • 04 - Modeling the Models
  • 05 - Scheming Schemas
  • 06 - Making Migrations
  • 07 - Uniformed Users
  • 08 - Alarming Authentication
  • 09 - Account Actions
  • 10 - Postman Prevalence and Examining Endpoints
  • 11 - Blog Post Blogging
Powered by GitBook
On this page
  • Preparation Steps
  • The User Model
  • The Blog Post Model

Was this helpful?

04 - Modeling the Models

In this module we will define our database models and schemas

Previous03 - Example Endpoint and an Adornable AppNext05 - Scheming Schemas

Last updated 6 years ago

Was this helpful?

Preparation Steps

Flask-SQLAlchemy provides a nice, easy to use, high level, ORM (object relational mapper). ORM's allow us to easily manipulate our database, with tiny bits of code. To do this, we use something called migrations. More on these later.

We are also going to add Bcrypt. Bcrypt is a nice encryption library that will help us with hashing and verifying passwords

Open up the file models/__init__.py and add the following code. When we add to an __init__, the entire package (directory) will be able to access from here

This will allow us to bind our models to SQLAlchemy's Model and Column objects.

The User Model

Open up models/user.py and add the following code

import datetime
from marshmallow import fields, Schema  # we will use these later

from . import db
from ..app import bcrypt

from .blog_post import BlogPostSchema

class UserModel(db.Model):
    __tablename__ = 'user'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128), nullable=False)
    email = db.Column(db.String(128), unique=True, nullable=False)
    password = db.Column(db.String(128), nullable=False)
    created_at = db.Column(db.DateTime)
    modified_at = db.Column(db.DateTime)
    # navigational property
    blogposts = db.relationship('BlogPostModel', backref='users', lazy=True)

    def __init__(self, data):
        self.name = data.get('name')
        self.email = data.get('email')
        self.password = self._generate_hash(data.get('password'))
        self.created_at = datetime.datetime.utcnow()
        self.modified_at = datetime.datetime.utcnow()

    def __repr__(self):
        return f'<id {self.id}>'

    def _generate_hash(self, password):
        return bcrypt.generate_password_hash(password, rounds=10).decode('utf-8')

    def check_hash(self, password):
        return bcrypt.check_password_hash(self.password, password)

    def delete(self):
        '''deletes row from db'''
        db.session.delete(self)
        db.session.commit()

    def save(self):
        '''saves current state of model to db'''
        db.session.add(self)
        db.session.commit()

    def update(self, data):
        '''takes in data to modify model'''
        for key, item in data.items():
            if key == 'password':
                self.password = self._generate_hash(value)
            setattr(self, key, item)
        self.modified_at = datetime.datetime.utcnow()
        db.session.commit()

    @staticmethod
    def get_all_users():
        return UserModel.query.all()

    @staticmethod
    def get_user_by_email(value):
        return UserModel.query.filter_by(email=value).first()

    @staticmethod
    def get_one_user(id):
        return UserModel.query.get(id)

This is a lot to understand what's going on all at once. So, let's break it down step by step.

__tablename__ : This variable will tell sqlalchemy what to call the table when it migrates

line 10 - 15 : These class attributes define what will go into the table and how the columns will be represented

__init__ : This is just a constructor, it will handle creating our objects before we persist data to the database

save/update/delete : These methods perform the equivalent actions to an entry to the database. Notice that in the update method, we never explicitly replace the actual instance attributes. This is still fine, because SQLAlchemy will ONLY commit attributes that are listed as db.Column

get_one_user/get_all_users : These methods are self explanatory however they are static methods. Think of them as not being tied to an instance and have an overarching view on the database

repr : Just in case we want to stringify the instances

generate_hash/check_hash : We also have a couple of helper methods that incorporate bcrypt. These are mostly just abstraction steps so we don't need the dependency throughout the entire project.

blogposts : This is navigational property. This just tells SQLAlchemy that there is a connection between these two models, but we haven't built out the blog post model yet. This is called coding with intent. We intend to implement it, it's just not there yet. Let's jump into it now

The Blog Post Model

Add the following to models/blog_post.py

from . import db
from datetime import datetime


class BlogPostModel(db.Model):
    __tablename__ = 'blogposts'

    id = db.Column(db.Integer, primary_key=True)
    owner_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    title = db.Column(db.String(128), nullable=False)
    content = db.Column(db.Text, nullable=False)
    created_at = db.Column(db.DateTime)
    modified_at = db.Column(db.DateTime)

    def __init__(self, data):
        self.owner_id = data.get('owner_id')
        self.title = data.get('title')
        self.content = data.get('content')
        self.created_at = datetime.utcnow()
        self.modified_at = datetime.utcnow()

    def __repr__(self):
        return f'<id {self.id}>'

    def delete(self):
        db.session.delete(self)
        db.session.commit()

    def save(self):
        db.session.add(self)
        db.session.commit()

    def update(self, data):
        for key, item in data.items():
            setattr(self, key, item)
        self.modified_at = datetime.utcnow()
        db.session.commit()

    @staticmethod
    def get_all_blogposts():
        return BlogPostModel.query.all()

    @staticmethod
    def get_one_blogpost(id):
        return BlogPostModel.query.get(id)

Take note of the owner_id on line 10 in the above snippet. This is our foreign key that links us to the user model, specifically the users primary key. This will aid us when we start creating our routes that are responsible for deleting and editing the blog posts. Essentially, we don't want other users to edit, or even delete, someone else's post