popolo - code simple..

popolo - code simple..

Masonite Project - CRUD Application Tutorial Example

Masonite Project - CRUD Application Tutorial Example

Theodoros Kafantaris's photo
Theodoros Kafantaris
·Apr 25, 2022·

6 min read

In this series, we will create a basic CRUD Application Tutorial Example from scratch.

We will create a products table with name and detail column using Masonite migration, then we will create routes, controller, view, and model files for the product. We will use bootstrap 5 for design now as a front-end toolkit. So let's follow the below step to create a crud operation with Masonite.

Masonite Installation

mkdir simple-crud
cd simple-crud

Activating Our Virtual Environment

python -m venv venv
source venv/bin/activate

Install Masonite

pip install masonite

Start project

project start .

Start Development Server

python craft serve

This is it! We have the new Masonite Project running now!

Database Configuration

In this step, we will configure mysql DB in our .env. We need to add database name, mysql username and password. So we need to create in our mysql server a DB with name simple-crud.

DB_CONNECTION=mysql
#SQLITE_DB_DATABASE=masonite.sqlite3
DB_HOST=127.0.0.1
DB_USERNAME=root
DB_PASSWORD=
DB_DATABASE=simple-crud
DB_PORT=3306
DB_LOG=True

Create Migration

Here, we will create products table using masonite craft migration. So let's use following command to create migration file.

python craft migration create_products_table --create products

After this command you will find one file in the following path database/migrations and you have to put code below in your migration file for creating the products table.

"""CreateProductsTable Migration."""

from masoniteorm.migrations import Migration


class CreateProductsTable(Migration):
    def up(self):
        """
        Run the migrations.
        """
        with self.schema.create("products") as table:
            table.increments("id")
            table.string("name")
            table.text("details")
            table.timestamps()

    def down(self):
        """
        Revert the migrations.
        """
        self.schema.drop("products")

Now we will run this migration by the following command:

python craft migrate

Create Controller and Model

In this step, now we should create new controller as ProductController. So run bellow command and create new controller.

python craft controller ProductController

After the bellow command, you will find a new file in this path app/controllers/ProductController.py.

In this controller we will create seven methods as bellow methods:

  1. index
  2. create
  3. store
  4. show
  5. edit
  6. update
  7. delete

app/controllers/ProductController.py

from masonite.request import Request
from masonite.controllers import Controller
from masonite.views import View
from masonite.response import Response
from app.models.Product import Product
from masonite.validation import Validator


class ProductController(Controller):
    def index(self, view: View):
        products = Product.all()
        return view.render('products/index', {'products': products})

    def create(self, view: View):
        return view.render('products/create')

    def store(self, request: Request, response: Response, validate: Validator):
        errors = request.validate(
            validate.required(['name', 'details']),
        )

        if errors:
            return response.redirect('/products/create').with_errors(errors)
        Product.create(
            name=request.input('name'),
            details=request.input('details')
        )
        return response.redirect('/products')

    def show(self, view: View, request: Request):
        product = Product.where('id', request.param('product_id')).first()
        return view.render('products/show', {'product': product})

    def edit(self, view: View, request: Request):
        product = Product.where('id', request.param('product_id')).first()
        return view.render('products/edit', {'product': product})

    def update(self, request: Request, response: Response, validate: Validator):
        errors = request.validate(
            validate.required(['name', 'details']),
        )

        if errors:
            return response.redirect('/products/edit/{}'.format(request.input('id'))).with_errors(errors)
        product = Product.where('id', request.input('id')).first()
        product.name = request.input('name')
        product.details = request.input('details')
        product.save()
        return response.redirect('/products)

    def delete(self, request: Request, response: Response):
        product = Product.where('id', request.param('product_id')).first()
        product.delete()
        return response.redirect('/products')

In order to have shared individual input validation errors we need to import and register the following Middleware (ShareErrorsInSessionMiddleware) in our Kernel.py.

Kernel.py

from masonite.foundation import response_handler
from masonite.storage import StorageCapsule
from masonite.auth import Sign
from masonite.environment import LoadEnvironment
from masonite.utils.structures import load
from masonite.utils.location import base_path
from masonite.middleware import (
    SessionMiddleware,
    EncryptCookies,
    ShareErrorsInSessionMiddleware,
    LoadUserMiddleware,
    MaintenanceModeMiddleware,
)
from masonite.routes import Route
from masonite.configuration.Configuration import Configuration
from masonite.configuration import config

from app.middlewares import VerifyCsrfToken, AuthenticationMiddleware


class Kernel:

    http_middleware = [MaintenanceModeMiddleware, EncryptCookies]

    route_middleware = {
        "web": [SessionMiddleware, LoadUserMiddleware, VerifyCsrfToken, ShareErrorsInSessionMiddleware],
        "auth": [AuthenticationMiddleware],
    }

    def __init__(self, app):
        self.application = app

    def register(self):
        # Register routes
        self.load_environment()
        self.register_configurations()
        self.register_middleware()
        self.register_routes()
        self.register_database()
        self.register_templates()
        self.register_storage()

    def load_environment(self):
        LoadEnvironment()

    def register_configurations(self):
        # load configuration
        self.application.bind("config.location", "config")
        configuration = Configuration(self.application)
        configuration.load()
        self.application.bind("config", configuration)
        key = config("application.key")
        self.application.bind("key", key)
        self.application.bind("sign", Sign(key))
        # set locations
        self.application.bind("resources.location", "resources/")
        self.application.bind("controllers.location", "app/controllers")
        self.application.bind("jobs.location", "app/jobs")
        self.application.bind("providers.location", "app/providers")
        self.application.bind("mailables.location", "app/mailables")
        self.application.bind("listeners.location", "app/listeners")
        self.application.bind("validation.location", "app/validation")
        self.application.bind("notifications.location", "app/notifications")
        self.application.bind("events.location", "app/events")
        self.application.bind("tasks.location", "app/tasks")
        self.application.bind("models.location", "app/models")
        self.application.bind("observers.location", "app/models/observers")
        self.application.bind("policies.location", "app/policies")
        self.application.bind("commands.location", "app/commands")
        self.application.bind("middlewares.location", "app/middlewares")

        self.application.bind("server.runner", "masonite.commands.ServeCommand.main")

    def register_middleware(self):
        self.application.make("middleware").add(self.route_middleware).add(self.http_middleware)

    def register_routes(self):
        Route.set_controller_locations(self.application.make("controllers.location"))
        self.application.bind("routes.location", "routes/web")
        self.application.make("router").add(
            Route.group(
                load(self.application.make("routes.location"), "ROUTES"), middleware=["web"]
            )
        )

    def register_database(self):
        from masoniteorm.query import QueryBuilder

        self.application.bind(
            "builder",
            QueryBuilder(connection_details=config("database.databases")),
        )

        self.application.bind("migrations.location", "databases/migrations")
        self.application.bind("seeds.location", "databases/seeds")

        self.application.bind("resolver", config("database.db"))

    def register_templates(self):
        self.application.bind("views.location", "templates/")

    def register_storage(self):
        storage = StorageCapsule()
        storage.add_storage_assets(config("filesystem.staticfiles"))
        self.application.bind("storage_capsule", storage)

        self.application.set_response_handler(response_handler)
        self.application.use_storage_path(base_path("storage"))

It is now the time to create our Product model.

python craft model Product

app/models/Product.py

""" Product Model """

from masoniteorm.models import Model


class Product(Model):
    __fillable__ = ['name', 'details']

Add Routes

We need to add routes for product crud application. So we open our "routes/web.py" file and add following route.

from masonite.routes import Route

ROUTES = [Route.get("/", "ProductController@index"),
          Route.get("/products", "ProductController@index"),
          Route.get("/products/create", "ProductController@create"),
          Route.post("/products", "ProductController@store"),
          Route.get("/products/show/@product_id", "ProductController@show"),
          Route.get("/products/edit/@product_id", "ProductController@edit"),
          Route.post("/products/update", "ProductController@update"),
          Route.get('/products/delete/@product_id', 'ProductController@delete'),
          ]

Add template files

In this step we have to create template files. So mainly we have to create base file and then create new folder "products" then create html files of crud app.

templates/base.html

<!DOCTYPE html>
<html>
  <head>
    <title>Masonite 4 CRUD Application - blog.popolo.dev</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
      rel="stylesheet"
    />
  </head>
  <body>
    <div class="container">{% block content %} {% endblock %}</div>
  </body>
</html>

templates/products/index.html

{% extends "base.html" %} {% block content %}
<div class="row">
  <div class="col-lg-12 d-flex justify-content-between mt-4">
    <div class="pull-left">
      <h2>Masonite 4 CRUD Example from scratch - blog.popolo.dev</h2>
    </div>
    <div class="pull-right mb-4">
      <a class="btn btn-success" href="products/create"> Create New Product</a>
    </div>
  </div>
</div>
<table class="table table-bordered">
  <tr>
    <th>No</th>
    <th>Name</th>
    <th>Details</th>
    <th width="280px">Action</th>
  </tr>
  @for product in products
  <tr>
    <td>{{product.id}}</td>
    <td>{{product.name}}</td>
    <td>{{product.details}}</td>
    <td>
      <form action="" method="POST">
        <a class="btn btn-info" href="/products/show/{{product.id}}">Show</a>

        <a class="btn btn-primary" href="/products/edit/{{product.id}}">Edit</a>
        <a class="btn btn-danger" href="/products/delete/{{product.id}}">Delete</a>
      </form>
    </td>
  </tr>
  @endfor
</table>
{% endblock %}

templates/products/create.html

{% extends "base.html" %}

{% block content %}
<div class="row">
    <div class="col-lg-12 mt-4 d-flex justify-content-between">
        <div class="pull-left">
            <h2>Add New Product</h2>
        </div>
        <div class="pull-right">
            <a class="btn btn-primary" href="/products"> Back</a>
        </div>
    </div>
</div>

<form action="/products" method="POST">
    {{ csrf_field }}

     <div class="row">
        <div class="col-xs-12 col-sm-12 col-md-12">
            <div class="form-group">
                <strong>Name:</strong>
                <input type="text" name="name" class="form-control" placeholder="Name">
            </div>
            @if errors.has('name')
            <div class="text-danger"> 
                <code>{{ errors.get('name')[0] }}</code>
            </div>
            @endif
        </div>
        <div class="mb-2 col-xs-12 col-sm-12 col-md-12">
            <div class="form-group">
                <strong>Detail:</strong>
                <textarea class="form-control" style="height:150px" name="details" placeholder="Detail"></textarea>
            </div>
            @if errors.has('details')
            <div class="text-danger"> 
                <code>{{ errors.get('details')[0] }}</code>
            </div>
            @endif
        </div>
        <div class="col-xs-12 col-sm-12 col-md-12 text-center">
                <button type="submit" class="btn btn-primary">Submit</button>
        </div>
    </div>

</form>
{% endblock %}

templates/products/edit.html

{% extends "base.html" %}

{% block content %}
<div class="row">
    <div class="col-lg-12 mt-4 d-flex justify-content-between">
        <div class="pull-left">
            <h2>Add New Product</h2>
        </div>
        <div class="pull-right">
            <a class="btn btn-primary" href="/products"> Back</a>
        </div>
    </div>
</div>

<form action="/products/update" method="POST">
    {{ csrf_field }}

     <div class="row">
        <div class="col-xs-12 col-sm-12 col-md-12">
            <input type="hidden" name="id" value="{{product.id}}" >
            <div class="form-group">
                <strong>Name:</strong>
                <input type="text" name="name" value="{{product.name}}" class="form-control" placeholder="Name">
            </div>
            @if errors.has('name')
            <div class="text-danger"> 
                <code>{{ errors.get('name')[0] }}</code>
            </div>
            @endif
        </div>
        <div class="mb-2 col-xs-12 col-sm-12 col-md-12">
            <div class="form-group">
                <strong>Detail:</strong>
                <textarea class="form-control" style="height:150px"  name="details" placeholder="Detail">{{product.details}}</textarea>
            </div>
            @if errors.has('details')
            <div class="text-danger"> 
                <code>{{ errors.get('details')[0] }}</code>
            </div>
            @endif
        </div>
        <div class="col-xs-12 col-sm-12 col-md-12 text-center">
                <button type="submit" class="btn btn-primary">Submit</button>
        </div>
    </div>

</form>
{% endblock %}

templates/products/show.html

{% extends "base.html" %}

{% block content %}
<div class="row">
    <div class="mt-4 col-lg-12 mb-2 d-flex justify-content-between">
        <div class="pull-left">
            <h2> Show Product</h2>
        </div>
        <div class="pull-right">
            <a class="btn btn-primary" href="/products"> Back</a>
        </div>
    </div>
</div>

<div class="row">
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Name:</strong>
            {{ product.name }}
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Details:</strong>
            {{ product.details }}
        </div>
    </div>
</div>
{% endblock %}

Visit application

Now, open your web browser, type the given URL and view the app output:

http://127.0.0.1:8000/products

index page

image.png

create page

image.png

Edit Page

image.png

show page

image.png

You can download the code from the repo below:

Github Repo

 
Share this