Week 5 — Pengin-Pi-3: Overview & Quick Start

Part 1 — Overview (read before you start typing)

Pengin-Pi-3 is Tobu Pengin's internal Django-based platform mirror, hosted at: https://github.com/Pengin-Open-Source/pengin-pi-3

At a high level, it's a Django "starter kit" for building containerized business software (ERP/CRM/CMS-style tools), bundled with a set of reusable modules the team relies on across client projects — things like a ticketing system, customer/company management, a CMS and blog, a calendar, user/group permissions, logging, and webhook and workflow support, all designed to run behind NGINX in Docker with MongoDB available as an option alongside the default database.

Think of it the way the name suggests: a "Raspberry Pi" for cloud software — a general-purpose, customizable base you build specific client tools on top of, rather than starting every project from a blank Django project.

You are not setting up the full mirror this week. It's a large, production-oriented codebase, and cloning and configuring the whole thing on day one would bury the actual lesson. Instead, today you'll build a small Django app that mirrors one slice of that architecture — a single self-contained module, the same way a real feature would start life inside Pengin-Pi-3 — and containerize it in Docker. Weeks 6–8 build on this same project.

What to notice as you go: every piece you build today (an app with a model, an admin registration, a view, a Dockerfile) is a miniature version of a pattern that repeats throughout the real platform. When you eventually do read the mirror's source, these pieces should look familiar rather than foreign.


Part 2 — Quick start: build a mini module and containerize it

This assumes you completed the Week 3 Django + Docker handout. We'll reuse the same shape, applied to a new, slightly more "platform-like" example: a minimal support-ticket module — a simplified stand-in for the ticket system in the real Pengin-Pi-3 stack.

Set up the project

mkdir mini-pengin && cd mini-pengin
python3 -m venv venv
source venv/bin/activate        # macOS/Linux
# venv\Scripts\activate         # Windows

pip install django
pip freeze > requirements.txt

django-admin startproject config .
python manage.py startapp tickets

Register the app in config/settings.py:

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "tickets",
]

Build the ticket module

tickets/models.py

from django.db import models


class Ticket(models.Model):
    STATUS_CHOICES = [
        ("open", "Open"),
        ("in_progress", "In Progress"),
        ("closed", "Closed"),
    ]

    subject = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="open")
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"[{self.status}] {self.subject}"

tickets/admin.py

from django.contrib import admin
from .models import Ticket


@admin.register(Ticket)
class TicketAdmin(admin.ModelAdmin):
    list_display = ("subject", "status", "created_at")
    list_filter = ("status",)

tickets/views.py

from django.shortcuts import render
from .models import Ticket


def ticket_list(request):
    tickets = Ticket.objects.all().order_by("-created_at")
    return render(request, "tickets/ticket_list.html", {"tickets": tickets})

tickets/urls.py (new file)

from django.urls import path
from . import views

urlpatterns = [
    path("", views.ticket_list, name="ticket_list"),
]

config/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("tickets/", include("tickets.urls")),
]

tickets/templates/tickets/ticket_list.html (new folders)

<!DOCTYPE html>
<html>
<head><title>Tickets</title></head>
<body>
    <h1>Support Tickets</h1>
    <ul>
        {% for ticket in tickets %}
            <li><strong>{{ ticket.status }}</strong> — {{ ticket.subject }}</li>
        {% empty %}
            <li>No tickets yet.</li>
        {% endfor %}
    </ul>
</body>
</html>

Migrate and create a superuser:

python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser

Checkpoint: python manage.py runserver, log into /admin/, add a couple of tickets with different statuses, then check /tickets/ renders them with their status.

Containerize it

Dockerfile

FROM python:3.11-slim

ENV PYTHONUNBUFFERED=1
WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

.dockerignore

venv/
__pycache__/
*.pyc
db.sqlite3
.git/

docker-compose.yml

services:
  web:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - .:/app
    command: python manage.py runserver 0.0.0.0:8000
docker compose up --build

Visit http://127.0.0.1:8000/tickets/ — same app, now containerized.


Part 3 — How this connects to the real platform

Once this is running comfortably, skim (don't deep-dive yet) the real repository's main/, templates/, and util/ folders at the mirror link above. You're not expected to understand all of it — just notice the shape: apps organized by feature, a shared templates/ convention, and Docker/NGINX handling deployment the same way your Dockerfile does here, just at production scale.

Exercises before Week 6

  1. Add a priority field (choices: low/medium/high) to Ticket, and filter the admin list by it.
  2. Add a simple detail view/URL (/tickets/<id>/) so clicking a ticket shows its full description.
  3. In your own words (a few sentences is fine), write down which module in the real Pengin-Pi-3 feature list (ticket system, CMS, calendar, etc.) this exercise most resembles, and one thing you'd add to make it match more closely.

What's next

Weeks 6–8 build directly on this mini-pengin project as you extend it into a more complete app — that's where the real Pengin-Pi-3 patterns (middleware, more complex module structure) start getting introduced.