Skip to content
Shop

CommunityJoin Our PatreonDonate

Sponsored Ads

Sponsored Ads

Make a Flask CRUD App

September 18, 2024

You will learn how to make a Flask CRUD app using Flask, Tailwind and HTMX!

Note

This tutorial only includes the CRUD with no styles. You can purchase the full app with the UI on the Dev Shop

What you will need:

Data Models

This app will have one-to-one and many-to-many relationships.

User

A task to complete

ColumnTypeDescription
idintunique id for the user
usernametextunique username for the user
passwordtextpassword for the user
details_idMore details about the user

Item

A generic item. Edit these fields to your needs

ColumnTypeDescription
idintunique id for the user
nametextunique username for the user
descriptiontextpassword for the user
image_urltexturl to image for item
numeralinta number
numeral_nametextthe name of the numeral
created_attextdate item was created
updated_attextdate item was updated
.........

UserItems

A user has many items and an item belongs to many users.

ColumnTypeDescription
idintunique id for the user detail
user_idtextid of the user
item_idtextid of the item

User Details

More details about a user. Edit these fields to your needs

ColumnTypeDescription
idintunique id for the user detail
user_idtextid of the user
.........

UI/UX

This is the final UI. I left the other components outside of the index.html and show.html files blank so that you can update them to look however you'd like.

Mockup By ilnicki

Requirements

Functional Requirements

  • CRUD Users
  • Save to JSON

Non Functional Requirements

  • Beautiful - Nice looking UI
  • Useful - could be used for real work
  • Stable - No bugs

Technical Requirements

  • Desktop
  • Responsive

Business Rules

These business rules were generated by Google Gemini. These are only ideas for future features and are not included in this project (TBA):

Create a new Flask App

Create the folders for your flask app. You should be able to copy the content below and paste it into your terminal. You can also create the folders manually using your mouse and keyboard. the app_name/ folder can be called whatever the name of your app is. For example you can do mkdir inventory_app instead of mkdir app_name.

bash
mkdir app_name
cd app_name
touch app.py
touch config.py
mkdir templates
touch templates/base.html
touch templates/index.html
touch templates/_nav.html
touch templates/_footer.html
mkdir templates/users
touch templates/users/index.html
touch templates/users/show.html
touch templates/users/new.html
touch templates/users/edit.html
mkdir templates/items
touch templates/items/edit_item.html
touch templates/items/new_item.html
mkdir static
touch static/style.css
touch static/script.js

This is the folder structure of the app.

markdown
	app_name/
		templates/
			users/
				index.html
				show.html
				edit.html
				new.html
            items/
                edit_item.html
                new_item.html
			base.html
			index.html
            _nav.html
            _footer.html
		static/
			script.js
			style.css
		app.py
		config.py

Install dependencies

Create a virtual environment and install flask and flask-sqlalchemy

bash
python3 -m venv .venv
source .venv/bin/activate
pip3 install flask
pip3 install flask-sqlalchemy

Configuration

Set up some configuration.

Optional Folder Structure
markdown
	app_name/
		templates/
			alerts/
				_error_messages.html
				_success.html
				_error.html
			components/
				_nav.html
				_footer.html
			auth/
				login.html
				register.html
            items/
                edit_item.html
                new_item.html
			users/
				index.html
				show.html
				edit.html
				new.html
			base.html
			index.html
		static/
			script.js
			style.css
		app.py
		config.py
Optional Configuration

You will need to add your own logo to the nav.html component and replace the logo image.

Other components such as footer.html login.html and register.html haven't been implemented and are there for your convenience.

html
<html>
	<head>
	<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
	</head>
	<body>
        <div class="p-10">
      	{% if request.endpoint != 'new'%}
        	{% include 'components/_nav.html' %}
        {%endif%}
        {% include 'alerts/_error_messages.html' %}
        {% block content %}
        {% endblock %}
        {% include 'components/_footer.html' %}
    </div>
        <script src="https://cdn.tailwindcss.com"></script>
		<script src="https://unpkg.com/htmx.org@2.0.2" integrity="sha384-Y7hw+L/jvKeWIRRkqWYfPcvVxHzVzn5REgzbawhxAuQGwX1XWe70vji+VSeHOThJ" crossorigin="anonymous"></script>
        <script src="{{url_for('static', filename='script.js')}}"></script>
	</body>
</html>
python
<!-- ErrorMessages -->
{% with messages = get_flashed_messages(with_categories=true) %}
  {% if messages%}
    {% for category, message in messages %}
      {% if category == 'success' %} 
        {% include 'alerts/_success.html' %}
      {% elif category == 'error' %}
        {% include 'alerts/_error.html' %}
      {% endif %}
    {% endfor %}
  {% endif %}
{% endwith %}
<!-- /ErrorMessages -->
python
<div id="alert" class="bg-red-500 text-white p-2">
	{{category}}
	{{message}}
</div>
python
<div id="alert"  class="bg-green-500 text-white p-2">
    {{category}}
    {{message}}
</div>
python

<nav>
    <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/users">Users</a></li>
    </ul>
</nav>
python
<footer>
    <ul>
        <li>Footer</li>
    </ul>
</footer>

The Code

All components live in /templates/. CSS and Javascript files live in /static/. Copy the code in the files below to the appropriate files in your project.

python
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret_key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

class User(db.Model):
    __tablename__='users'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(50), unique=True)
    password = db.Column(db.String(128))
    details_id = db.Column(db.Integer, db.ForeignKey('user_details.id')) #one-to-one relationship
    items = db.relationship('Item', secondary="users_items", back_populates="users")

    def __repr__(self):
        return f'<User {self.username}>'
    
    @classmethod
    def get_by_id(cls, id):
        return cls.query.get_or_404(id)
    
    @classmethod
    def all(cls):
        return cls.query.all()

class Item(db.Model):
    __tablename__='items'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80))
    description = db.Column(db.String(200))
    image_url = db.Column(db.String(800))
    numeral = db.Column(db.Integer)
    numeral_name = db.Column(db.String(200)) #cost, count, price
    created_at = db.Column(db.DateTime, default=datetime.now)
    updated_at = db.Column(db.DateTime, default=datetime.now)
    users = db.relationship('User', secondary="users_items", back_populates="items")

    @classmethod
    def get_by_id(cls, id):
        return cls.query.get_or_404(id)
    
    @classmethod
    def all(cls):
        return cls.query.all()

# JOIN TABLE or many-to-one relationship
class UserItems(db.Model):
    __tablename__ = "users_items"
    id = db.Column(db.Integer, primary_key=True)
    item_id = db.Column('item_id', db.Integer, db.ForeignKey('items.id'))
    user_id = db.Column('user_id', db.Integer, db.ForeignKey('users.id'))

    item = db.relationship('Item', backref= db.backref('items', cascade="all, delete-orphan"))
    user = db.relationship('User', backref= db.backref('users', cascade="all, delete-orphan"))
    db.UniqueConstraint('item_id', 'user_id', name='UC_item_id_user_id')

# one-to-one relationship
class UserDetails(db.Model):
    __tablename__='user_details'
    id = db.Column(db.Integer, primary_key=True)
    profile_photo = db.Column(db.String(200))
    gallery = db.Column(db.String(200))
    bio = db.Column(db.String(200))
    first_name = db.Column(db.String(200))
    last_name = db.Column(db.String(200))
    occupation = db.Column(db.String(200))
    address = db.Column(db.String(200))
    state = db.Column(db.String(200))
    city = db.Column(db.String(200))
    family_members = db.Column(db.String(200))
    friends = db.Column(db.String(200))
    images = db.Column(db.String(800))
    website = db.Column(db.String(800))
    birth_date = db.Column(db.String(800))
    weight = db.Column(db.String(200))
    height = db.Column(db.String(200))
    user = db.relationship("User",  backref="details")  #one-to-one relationship


@app.route('/')
def index():
    items = Item.all()
    return render_template('index.html',items=items)

@app.route('/users')
def all_users():
    users = User.all()
    return render_template('users/index.html', users=users)

@app.route('/users/new', methods=['GET', 'POST'], endpoint='new')
def create_user():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = User(username=username,password=password)
        db.session.add(user)
        db.session.commit()
        flash('User created')
        return redirect(url_for('all_users'))
    return render_template('users/new.html')

@app.route('/users/<int:user_id>')
def retrieve_user(user_id):
    user = User.get_by_id(user_id)
    return render_template('users/show.html', user=user)

@app.route('/users/<int:user_id>/edit', methods=['GET', 'POST'])
def update_user(user_id):
    user = User.get_by_id(user_id)
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        details = request.form['bio']
        user.username = username
        user.password = password
        try:
            user.details.bio = details
        except:
            details = UserDetails(bio=details)
            user.details = details
        db.session.commit()
        flash('User details updated')
        return redirect(url_for('retrieve_user', user_id=user.id))
    return render_template('users/edit.html', user=user)

@app.route('/users/<int:user_id>/delete')
def delete_user(user_id):
    user = User.get_by_id(user_id)
    db.session.delete(user)
    db.session.commit()
    flash('User Deleted')
    return redirect(url_for('all_users'))

# ITEMS
@app.route('/items', defaults={'id': None},methods=['GET','POST'])
@app.route('/items/<id>', methods=['GET','POST', 'PUT', 'DELETE'])
def items(id):
    if not id:
        if request.method == 'GET':
            return render_template('items/new_item.html')
        if request.method == 'POST':
            new_item = Item(**request.form)
            db.session.add(new_item)
            db.session.commit()
            return redirect(url_for('index'))
    item = Item.get_by_id(id)
    if request.method == 'GET':
        return render_template('items/edit_item.html',item=item)
    if request.method == 'POST':
        for key, value in request.form.items():
            setattr(item, key, value)
        db.session.commit()
        return redirect(url_for('index'))
    if request.method == 'DELETE':
        db.session.delete(item)
        db.session.commit()
        flash('Item Deleted')
        items = Item.all()
        return render_template('index.html',items=items)

with app.app_context():
    db.create_all()

if __name__ == '__main__':
    # db.create_all()
    app.run(debug=True,port=4000)
python
<html>
	<head>
	<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
	</head>
	<body>
        {% include '_nav.html' %}
        {% block content %}
        {% endblock %}
        {% include '_footer.html' %}
    </div>
        <script src="https://cdn.tailwindcss.com"></script>
		<script src="https://unpkg.com/htmx.org@2.0.2" integrity="sha384-Y7hw+L/jvKeWIRRkqWYfPcvVxHzVzn5REgzbawhxAuQGwX1XWe70vji+VSeHOThJ" crossorigin="anonymous"></script>
        <script src="{{url_for('static', filename='script.js')}}"></script>
	</body>
</html>
python
{% extends 'base.html' %}

{% block content %}
	
	<h1>WELCOME</h1>
	<a href='/users'>USERS</a>
	<div id="all">
		<a class="ml-2" href="/items">NEW ITEM</a>
		{%for item in items%}
		<div class="flex">
			{{item.name}}<br/>
			<a class="ml-2" href="/items/{{item.id}}">Edit</a>
			<a class="ml-2" hx-delete="/items/{{item.id}}" hx-target="body">DELETE</a>
		</div>
		{%endfor%}
	</div>
{% endblock %}
python
{% extends 'base.html' %}

{% block content %}
	
	<h1>Users</h1>
    <a href="users/new">New User</a><br/>
	{% for user in users%}
		<a href="/users/{{user.id}}">{{ user }}</a>
        <a href="/users/{{user.id}}/delete" onclick="return confirm('Are you sure?')">Delete</a>
	{% endfor %}

{% endblock %}
html
{% extends 'base.html' %}

{% block content %}
<form method="POST" action="/users/new">
	<input name="username">
	<input name="password">
    <input type="submit" value="Submit">
</form>
{% endblock %}
html
{% extends 'base.html' %}

{% block content %}
{{user.username}}
{{user.password}}
<a href="/users/{{user.id}}/edit">Edit User</a>

<h2>
    <p>Bio</p>
    {{user.details.bio}}
</h2>

<hr>
{%for item in user.items%}
{{item.name}}<br/>
{{item.numeral_name}}
{{item.numeral}}
{%endfor%}

{% endblock %}
html
{% extends 'base.html' %}

{% block content %}
<form  method="POST" action="/users/{{user.id}}/edit">
	<input name="username" value="{{user.username}}">
	<input name="password" value="{{user.password}}">

	<div>
		<label class="w-full" for="bio">Bio</label>
		<input required name="bio" id="bio" class="text-black h-10 p-2" value="{{user.details.bio}}">
	
    <input type="submit" value="Submit">
</form>
{% endblock %}
python
{% extends 'base.html' %}

{% block content %}
<form method="POST" action="/items/{{item.id}}">
	<input name="name" value="{{item.name}}">
	<input name="description" value="{{item.description}}">
    <input type="submit" value="Submit">
</form>
{% endblock %}
python
{% extends 'base.html' %}

{% block content %}
<form method="POST" action="/items">
	<input name="name">
	<input name="description">
    <input type="submit" value="Submit">
</form>
{% endblock %}
python

<nav>
    <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/users">Users</a></li>
    </ul>
</nav>
python
<footer>
    <ul>
        <li>Footer</li>
    </ul>
</footer>

Run the App

bash
python3 app.py

Resources