An educational desktop mini-CRM built as a portfolio project. It manages clients, deals and tasks through a Tkinter GUI backed by a FastAPI service and a SQLite database, and it exports analytics reports to Google Sheets.
- Clients, deals and tasks with create, view, edit and delete; clients can also be archived and tasks can be completed or reopened.
- Nullable relations: a deal can reference a client, and a task can reference a client, a deal, both or neither.
- Search on all three entities through the HTTP API.
- Analytics reports (clients, deals, tasks) exported as new Google Spreadsheets, from the GUI or from a CLI.
- A demo-data generator that fills a running backend through its HTTP API.
Tkinter GUI ──HTTP──▶ FastAPI backend ──▶ SQLite
│
└── reporting layer (analytics + exporter)
├── Google Drive API (user OAuth) creates the spreadsheet in a Drive folder
└── Google Sheets API (service account) writes and formats the content
- Backend (
backend/): FastAPI application with a SQLite database. It knows nothing about Google. - Desktop GUI (
gui/,run_gui.py): Tkinter/ttk client that talks to the backend over HTTP. The interface is in English. - Reporting (
reports/): fetches all records from the backend API (paginated), computes analytics, and writes the report. - Google integration (
google_integration/):- Google Drive, user OAuth. A Desktop-app OAuth client authorizes you in the browser once. The token is stored locally and is used to create a spreadsheet inside the configured Drive folder.
- Google Sheets, service account. A service account writes and formats the created spreadsheet. It needs Editor access to that Drive folder.
Only the backend is containerized. The GUI, the report export and the Google credentials stay on the host machine, because they need a browser, a display and local credential files.
- Python 3.12 is what the project is developed on and what the Docker image uses. The test suite also passes on Python 3.11 and 3.14 (see Testing). Other versions are untested.
- Tkinter for the desktop GUI. It ships with the Python installers for Windows and macOS. On Debian/Ubuntu install it separately (
sudo apt install python3-tk); other Linux distributions have an equivalent package. Check withpython -m tkinter. The backend, the CLI scripts and most tests do not need Tk. - Docker (optional) to run the backend in a container.
- A Google Cloud project (only for report export); see Google setup.
Create a virtual environment and install the dependencies.
Windows (PowerShell):
python -m venv .venv
.venv\Scripts\Activate.ps1macOS / Linux (use python3 if python is not available):
python -m venv .venv
source .venv/bin/activateThen, on any platform:
python -m pip install -r requirements.txtRequirements files:
| File | Contents |
|---|---|
requirements.txt |
Everything needed to run the application locally: backend, GUI, reporting, Google integration. |
requirements-backend.txt |
Backend only. Installed by the Docker image and included by requirements.txt. |
requirements-dev.txt |
requirements.txt plus the test tooling and the demo-data generator (Faker). |
Versions are given as compatible ranges (a tested lower bound and a cap at the next major release). Transitive dependencies are not pinned.
Copy the template and edit the local copy:
Copy-Item .env.example .env # Windows (PowerShell)cp .env.example .env # macOS / LinuxVariables read by the code:
| Variable | Used by | Purpose |
|---|---|---|
DATABASE_PATH |
backend | SQLite file. Relative paths are resolved from the project root. Default: data/crm.db. |
BACKEND_URL |
GUI, reports, scripts | Where clients reach the backend. Default: http://localhost:8000. |
GOOGLE_OAUTH_CLIENT_SECRET_PATH |
Drive | OAuth Desktop client JSON downloaded from Google Cloud. |
GOOGLE_OAUTH_TOKEN_PATH |
Drive | Where the OAuth token is stored after the first authorization. |
GOOGLE_SERVICE_ACCOUNT_PATH |
Sheets | Service account key JSON. |
GOOGLE_DRIVE_FOLDER_ID |
reports | ID of the Drive folder that receives the reports. |
.env.example also lists BACKEND_HOST and BACKEND_PORT. The Python code does not read them; the backend port is set by the uvicorn command (8000 in the Dockerfile and Compose file).
Real environment variables take precedence over values in .env.
Only needed for report export. The rest of the application works without it.
- In a Google Cloud project, enable the Google Drive API and the Google Sheets API.
- Configure the OAuth consent screen. While the app is in Testing mode, add your Google account as a test user.
- Create an OAuth client of type Desktop app and download its JSON.
- Create a service account and download its JSON key.
- Create a Google Drive folder for the reports and share it with the service account's email address as Editor.
- Put the two JSON files in
credentials/(or point the environment variables elsewhere) and setGOOGLE_DRIVE_FOLDER_IDto the folder's ID (the last segment of the folder URL).
The default locations from .env.example are:
credentials/client_secret.json OAuth client (you provide)
credentials/service-account.json service account key (you provide)
credentials/token.json OAuth token (created automatically)
On the first export, a browser window opens for the Google authorization and the token is saved to GOOGLE_OAUTH_TOKEN_PATH. Later runs reuse it.
With Docker (development setup, see Docker notes):
docker compose up -d --build
docker compose psOr directly with Python, from the project root:
python -m uvicorn backend.main:app --reloadEither way the API is at http://localhost:8000, with interactive docs at /docs and a health check at /health. Stop the container with docker compose down.
The SQLite database lives in data/ and survives container restarts.
The generator needs Faker, so install the development requirements first (python -m pip install -r requirements-dev.txt). The backend must be running; the script writes through its HTTP API.
python -m scripts.fill_test_data --clients 5 --deals 5 --tasks 5 --seed 42Counts default to 1000 each. --seed (default 42) seeds the random generators; client e-mail addresses also contain a per-run identifier, so running the script twice adds distinct records. --base-url overrides BACKEND_URL.
python run_gui.pyThe backend must already be running. Each tab (Clients, Deals, Tasks) has search, add, edit, delete and an Export report button. After an export, a dialog lets you open the spreadsheet or copy its URL. Tables show at most 100 records at a time; the related-record pickers in the Add/Edit forms load the full list.
With the backend running and the Google setup done:
python -m scripts.export_reports --type clients
python -m scripts.export_reports --type deals
python -m scripts.export_reports --type tasks
python -m scripts.export_reports --type all # defaultEach report is a new Google Spreadsheet named Mini CRM - <Clients|Deals|Tasks> Report - YYYY-MM-DD HH-MM in the configured Drive folder. It has a summary block followed by a formatted table of all records:
- Clients: totals, active/archived counts, clients with and without a company, most common company, share of active clients.
- Deals: totals and amounts, averages, breakdown by status, won amount, deals with and without a client.
- Tasks: totals, completed and open counts, completion percentage, overdue open tasks, tasks with a client, with a deal, and without any link.
When run, these scripts call real services (the automated test suite only exercises them with fakes):
python -m scripts.smoke_test_backend # needs the running backend
python -m scripts.smoke_test_google_drive # needs Google OAuth setup
python -m scripts.smoke_test_google_integration # needs OAuth + service account setupThe Google smoke tests create a temporary spreadsheet and delete it afterwards; the backend smoke test creates a client, deal and task and removes them.
python -m pip install -r requirements-dev.txt
python -m pytestThe suite uses fakes and temporary databases. It needs no running backend, no Google credentials and no network access to Google. Tests that import the Tkinter GUI are skipped automatically on a Python built without Tk; everything else still runs.
The suite was run successfully in fresh virtual environments on Python 3.11, 3.12 and 3.14 in September 2026.
- Only the backend is containerized.
docker-compose.ymldefines a singlebackendservice. - The image installs
requirements-backend.txtonly: no test tooling, no Google libraries, no GUI dependencies. - The setup is meant for development: the source (
./backend) and the database directory (./data) are bind-mounted anduvicornruns with--reload. .dockerignorekeeps.env,credentials/, token files, databases,tests/andgui/out of the build context, and Compose mounts only./backendand./data. The container never receives Google credentials.- The GUI and the report export run on the host and reach the container through
BACKEND_URL.
credentials/*.json, token files,.envand SQLite databases are listed in.gitignore. Never commit them. Keep OAuth client files, service account keys and tokens on your machine only.- The Drive authorization requests the full
https://www.googleapis.com/auth/drivescope; the Sheets client requestshttps://www.googleapis.com/auth/spreadsheets. - Give the service account access only to the reports folder.
- Errors from the Google integration are designed not to echo credential contents or raw token-endpoint responses.
- Built for local, single-user desktop use. The API has no authentication or authorization; do not expose it to a network you do not trust.
- SQLite with a synchronous backend; no migrations.
- The Docker setup is for development (
--reload, bind mounts), not a production deployment. - Each export creates a new spreadsheet; nothing is updated or deleted. If writing fails after the spreadsheet was created, the file stays in the Drive folder and the error message reports its ID and link.
- Report export needs a Google Cloud project, a Drive folder and both credential types. The GUI tables show at most 100 records.
BACKEND_HOSTandBACKEND_PORTin.env.exampleare not used by the code (see Configuration).
mini-crm-google-reports/
├── backend/ FastAPI app, SQLite access, routers, repositories, schemas
├── gui/ Tkinter desktop client (api_client, app, dialogs)
├── reports/ Analytics, backend API client for reports, Google Sheets exporter
├── google_integration/ Config loading, Drive client (OAuth), Sheets client (service account)
├── scripts/ Report export CLI, demo-data generator, smoke tests
├── tests/ Automated test suite
├── credentials/ Local credential files (gitignored, only .gitkeep is tracked)
├── data/ Local SQLite database (gitignored, only .gitkeep is tracked)
├── run_gui.py GUI entry point
├── Dockerfile, docker-compose.yml, .dockerignore
├── requirements.txt, requirements-backend.txt, requirements-dev.txt
└── .env.example
- clients: name, company, email, phone, status (
activeorarchived), timestamps. - deals: title, amount, status (
new,in_progress,won,lost), expected close date, optionalclient_id, timestamps. - tasks: title, description, due date, completed flag, optional
client_idanddeal_id, timestamps.
Deleting a client or deal sets the references that point to it to NULL (ON DELETE SET NULL); the dependent records are kept.
- Authentication for the API.
- PostgreSQL instead of SQLite, with migrations.
- Server-side pagination and sorting in the GUI.
- Scheduled or background report exports.