Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🗳️ Electronic Voting Machine

A desktop voting machine built with Python (Tkinter) and MySQL, simulating a simplified electronic ballot: candidate selection, null vote, and vote confirmation, with all votes persisted to a relational database.

This was my first project combining Python with MySQL. It has since been refactored from a single-file script into a layered structure (connection / service / UI), and the database was normalized from a flat vote table into a proper candidates + votes relational model with a foreign key.


📋 Table of Contents


🛠 Tech Stack

  • Python 3.11+
  • Tkinter (GUI)
  • MySQL 8+
  • mysql-connector-python
  • python-dotenv

⚡ Technical Highlights

  • Layered architecture: connection handling, business logic, and UI are fully separated (db/, services/, ui/), instead of one monolithic script.
  • Safe resource management: database connections and cursors are opened with Python's with statement, so they're always closed properly, even if an error occurs mid-query.
  • Environment-based configuration: database credentials are loaded from a local senha.env file (never committed), with an absolute path resolution (Path(__file__).resolve()) so the app works regardless of which directory it's launched from.
  • Relational normalization (v1 → v2): candidates were originally hardcoded in Python and votes stored only as raw numbers. The schema was redesigned so candidates live in their own table, and votes.candidate_id is a real foreign key — guaranteeing every non-null vote points to an existing candidate.
  • Parameterized queries: all INSERT/SELECT statements use placeholders (%s), avoiding SQL injection.

📁 Project Structure

urna-eletronica/
├── main.py                  # Application entry point
├── sql/
│   ├── schema.sql           # Table creation + seed data
│   └── select.sql           # Useful queries for checking election results
├── db/
│   ├── __init__.py
│   └── connection.py        # Database connection handling
├── services/
│   ├── __init__.py
│   └── vote_service.py      # Business logic: candidate lookup, vote registration
├── ui/
│   ├── __init__.py
│   └── voting_window.py     # Tkinter interface
├── img/                      # Screenshots and GIFs
├── requirements.txt
├── .env.example              # Template for environment variables (no real credentials)
└── README.md

🗄 Database

Setup

Run sql/schema.sql against your MySQL server. It will:

  1. Create the db_machine_voting database (if it doesn't exist)
  2. Create the candidates and votes tables
  3. Seed the three initial candidates

Checking results

sql/select.sql contains a set of ready-to-use queries for auditing the election: total votes per candidate, null vote count, current winner, vote percentage, votes over time, and a full timestamped audit log.

Example output — total votes per candidate:

candidate vote_number total_votes
Jampaguara 1234 8
Kendry 8989 5
Shaulin 4567 2

Example output — current winner:

candidate total_votes
Jampaguara 8

Example output — audit log (most recent votes):

id candidate date_hour
16 Null vote 2026-07-04 14:32:10
15 Kendry 2026-07-04 14:31:48
14 Jampaguara 2026-07-04 14:30:57

Tables

Table Description
candidates Stores each candidate's ballot number and name
votes Stores each cast vote, linked to a candidate via candidate_id (nullable for blank/null votes)

Schema

CREATE TABLE candidates (
    id INT NOT NULL AUTO_INCREMENT,
    vote_number INT NOT NULL UNIQUE,
    name VARCHAR(100) NOT NULL,
    PRIMARY KEY (id)
);

CREATE TABLE votes (
    id INT NOT NULL AUTO_INCREMENT,
    candidate_id INT DEFAULT NULL,
    date_hour DATETIME DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    FOREIGN KEY (candidate_id) REFERENCES candidates(id)
);

🖥 Screens

Casting a vote for a candidate

The voter types the candidate's ballot number, confirms the name shown on screen, and the vote is recorded against that candidate.

Regular vote


Casting a null (blank) vote

Pressing Null immediately records a vote with no candidate attached — used when the voter chooses not to vote for anyone.

Null vote


⚖️ Business Rules

Candidate validation

Before a vote is confirmed, the typed number is looked up in the candidates table. If no candidate matches that ballot number, the vote is rejected with an error message and the field is cleared.

Confirmation step

Every valid vote requires an explicit yes/no confirmation dialog showing the candidate's name, preventing accidental votes from a mistyped number.

Null votes

A null vote is stored with candidate_id = NULL, distinguishing it from a normal vote at the database level, not just in the UI.


▶️ Getting Started

  1. Make sure you have Python 3.11+ and MySQL 8+ installed.
  2. Install the dependencies:
    pip install -r requirements.txt
  3. Run sql/schema.sql against your MySQL server to create the database, tables, and seed candidates.
  4. Copy .env.example to senha.env and fill in your actual MySQL credentials:
    DB_HOST=127.0.0.1
    DB_USER=root
    DB_PASS=your_password_here
    DB_NAME=db_machine_voting
    
  5. Run the app:
    python main.py

📌 Possible Next Steps

  • Build an admin/results screen inside the app itself, using the queries from sql/select.sql as a base (currently they need to be run manually).
  • Add input masking/limits so the entry field only accepts digits.
  • Add automated tests for vote_service.py, since business logic is now fully decoupled from the UI.

About

Desktop electronic voting machine built with Python, Tkinter and MySQL — refactored into a layered architecture with a normalized relational database.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages