Bulma – modal component + React +Flask-RESTful

In the post I will describe how to activate the modal component from the Bulma framework, so that after pressing the delete row-contract button in the contract table, it is possible to confirm or cancel the deletion of the contract.

<tbody>
  {cursor && Object.keys(cursor).map(
      (keyName, keyIndex) =>
      <tr  className="has-text-centered is-size-7"
           key={keyIndex}>
          <td>{keyIndex+1}</td>
          <td>{(cursor[keyName].status)}</td>
          <td>{(cursor[keyName].contract_number)}</td>
          <td>{(cursor[keyName].contractor)}</td>
          <td>{(cursor[keyName].customer)}</td>
          <td>{(cursor[keyName].date_of_order)}</td>
          <td>{(cursor[keyName].date_of_delivery)}</td>
          <td>{(cursor[keyName].pallets_position)}</td>
          <td>{(cursor[keyName].pallets_planned)}</td>
          <td>{(cursor[keyName].pallets_actual)}</td>
          <td>{(cursor[keyName].warehouse)}</td>
          <td>
            <FaEdit />
            <FaTrashAlt onClick={() => 
               showModal(cursor[keyName].id)}
               title={`Delete contract ${keyIndex+1}`} />
          </td>
      </tr>)}   
</tbody>
Contract deletion

Instead of directly calling the deleteContract() function, the showModal() function is called, which gets as the id parameter of the selected contract.

The showModal() function sets the variables id and modal, respectively.

const [modal, setModal] = useState(false)
const [id, setId] = useState()

const showModal = (id) => {
        setId(id)
        setModal(!modal)
    }

The id variable stores the id number of the selected contract, while the modal variable stores the boolean value that specifies the state of the modal window (displayed or invisible-default). Calling the setModal() function changes the default value – false of the modal variable to true.

The code of the modal window displaying the confirmation of contract deletion looks like this:

<div class={`modal ${modal && 'is-active'}`}>
  <div class="modal-background"></div>
  <div class="modal-card column is-one-quarter">
     <header class="modal-card-head">
        <p class="modal-card-title has-text-danger">
           Delete Contract?
        </p>
      </header>
      <section class="modal-card-foot">
         <button class="button is-danger" 
             onClick={() => deleteContract(id)}>Delete
         </button>
         <button class="button"
             onClick={() => setModal(!modal)}>Cancel
         </button>
       </section>
  </div>
</div>

Selecting the Cancel button makes the modal window invisible. Selecting the Delete button activates the function of deleting the contract, i.e.

const deleteContract = (id) => {
  setCursor(Object.values(cursor)
    .filter((row) => row.id !== id))
  fetch('/api/contract/delete', {
    method: 'POST',
    headers: {
      'Content-type': 'application/json',
     },
       body: JSON.stringify({'id': id, 'token': token})
  })
  .then(res => res.json())
  .then(data => console.log(data))
  .catch((err) => console.log(err));
  setModal(!modal)
}

The setCursor function gets the values of the cursor object and filters them so that the selected contract is discarded. Then the api is called in Flask, in addition to the contract id, the user’s token is also sent. The result of a successful contract canceling is displayed in the console.

What the API looks like in Flask I described in the previous post.

In the last row of the deleteContract() function, I set the modal window inactive.

Delete a record (Flask-RESTful, SQLAlchemy, React, and Bulma CSS framework)

In the contract table, after pressing the appropriate button to delete a row in the table, the deleteContract function is activated, which takes the contract id value as a parameter, i.e.:

<td>
    <FaTrashAlt onClick={() => deleteContract(cursor[keyName].id)} />
</td>

The deleteContract function filters the data of the cursor variable in such a way that the row that we want to delete is rejected. Then a query is called to the backend, where the POST method sends the value of the contract id and the token. When the “deletion” of the record is successful, the API sends information in the message variable (in the given example, the value of this variable is displayed in the console).

const deleteContract = (id) => {
    setCursor(Object.values(cursor).filter((row) => row.id !== id))
    fetch('/api/contract/delete', {
            method: 'POST',
            headers: {
                'Content-type': 'application/json',
            },
            body: JSON.stringify({'id': id, 'token': token})
        })
        .then(res => res.json()).then(data => console.log(data)).catch((err) => console.log(err));
    }

Backend we Flask-RESTful z wykorzystaniem SQLAlchemy:

class DeleteContract(Resource):
    def post(self):
        req = request.json
        contract_id = req['id']
        token = req['token'].strip('"')
        user = User.query.filter_by(token=token).first()
        if user.role == 'Customer':
            contract = Contract.query.filter_by(customer=user.username).filter_by(id=contract_id).first()
            if contract:
                contract.status ='cancelled'
                db.session.commit()
                return {"message": "Row deleted"}

I assign the id and token values sent using the POST method to the corresponding variables contract_id and token. Then I find the user in the database based on the token sent. If the user belongs to the Customer group, the contract is searched based on the user name and the contract id number. If this contract exists, instead of removing it from the database, I change its status to cancelled.

I also updated the post() method of the AllContracts(Resource) class so that the created cursor object does not contain records with an cancelled status, i.e.:

class AllContracts(Resource):
    def get(self, token):
        token = token.strip('"')
        user = User.query.filter_by(token=token).first()
        if user.role == 'Customer':
            contracts = Contract.query.filter(
                Contract.status!='cancelled').
                filter_by(customer=user.username).
                order_by(Contract.id.desc()).all()
        elif user.role == 'Contractor':
            contracts = Contract.query.
                    filter(Contract.status!='cancelled').
                    filter_by(contractor=user.username).
                    order_by(Contract.id.desc()).all()
        cursor = {}
        for i, contract in enumerate(contracts):
            cursor[i] = contract.serialize()
        return cursor

The useEffect() hook for loading contracts after selecting the Contracts option from the menu looks like this:

    useEffect( 
        () => {
            const token = sessionStorage.getItem('token')
            fetch('/api/contract/' + token)
            .then(res => res.json()).then(data => setCursor(data)).catch((err) => console.log(err))
        }, [token, setCursor],
    );

React, Flask-restful and SQLAlchemy – getting data from the database

Frontend:

I download the data from the backend when selecting the option from the Navbar component of Bulma framework. I use the useEffect hook to get the data to be read during component rendering. Then, a query to the backend is created, and a token is passed as a parameter, set when the user logs in. The response is sent from the backend in the form of a Promise object, from which if the query is completed successfully, the cursor variable is set (change of state with setCursor). Cursor and setCursor function are passed to the component from the parent App.js component as parameters (props), i.e.

import LoginForm from '../Forms/LoginForm/LoginForm'
import { useEffect } from "react";

function Contracts({token, putToken, cursor, setCursor}) {

    useEffect( 
        () => {
            const token = sessionStorage.getItem('token')
            fetch('/api/contract/' + token)
            .then(res => res.json()).then(data => setCursor(data)).catch((err) => console.log(err))
        }, [token, setCursor],
    );
    
    if (!token) {
        return <LoginForm putToken={putToken} />
    };

In order for the component to refresh the data after successfully logging in, in the additional parameter of the useEffect method in the array, I put the token variable, which is set in the LoginForm.js component.

In order for the component to refresh the data after changing the cursor content, e.g. by adding a new contract, I have placed the setCursor function as an additional parameter of the useEffect method.

Individual data lines are placed in the table, i.e.

<tbody>
    {cursor && Object.keys(cursor).map((keyName, keyIndex) => 
    <tr key={keyIndex}>
            <td>{keyIndex+1}</td>
            <td>{(cursor[keyName].status)}</td>
            <td>{(cursor[keyName].contract_number)}</td>
            <td>{(cursor[keyName].contractor)}</td>
            <td>{(cursor[keyName].customer)}</td>
            <td>{(cursor[keyName].date_of_order)}</td>
            <td>{(cursor[keyName].date_of_delivery)}</td>
            <td>{(cursor[keyName].pallets_position)}</td>
            <td>{(cursor[keyName].pallets_planned)}</td>
            <td>{(cursor[keyName].pallets_actual)}</td>
            <td>{(cursor[keyName].warehouse)}</td>
     </tr>)}   
</tbody>

Backend with Flask-restful:

In the init.py file, I add another resource class:

from api.resources.contracts import AllContracts

api.add_resource(AllContracts, '/api/contract/<token>', endpoint='all_contracts')

In contracts.py, I define the resource class:

from datetime import datetime
from flask import request
from flask_restful import Resource
from .. import db
from ..common.models import User, Contract

class AllContracts(Resource):
    def get(self, token):
        token = token.strip('"')
        user = User.query.filter_by(token=token).first()
        contracts = Contract.query.filter_by(customer=user.username).order_by(Contract.id.desc()).all()
        cursor = {}
        for i, contract in enumerate(contracts):
            cursor[i] = contract.serialize()
        return cursor

In the models.py file I define, among others, how the Contract class inheriting from the Model class from SQLAlchemy is to be serialized:

from .. import db
from datetime import datetime

class Contract(db.Model):
    '''Model of contract between a contractor and a customer'''

    id = db.Column(db.Integer, primary_key=True)
    status = db.Column(db.String(10), nullable=False, default='open')
    contract_number = db.Column(db.String(20), nullable=False)
    contractor = db.Column(db.String(20), db.ForeignKey('user.username'), nullable=False)
    customer = db.Column(db.String(20), db.ForeignKey('user.username'), nullable=False)
    date_of_order = db.Column(db.Date, nullable=False, default=datetime.utcnow)
    date_of_delivery = db.Column(db.Date, nullable=False)
    pallets_position = db.Column(db.Integer)
    pallets_planned = db.Column(db.Integer, nullable=False)
    pallets_actual = db.Column(db.Integer)    
    warehouse = db.Column(db.String(10), nullable=False)

    def serialize(self):
        return {'id': self.id,
                'status': self.status,
                'contract_number': self.contract_number,
                'contractor': self.contractor,
                'customer': self.customer,
                'date_of_order': datetime.strftime(self.date_of_order, '%Y-%m-%d'),
                'date_of_delivery': datetime.strftime(self.date_of_delivery, '%Y-%m-%d'),
                'pallets_position': self.pallets_position,
                'pallets_planned': self.pallets_planned,
                'pallets_actual': self.pallets_actual,
                'warehouse': self.warehouse 
                }

React and Bulma navbar

Problems solved:

  1. How to collapse a burger menu when selecting options from the menu (by default, selecting options from the burger menu leaves the menu expanded).
  2. How to collapse a pull-down menu when selecting an option. (by default, the drop-down menu does not close when an option is selected)

re. 1

The menu finally collapses when an option is selected.
<div id="navbarBasicExample" className={`navbar-menu ${burgerActive? "is-active": ""}`}>
                <div class="navbar-start">
                <Link to="/" class="navbar-item" onClick={handleOnClick}>
                    Home
                </Link>

wherein:

    const [burgerActive, setBurgerActive] = useState(false)

    const handleOnClick = () => {
        setBurgerActive(false)
    }

re. 2

The pull-down menu collapses when an option is selected.
<div class="navbar-item has-dropdown is-hoverable" key={location.pathname}>

wherein:

import {
    Link, useLocation
  } from 'react-router-dom' 

const Navbar = () => {
    let location = useLocation();

Order processing for Logistics Centers

The application allows the customer to place new orders and accept orders by the contractor as well as generate reservations, i.e. manage delivery time slots to central warehouses and logistics centers.

The source code for the application is available here.

You can try the app here.

In this post, I will introduce the new_booking(id) function which handles the booking for a specific order (contract). It takes the contract identification number as a parameter, i.e.

@bp.route('/booking/<int:id>', methods=['GET', 'POST'])
@login_required
def new_booking(id):

Then, in the new_booking () function, I create a booking form and get the booking object for a given contract, i.e.

    form = BookingForm()
    result = Booking.query.filter_by(contract_id=id).first()

Next, I set the contract object for the booking, i.e.

    current_contract = Contract.query.get(id)     # Returns contract for current booking

In the following lines I get a list of orders that were generated for a specific warehouse on the day of delivery, i.e.

# Filtering by one column gives a list of tuple(s) so I converted it to a list of values
    contracts = [ids[0] for ids in Contract.query.with_entities(Contract.id).filter_by(
        date_of_delivery=current_contract.date_of_delivery).filter_by(
        warehouse=current_contract.warehouse).all()]

Then I validate if the form has been submitted. Depending on whether the reservation for a given contract has already been created, I update the data in the database based on the data from the form. If the booking object does not exist, I create a new booking object and save it to the database, i.e.

    if form.validate_on_submit():
        if result:
            result.booking_time = form.booking_time.data
            result.driver_full_name = form.driver_full_name.data
            result.driver_phone_number = form.driver_phone_number.data
            result.truck_reg_number = form.truck_reg_number.data
            db.session.commit()
        else:
            booking = Booking(booking_time=form.booking_time.data, 
                            contract_id = id,
                            driver_full_name=form.driver_full_name.data,
                            driver_phone_number=form.driver_phone_number.data,
                            truck_reg_number=form.truck_reg_number.data)
            db.session.add(booking)
            db.session.commit()

Next, I change the order status to accepted and redirect to the function displaying all orders for a given contractor, i.e.

        contract = Contract.query.get(id)
        contract.status = 'accepted'
        db.session.commit()
        page = session.get('page')
        per_page = session.get('per_page')
        return redirect(url_for('contracts.contracts', page=page, per_page=per_page))

If the page is loaded using the GET method, then depending on whether the booking is already present in the system or is being created, the data is completed in the form, i.e. if the booking is available and we open it, e.g. to edit the data, then the form will be completed with the previous one entered data. However, if the reservation is just being created, the form will contain a modification, the dates of which cannot be selected, because they have already been selected by other suppliers, i.e.

    if result is not None:
        reserved_booking_time = [times[0] for times in 
            Booking.query.with_entities(Booking.booking_time).filter(
            Booking.contract_id.in_(contracts)).all() if times[0]!=result.booking_time]   
        form.booking_time.data = result.booking_time
        form.driver_full_name.data = result.driver_full_name
        form.driver_phone_number.data = result.driver_phone_number
        form.truck_reg_number.data = result.truck_reg_number   
    else:
        reserved_booking_time = [times[0] for times in 
            Booking.query.with_entities(Booking.booking_time).filter(
            Booking.contract_id.in_(contracts)).all()] 
    return render_template('booking.html', form=form, reserved_booking_time=reserved_booking_time)

The complete code for the new_booking() function is available here.

A contract accepted in this way can only be edited by the customer. However, if the contract has already been accepted and the booking has been made, the change of the contract changes the status of the order from accepted back to open and requires the contractor to accept the changes again (but the entered data are present and the reservation is does not require data to be repeated).

The contractor may also download a pdf of the booking if the contract is accepted (for open contract the option to download pdf is inactive).

Reservation made – now you can generate pdf

Reportlab – generating and downloading pdf in Flask without creating the file on the server

Solved problem: how to generate pdf with order data and reservation barcode without saving the file on disk, which can then be downloaded by the website user in Flask.

We can use the Canvas object to embed document elements such as texts, tables, etc. As an argument we can give the name of the created pdf, but in this case I will use the buffer instead of creating the file on the server. I’m explicitly entering the page size as A4.

from io import BytesIO
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.pagesizes import A4

buffer = BytesIO()
canvas = Canvas(buffer, pagesize=A4)

To use non-standard characters, register the appropriate fonts that support the required characters. In the sample pdf file, I used the Vera font.

from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

pdfmetrics.registerFont(TTFont('Vera', 'Vera.ttf'))

The registered font can now be used:

canvas.setFont("Vera", size=10)

I place the images (in this case, the reservation barcode) generated on the basis of the data from the application in the document being created using the drawImage () method, which takes the ImageReader object as the first argument, i.e.

im = ImageReader(image)
canvas.drawImage(im, x=0, y=-5*cm, width=150, height=100)

A list of text strings can be added to a document using a text object, i.e.

txt_obj = canvas.beginText(14, -6.5 * cm)
txt_lst = ["line of text 1", "line of text 2", "line of text 3"]
for line in txt_lst:
        txt_obj.textOut(line)
        txt_obj.moveCursor(0, 16)
canvas.drawText(txt_obj)

A table can be added to a document as a Table class object. I separately define a list containing the data of individual table rows (table_data variable). I also define styles that are valid for all or part of the table.

t = Table(table_data, colWidths=[60, 230, 70, 60, 50], rowHeights=30)
style = [('BACKGROUND',(0,0),(-1,-2),colors.lightblue),
            ('ALIGN',(0,-1),(-1,-1),'CENTER'),
            ('BOX', (0,0), (-1,-1), 0.25, colors.black),
            ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black),
            ('FONTSIZE', (0,0), (-1,-1), 10),
            ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE')]
t.setStyle(tblstyle=style)
t.wrapOn(canvas, 10, 10)
    t.drawOn(canvas=canvas, x=20, y=-22*cm)

Then I save the created Canvas object and return the resulting buffer to the controller. In order to send a pdf file based on data from the buffer, I use the send_file () Flask function:

from flask.helpers import send_file

@bp.route('/get-pdf/<int:id>', methods=['GET'])
@login_required
def get_pdf(id):
    contract = Contract.query.get(id)
    contractor = User.query.get(contract.contractor_id)
    booking = Booking.query.filter_by(contract_id=contract.id).first()
    pdf = create_pdf(booking_no=booking.id,
                        contractor=contractor.username,
                        contractor_no=contractor.id,
                        truck_plate=booking.truck_reg_number,
                        warehouse=contract.warehouse,
                        date=contract.date_of_delivery,
                        time=booking.booking_time,
                        pallets_pos=contract.pallets_position,
                        pallets=contract.pallets_actual)
    pdf.seek(0)
    return send_file(pdf, as_attachment=True, mimetype='application/pdf',
        attachment_filename='booking.pdf', cache_timeout=0)
sample file generated on the server side, downloaded by the contractor.

Time slot management system using Flask #2

The source code of the application can be downloaded here.

You can try the app here.

The plan for the application described in the previous article looks like this:

├───flask_contracts
│ ├───api
│ ├───auth
│ │ ├───static
│ │ │ └───logos
│ │ ├───templates
│ │ │ └───auth
│ │ ├───utils
│ │ │ ├───templates
│ │ │ │ └───utils
│ ├───contracts
│ │ ├───static
│ │ ├───templates
│ │ │ └───contracts
│ │ │ └───errors
│ │ ├───utils
│ ├───static
│ ├───templates
├───tests
│ ├───functional
│ ├───unit
└───venv

Each part of the application is a separate entity. A similar solution is used in Django. I wrote the manage.py file to manage the application. Each component has its own template subdirectories – a convention similar to Django’s.

Time slot management system using Flask

Thanks to the application: downtime is minimized and warehouse operation is optimized, which will reduce costs.

The application allows you to manage delivery times to central warehouses and logistics centers.

The source code of the application can be downloaded here.

You can try the app here.

The project will include an application in Flask (using the css – Bulma framework).

Customers and contractors of supplies will be able to log in to the application (verification of the account by e-mail to the address provided).

Only customers of deliveries will be able to issue orders, while contractors will be able to reserve available delivery appointments and generate a pdf document with a barcode.

part 2

Transfer of values ​​to and from modal (Bulma framework) in Flask

Solved problem: how to pass a value to a modal window and how to pass a value from a modal window to an application in Flask.

In the presented case, each row of the html table contains a button that enables canceling the contract. The data for each row is passed from the controller using the variable contracts:

result = Contract.query.filter_by(
            customer_id=current_user.id).filter(
                Contract.status!='cancelled').order_by(
                    Contract.id.desc()).paginate(page=page, per_page=per_page)
return render_template('contracts.html', title='Contracts', header=columns, contracts=result)

The data for the rows is available in the template:

{% for contract in contracts.items %}
    <tr>
        <td>{{  loop.index  }}</td>
        {% for item in header %}
            <td>
                 {{ contract[item] }}
            </td>
        {% endfor %}
    </tr>
{% endfor %}

In addition to the data sent from the controller, for each row of the table, a button should be created that activates the appropriate modal – differing in the value of the data-target parameter, i.e.

<button class="button mr-2 modal-button" data-target="modal{{contract.id}}"  title="Cancel Contract">

The value of the data-target parameter will vary for each row depending on the value of contract.id, which is unique – the Contract table key.

Depending on the value of the data-target parameter, the modal with the same value of the id parameter is launched, i.e.

<div id="modal{{contract.id}}" class="modal">

The modal contains a button to approve changes, in this case to cancel the contract. To pass the contract to be canceled, the contract id should be passed as a parameter to the url_for () function, i.e.

<form action="{{ url_for('contracts.cancel_contract', id=contract.id) }}">
            <button class="button is-danger is-rounded" formmethod="POST">Yes</button>
</form>