<style> .present { text-align: left; } </style> # Using SQLAlchemy with Flask ## Week 18 Day 3 --- ## Lecture Videos 1 (18min) Watch: - More About Classes (15:55) --- ### Instance variables - Specific to each instance of the class—changing on one instance won't affect another - Defined in the constructor and attached to `self` - You can access on an instance, but not on the class as a whole ```python= class Widget: # constructor def __init__(self, color): # instance variables self.color = color my_widget = Widget("blue") print(my_widget.color) # "blue" print(Widget.color) # error ``` --- ### Class variables - Shared across instances and on the class itself - Each instance points to the same class variables, so updating the value on the class will change for all instances (unless their value was already changed) - Updating on one instance will not change other instances ```python= class Widget: price = "$5" def __init__(self, color): # instance variables self.color = color my_widget = Widget("blue") second_widget = Widget("chartreuse") print(my_widget.price) # "$5" print(Widget.price) # "$5" my_widget.price = "$100" print(second_widget.price) # "$5" print(Widget.price) # "$5" Widget.price = "$50" print(second_widget.price) # "$50" print(my_widget.price) # "$100" ``` --- ### Instance methods So far, we've written instance methods on classes—that is the default type of method on a class. - `self` is passed in implicitly as the first parameter—this refers to the instance the method was invoked on ```python= # inside class def greet_widget(self): return f"hello, {self.color} widget" my_widget = Widget("blue") print(my_widget.greet_widget()) # "hello blue widget" ``` --- ### Class methods We can use the `@classmethod` decorator to write class methods. The first argument will refer to the class itself (conventionally called `cls`), rather than an individual instance. ```python # inside class @classmethod def widget_factory(cls, colors): widgets = [cls(color) for color in colors] print([widget.greet_widget() for widget in widgets]) return widgets print(Widget.widget_factory(["red", "yellow", "beige"])) ``` --- ### Static methods Static methods don't take implicit arguments—they can't access the class or any instance of it. ```python= @staticmethod def something_about_widgets(): return "widgets are neat" ``` --- ## Lecture videos 2 (8 min) Watch: - FlaskSQLAlchemy Set-Up Best Practices (6:15) --- ### Steps for setting up Flask-SQLAlchemy 1. Create your database & user in psql. ```sql create user widget_user with password 'password'; create database widget_database with owner 'widget_user'; ``` 2. Add a DATABASE_URL to your `.env` ```bash= DATABASE_URL=postgresql://widget_user:password@localhost/widget_database ``` 3. Install psycopg2, sqlalchemy, and flask-sqlalchemy ```bash pipenv install psycopg2-binary sqlalchemy flask-sqlalchemy ``` --- ### Steps for setting up Flask-SQLAlchemy 4. Modify your config class. a. Add a class variable called `SQLALCHEMY_DATABASE_URI`, and set the value to the `DATABASE_URL` from your environment b. Add a class variable called `SQLALCHEMY_TRACK_MODIFICATIONS` and set the value ```python= SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL") SQLALCHEMY_TRACK_MODIFICATIONS = False ``` --- ### Steps for setting up Flask-SQLAlchemy 5. Create a models.py file, and instantiate a `SQLAlchemy` object from `flask_sqlalchemy`. ```python= from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() ``` --- ### Steps for setting up Flask-SQLAlchemy 6. Import the `db` instance into the file where you are defining your app. ```python= from app.models import db # much additional code... db.init_app(app) ``` --- ## Lecture Videos 3 (32 min) Watch: - FlaskSQLAlchemy: Creating Mapping Files (15:00) - FlaskSQLAlchemy: Interacting with Data (13:10) --- ### Creating Models Models inherit from the `db.Model` class, which comes from our `SQLAlchemy` instance. If you want to specify a tablename, use the `__tablename__` property. ```python= db = SQLALchemy() class Widget(db.Model): __tablename__ = "widgets" ``` --- ### Columns on models Columns are added to the model as class variables—instances of `db.Column` from our `SQLAlchemy` instance. Column types (`db.String`, `db.Integer`, etc.) also come from our SQLAlchemy instance. ```python= class Widget(db.Model): __tablename__ = "widgets" id = db.Column(db.Integer, primary_key=True) color = db.Column(db.String(50), nullable=False) ``` --- ### One-To-Many Relationships Let's make a relationship between widgets and stores. Each widget belongs to one store, stores can have many widgets. ```python= class Store(db.Model): __tablename__ = "stores" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), nullable=False) ``` --- ### One-To-Many Relationships First we need to add a foreign key to the widgets model that points to the store model. To make a column into a foreign key we use `db.ForeignKey(tablename.columnname)`. ```python= class Widget(db.Model): __tablename__ = "widgets" id = db.Column(db.Integer, primary_key=True) color = db.Column(db.String(50), nullable=False) store_id = db.Column(db.Integer, db.ForeignKey("stores.id")) ``` --- ### Adding the relationship property Add a `db.relationship` to both of the models. Use the `back_populates` property to connect the two relationship attributes. ```python= attribute_name = db.relationship("OtherModelName", back_populates="name_of_attribute_from_opposite_model") ``` ```python= # on the Widget model store = db.relationship("Store", back_populates="widgets") ``` ```python= # on the Store model widgets = db.relationship("Widget", back_populates="store") ``` --- ### Many-to-Many Relationships Many-to-many relationships are a lot like one-to-many relationships in terms of how they use the `db.relationship` property. However, since neither of the two associated tables contains the foreign keys, we would need to build a separate association table to connect the models. We would use a `db.Table` to do make an association table. --- ### Many-to-Many Relationships The first argument to `db.Table` will be the table name. Each additional argument is a column. A typical association table only needs two columns—one for each of the two foreign keys. It does not need its own id column (this is different from Sequelize). ```python= widget_stores = db.Table( "widget_stores", db.Column( "widget_id", db.Integer, db.ForeignKey("widgets.id"), primary_key=True ), db.Column( "store_id", db.Integer, db.ForeignKey("stores.id"), primary_key=True ) ) ``` --- ### Many-to-Many Relationships The only change we have to make the `db.relationship` attributes is to add a `secondary` argument. The value here is the table. ```python= attribute_name = db.relationship( "OtherModelName", secondary=association_table_name, back_populates="name_of_attribute_from_opposite_model" ) ``` --- ### Creating records To make a new row in your database, first create a new instance of one of your model classes. After you create your instance, add it to your session and commit it to the database. ```python= new_widget = Widget(color="red") db.session.add(new_widget) db.session.commit() ``` --- ### Querying with SQLAlchemy To query the database, we use the `query` attribute on our `db.Model` classes. Get all records: ```python= widgets = Widget.query.all() ``` Get one record by primary key: ```python= one_widget = Widget.query.get(id) ``` --- ### Filtering on queries To apply a filter, you can use the `.filter` method, passing in an operator on a column. Use `.all()` to get a list of all records that match the filter, or use `.first()` to get the first matching record. ```python= # all widgets where color is blue Widget.query.filter(Widget.color == "blue").all() ``` ```python= # all widgets where the color starts with "r" Widget.query.filter(Widget.color.like("r%")).first() ``` [more available operators](https://docs.sqlalchemy.org/en/14/core/operators.html) --- ### Updating records Once you have queried or created an instance of your model, you can update it by changing a value on that instance, and adding/commiting to the db. ```python= widget = Widget.query.get(1) widget.store_id = 1 db.session.add(widget) db.session.commit() ``` --- ### Deleting records Deleting records uses the `delete` method on the `db.session` object. ```python= widget = Widget.query.get(1) db.session.delete(widget) db.session.commit() ```