Barangay Document Request System using Python Flask and SQLite with Source Code

Language

The Barangay Document Request System is a web application built with Python Flask and SQLite that lets residents request barangay documents online instead of lining up at the barangay hall. Residents create an account, request a Barangay Clearance, Certificate of Residency, Certificate of Indigency, Business Clearance or Certificate of Good Moral Character, and track each request from Pending to Released. Barangay staff process the requests, leave remarks for the resident, print the certificate, and manage document types and fees.

It is a good fit for an IT capstone project, a Flask final project, or a starting point for a real barangay e-services portal. The code is plain Flask with the built-in sqlite3 module (no ORM), so every query is easy to follow. The first run creates the database with sample residents and requests, so you can try every feature in a minute. The full source code is free to download below.

Barangay Document Request System Basic Information

  • Language used: Python 3.10+ (tested with Python 3.13)
  • Framework: Flask 3 (tested with Flask 3.1.3)
  • Database used: SQLite (built into Python, nothing to install)
  • Front end: Jinja2 templates with plain HTML and CSS, print-friendly certificate (no CDN required)
  • Coding Tool used: VS Code, PyCharm or any text editor
  • Type: Web Application

About the Project

In many barangays, getting a simple clearance still means going to the hall, writing your details in a logbook, waiting, and sometimes coming back the next day because the secretary or the captain is out. The office, on the other hand, has no easy way to see how many requests are waiting or how much was collected.

This system moves the request step online. The resident submits the request from a phone or computer and gets a reference number such as BRGY-2026-00007. Staff see all pending requests on the dashboard, update the status to Approved, Ready for Pickup, Released or Rejected, and type a remark like "Bring a valid ID". The resident sees the new status and remark on the My Requests page and only goes to the hall when the document is ready. Payment stays at the hall, which matches how most barangays work today.

Features

For residents

  • Online registration with full name, address (purok/street), contact number and birthdate.
  • Request a document, enter the purpose and number of copies (1 to 5). The fee is computed automatically (fee x copies) and shown as Free for zero-fee documents such as the Certificate of Indigency.
  • Unique reference number for every request.
  • My Requests page with color-coded status badges and staff remarks.
  • Cancel a request while it is still Pending.

For barangay staff

  • Dashboard with counts per status (clickable), number of residents, total fees collected from released documents, pending requests and requests per document type.
  • Request list with search by reference number or resident name and a status filter.
  • Request details page to update the status and remarks.
  • Printable certificate with the barangay name, municipality, resident name, address, purpose, date issued, Punong Barangay signature line and reference number. Printing is only allowed for approved requests.
  • Residents list with the number of requests per resident.
  • Document types and fees: add new documents, change fees and processing days, or deactivate a document so residents can no longer request it.

Security basics

  • Passwords hashed with Werkzeug's generate_password_hash() and checked with check_password_hash().
  • CSRF token on every form, role checks on every staff page, and residents can only see and cancel their own requests.
  • Parameterized SQL queries everywhere and auto-escaped Jinja2 templates.

Screenshots

The screenshots below were captured from the running application with the included sample data. The staff dashboard is the main screenshot; the others show the login and registration pages, a resident's request list and request form, the staff request list and request details, the printable certificate, the residents list, and the document types page.

Code Highlights

Every staff page is protected by a small decorator. Guests are sent to the login page and residents get a 403 error:

  1. def staff_required(view):
  2.     @wraps(view)
  3.     def wrapped(**kw):
  4.         if g.user is None:
  5.             return redirect(url_for('login', next=request.path))
  6.         if g.user['role'] != 'staff':
  7.             abort(403)
  8.         return view(**kw)
  9.     return wrapped

Every POST must carry the CSRF token stored in the session. Notice the not session.get('_csrf') part: without it, a visitor with no session and a form with no token would compare None == None and slip through. The included test suite caught exactly that bug during development.

  1. def csrf_token():
  2.     if '_csrf' not in session:
  3.         session['_csrf'] = secrets.token_hex(16)
  4.     return session['_csrf']
  5.  
  6.  
  7. app.jinja_env.globals['csrf_token'] = csrf_token
  8.  
  9.  
  10. @app.before_request
  11. def check_csrf():
  12.     if request.method == 'POST' and (not session.get('_csrf')
  13.                                      or request.form.get('_csrf') != session['_csrf']):
  14.         abort(400, 'Invalid form token. Please reload the page and try again.')

Reference numbers come from the SQLite AUTOINCREMENT id, which is never reused. That means a cancelled request never gives its number to someone else:

  1. def assign_reference(db, request_id):
  2.     """BRGY-<year>-<id>. AUTOINCREMENT ids are never reused, even after a cancel."""
  3.     ref = f'BRGY-{date.today().year}-{request_id:05d}'
  4.     db.execute('UPDATE requests SET reference_no = ? WHERE id = ?', (ref, request_id))
  5.     return ref

Submitting a request checks that the document exists and is active, validates the purpose and copies, computes the amount, then assigns the reference number:

  1. @app.route('/request/new', methods=['GET', 'POST'])
  2. @login_required
  3. def new_request():
  4.     db = get_db()
  5.     types = db.execute('SELECT * FROM document_types WHERE active = 1 ORDER BY name').fetchall()
  6.     if request.method == 'POST':
  7.         doc = db.execute('SELECT * FROM document_types WHERE id = ? AND active = 1',
  8.                          (request.form.get('document_type_id', type=int),)).fetchone()
  9.         purpose = request.form.get('purpose', '').strip()
  10.         copies = request.form.get('copies', 1, type=int) or 1
  11.         if doc is None or not purpose or not 1 <= copies <= 5:
  12.             flash('Choose a document, enter the purpose and 1-5 copies.', 'error')
  13.         else:
  14.             cur = db.execute('INSERT INTO requests (reference_no, user_id, document_type_id, purpose, copies, amount) '
  15.                              'VALUES (?, ?, ?, ?, ?, ?)', ('NEW-' + secrets.token_hex(8), g.user['id'], doc['id'],
  16.                                                           purpose, copies, doc['fee'] * copies))
  17.             ref = assign_reference(db, cur.lastrowid)
  18.             db.commit()
  19.             flash(f'Request {ref} submitted. Processing time: about {doc["processing_days"]} working day(s).', 'success')
  20.             return redirect(url_for('my_requests'))
  21.     return render_template('request_form.html', types=types)

Database Tables

The database has three tables: users (residents and staff, separated by a role column), document_types (name, fee, processing days, active flag) and requests. The CHECK constraint keeps the status column clean even if someone edits the database by hand:

  1. CREATE TABLE requests (
  2.     id INTEGER PRIMARY KEY AUTOINCREMENT,
  3.     reference_no TEXT NOT NULL UNIQUE,
  4.     user_id INTEGER NOT NULL REFERENCES users(id),
  5.     document_type_id INTEGER NOT NULL REFERENCES document_types(id),
  6.     purpose TEXT NOT NULL,
  7.     copies INTEGER NOT NULL DEFAULT 1,
  8.     STATUS TEXT NOT NULL DEFAULT 'Pending'
  9.         CHECK (STATUS IN ('Pending', 'Approved', 'Ready for Pickup', 'Released', 'Rejected')),
  10.     remarks TEXT NOT NULL DEFAULT '',
  11.     amount REAL NOT NULL DEFAULT 0,
  12.     requested_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
  13.     updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
  14. );

Project Structure

barangay-document-request-system/
  app.py              Flask routes, database helpers, login and CSRF checks
  schema.sql          Table definitions
  requirements.txt    Flask
  test_app.py         30 automated checks using Flask's test client
  README.txt          Setup steps and demo accounts
  static/style.css    Layout, badges and print styles
  templates/          Jinja2 pages (base, login, register, my_requests,
                      request_form, admin_dashboard, admin_requests,
                      admin_request_detail, certificate, admin_residents,
                      admin_documents)

How to Run the Barangay Document Request System

  1. Install Python 3.10 or newer from python.org. On Windows, tick "Add python.exe to PATH" during setup.
  2. Download the zip file below and extract it.
  3. Open a terminal (Command Prompt, PowerShell or the VS Code terminal) inside the barangay-document-request-system folder.
  4. Optional but recommended: create a virtual environment with python -m venv venv, then activate it with venv\Scripts\activate on Windows or source venv/bin/activate on macOS/Linux.
  5. Install Flask: pip install -r requirements.txt
  6. Start the app: python app.py. The first run creates barangay.db with sample data.
  7. Open http://127.0.0.1:5000 in your browser.

Demo accounts: staff admin / admin123; residents juan, maria and pedro, all with password resident123. To start over with fresh sample data, run flask --app app init-db. You can change the barangay name, municipality and Punong Barangay printed on the certificate with the BARANGAY_NAME, MUNICIPALITY and CAPTAIN environment variables, or by editing the defaults at the top of app.py.

Automated Tests

Run python test_app.py to check the main flows (it uses a temporary database, so your data is safe). This is the output from the version in the zip:

PASS guest redirected to login
PASS POST without CSRF token rejected
PASS wrong password rejected
PASS short password rejected on register
PASS resident registered
PASS duplicate username rejected
PASS resident lands on My Requests
PASS resident cannot open admin pages
PASS request submitted with next reference number
PASS amount = fee x copies (2 x 50)
PASS empty purpose rejected
PASS free document shows Free
PASS resident cancels own pending request
PASS cannot cancel another resident's request
PASS reference number not reused after cancel
PASS staff lands on dashboard
PASS dashboard pending count = 4
PASS dashboard fees collected = released only
PASS filter by status
PASS search by resident name
PASS print blocked while pending
PASS status updated
PASS invalid status rejected
PASS certificate prints name, purpose, reference
PASS resident sees new status and remarks
PASS residents page lists Ana with 2 requests
PASS document type added
PASS fee updated
PASS deactivated type hidden from residents
PASS logout works

30 passed, 0 failed

Ideas to Extend the Project

  • Send an SMS or email when a request becomes Ready for Pickup.
  • Let residents upload a photo of a valid ID during registration, with staff verification.
  • Add a QR code on the printed certificate that links to a verification page.
  • Add blotter records and block clearance requests for residents with an open case.
  • Add monthly reports of requests and collections, exported to CSV.
  • Before deploying, change SECRET_KEY and the demo passwords, turn off debug mode, and run it behind a production server such as Gunicorn or Waitress.

Conclusion

The Barangay Document Request System shows a complete Flask workflow in one small project: registration and login with hashed passwords, two user roles, a request lifecycle with statuses and remarks, a dashboard with summary queries, and a printable certificate. Download the source code, run it with the sample data, then make it your own with your barangay's documents, fees and branding. If you use it for a capstone, the extension ideas above are a good way to add your own features.

Note: Due to the size or complexity of this submission, the author has submitted it as a .zip file to shorten your download time. After downloading it, you will need a program like Winzip to decompress it.

Virus note: All files are scanned once-a-day by SourceCodester.com for viruses, but new viruses come out every day, so no prevention program can catch 100% of them.

FOR YOUR OWN SAFETY, PLEASE:

1. Re-scan downloaded files using your personal virus checker before using it.
2. NEVER, EVER run compiled files (.exe's, .ocx's, .dll's etc.)--only run source code.