Developer: Railson Gonçalves (raigonlab)
RaigonOS is a full-stack gallery management platform built with Django and PostgreSQL, allowing an artist to catalogue, organise and present their art Collections and individual Artworks online, with full create, read, update and delete control over their own content.
It is a conceptual continuation of raigon — a static, single-page gallery built with HTML, CSS and JavaScript only — but rebuilt entirely from scratch as a dynamic, database-backed application. No code from that project is reused; only the domain (an artist's digital gallery) carries over, as permitted by the assessment brief.
Target audience
- Primary — the site owner (artist): needs a simple, dependable tool to manage their portfolio (add/edit/remove Collections and Artworks) without touching code.
- Secondary — visitors: collectors, peers and the public browsing the published gallery.
Value provided
- The artist gets a private, authenticated back office to manage their public-facing gallery content directly.
- Visitors get organised, structured access to an artist's body of work, grouped into Collections.
Key technical decisions
- Django + PostgreSQL, with two core related models —
CollectionandArtwork(one-to-many) — kept deliberately minimal for the MVP. - Authentication restricts all create/edit/delete actions to the authenticated owner; the public gallery remains read-only for visitors.
- Configuration (
SECRET_KEY,DEBUG,DATABASE_URL, allowed hosts) is entirely environment-variable driven, so the same codebase runs unchanged locally (SQLite) and in production (Render + PostgreSQL). - Uploaded images are stored on Cloudinary in production (switched on via
CLOUDINARY_CLOUD_NAME/CLOUDINARY_API_KEY/CLOUDINARY_API_SECRET), since Render's own filesystem is wiped on every deploy and cannot be used for persistent user uploads.
Long-term vision (explicitly out of scope for this MVP)
Beyond this submission, RaigonOS is conceived as something bigger than a
single-artist portfolio tool: a secure, lifelong archive where any artist
can catalogue their entire body of work as it grows — a robust visual
record of their artistic journey, not just a gallery for the present.
None of the following is built or planned for this cycle; it's recorded
here to document the reasoning behind decisions like the extensible
owner-based data model.
- A record of the artist's evolution. Because Collections and Artworks are timestamped and organised chronologically, the platform is naturally positioned to show how an artist's style and body of work developed over their career, not just a snapshot of current pieces.
- Public exhibition beyond the site itself. Artwork catalogued here could be surfaced into real public spaces — museum displays, electronic street billboards, screens on public transport — in the spirit of how platforms like Unsplash license imagery for public use, but for original, human-made art rather than stock photography.
- Authenticity and provenance. As AI-generated imagery becomes harder to distinguish from human work, a documented, timestamped record of an artwork's creation — tied to a verified artist — becomes valuable in itself: proof that a piece is genuinely human-made. This could evolve into part of the platform's core value, not just a cataloguing convenience.
- Multi-artist mode. The data model is already shaped for this
(
Collection.owneris aUserforeign key), but public multi-artist sign-up is explicitly not part of this submission — see Features below for the full MoSCoW breakdown of what is and isn't included now. - An integration layer, not a walled garden. Rather than being the only place an artist's work lives, the catalogued archive could be exposed (via an API or embeddable widget) so other platforms — social media, online stores, third-party portfolio sites — can plug into it and reuse the same catalogued data, instead of the artist re-uploading their work separately everywhere.
- The public gallery as an exhibition in motion. The public side is meant to be experienced, not browsed: artworks drift slowly through a dark, mostly empty viewport, like objects floating in an exhibition space, with almost invisible controls. Movement can respond to scroll, pointer/touch or discreet navigation, but the visitor can also simply stay and contemplate. The conceptual reference is an earlier Raigon Lab project, raigon-mmxi — an inspiration for the motion and atmosphere, not something to copy; it will be redesigned in the RaigonOS visual language (deep black, soft contrast, subtle type, very restrained gold). A first version now lives on the public home (see Existing Features); the rest of the public site (Collections, Collection and Artwork pages) still uses the earlier cream design and is the next step towards the same atmosphere.
- A way in for street artists. Street art is ephemeral — pieces are painted over and documentation ends up scattered across phone galleries and feeds. A free, low-barrier place to catalogue and organise that work (a Collection per wall, series or crew project) is a natural audience for the platform.
- A toolbox and a storefront. Beyond the archive, optional "toolbox" modules could reuse the same Collections and Artworks data — the first being a storefront that turns catalogued work into a shop (originals, prints, digital files) without re-uploading anything. Payments is shown as "Coming soon" in the dashboard sidebar, next to The Maker, to make that direction visible.
- The Maker. A dedicated page for the person behind the work, in keeping with a system about documenting and preserving work rather than building a public persona. Shown as "Coming soon" in the dashboard sidebar so the direction is visible without spending development time on it.
The guiding principle behind all of the above: a platform artists can trust with their life's work, without extractive fees or unnecessary bureaucracy standing between them and presenting it to the world.
Comparable platforms (researched for direction, not copied from)
- Artwork Archive — art inventory software artists use to catalogue their full body of work: location, exhibition history, sales, condition — closest existing parallel to the "lifelong record" idea above.
- Niio — streams digital art to screens (TVs, business/public displays), the closest existing model for the public exhibition idea, though its catalogue includes AI-generated art, which runs counter to this project's "verified human-made" principle.
- Content Credentials / C2PA — an industry standard (backed by Adobe, Microsoft, Google, Meta, the BBC and 500+ others) for attaching verifiable creation/edit history to a file, distinguishing authentic from synthetically generated content. The technical model closest to the authenticity idea above.
- No existing platform combines all of the above (lifelong archive + public exhibition + authenticity + third-party integration) in one place — as far as this research found, that combination is an open gap rather than something already solved elsewhere.
Purpose
- Give an artist full control over publishing and organising their digital gallery, without needing to write code.
- Present that gallery to the public in a clean, distraction-free way that puts the artwork first.
Primary User Needs
- (Owner) Add, edit and remove Collections and Artworks quickly and safely.
- (Owner) Be confident that only they can modify their content.
- (Visitor) Browse Collections and view individual Artworks with clear detail (title, medium, year, description).
Business Goals
- Prove full CRUD competency against a real relational schema (Collection ↔ Artwork), satisfying the Back End Development unit's core requirements.
- Deliver a genuinely usable tool, not just a coursework demo — one the developer can keep using for their own gallery after submission.
Features (MVP — must-have)
- Authentication: signup, login, logout.
- Full CRUD on
Collection(owner only). - Full CRUD on
Artwork, linked to aCollection(owner only). - Public, read-only pages listing Collections and their Artworks.
Features (documented, not built this cycle — see MoSCoW table)
- Invitation system for private/invite-only Collections.
- Direct contact/inquiry form.
- Multi-artist public sign-up.
- Search and filtering.
Content Requirements
- Collection: title, description, cover image, status.
- Artwork: title, image, medium, year, description.
Information Architecture
/— public gallery home: every published Artwork, across all Collections (the default a visitor lands on)/collections/— browse published Artworks grouped by Collection instead/collection/<slug>/— Collection detail (its Artworks)/artwork/<id>/— Artwork detail/dashboard/— owner-only management area: list of the owner's Collections/dashboard/collections/<slug>/— one Collection's own dashboard page (its Artworks; View/Edit/Archive as a quiet action row, Delete in a "..." overflow menu)/dashboard/archive/— owner's archived Collections (kept on record, never public); archive/unarchive are single-click POST actions/dashboard/artworks/— owner's Artworks across all Collections/dashboard/collections/bulk/,/dashboard/artworks/bulk-delete/— POST-only endpoints behind the Select mode's bulk actions/dashboard/artworks/<id>/— Artwork preview (full page, not a modal), with Previous/Next through the rest of its Collection/accounts/login/,/accounts/logout/,/accounts/signup/
Two surfaces
RaigonOS is built as two separate surfaces today:
- Public artist gallery — what visitors see of an artist's published
work. Today a conventional page (
/,/collections/,/collection/<slug>/,/artwork/<id>/); planned to become the moving exhibition described under the long-term vision. The dashboard's "Public Gallery" link opens this surface. - Private dashboard — where the artist manages Collections,
Artworks and the Archive (
/dashboard/...).
A third surface — a RaigonOS landing page introducing the product/platform itself, separate from any one artist's gallery — is explicitly out of scope for this submission and tracked as backlog: see Issue #19.
User Flow
Two flows, one per audience. Visitors never need an account; the owner only ever leaves the dashboard deliberately, through its "Public Gallery" link.
flowchart TD
V([Visitor]) --> H["Gallery home /<br/>Exhibition or Grid view"]
H --> C["Collections<br/>/collections/"]
C --> CD["Collection detail<br/>/collection/#lt;slug#gt;/"]
H --> AD["Artwork detail<br/>/artwork/#lt;id#gt;/"]
CD --> AD
AD -- "Previous / Next" --> AD
O([Owner]) --> L["Log in / Sign up"]
L --> D["Dashboard — Collections<br/>/dashboard/"]
D --> CM["Collection page<br/>/dashboard/collections/#lt;slug#gt;/"]
D -- "New Collection" --> CF["Collection form"]
CM -- "Edit" --> CF
CM -- "Add Artwork" --> AF["Artwork form"]
CM -- "Delete" --> CDEL["Confirm delete page"]
CM -- "Archive" --> AR["Archive<br/>/dashboard/archive/"]
AR -- "Restore (to Draft)" --> CM
D --> AL["All Artworks<br/>/dashboard/artworks/"]
AL --> AM["Artwork preview<br/>/dashboard/artworks/#lt;id#gt;/"]
AM -- "Edit" --> AF
AM -- "Delete" --> ADEL["Confirm delete page"]
CF & AF & CDEL & ADEL -- "success message" --> D
D -- "Public Gallery" --> H
- Visitor: lands on the gallery home, which shows every published Artwork as a drifting exhibition, or as a plain grid (switchable from the site menu). From there they can browse by Collection, and step through a Collection one Artwork at a time with Previous/Next (or the arrow keys) without going back to a list.
- Owner: logs in and lands in the dashboard. Each Collection has its own page, where the owner adds, edits and deletes its Artworks. A new Collection starts as a Draft; the owner publishes it when it's ready. Every create, update and delete returns to the dashboard with a success message. Deletes always go through a confirmation page first. Archived Collections move to the Archive page, and restoring one puts it back to Draft, never straight to Published.
The screens along each flow are shown in Wireframes and, as built, in the responsiveness screenshots in TESTING.md.
Two passes per surface (see Wireframes below): a hand-drawn sketch first, covering the full screen set — the public gallery's four core screens (Grid, Collections, Exhibition and the shared top navigation) and the dashboard's two list layouts (thumbnail/ grid and list view) plus its shared header controls — then higher-fidelity wireframes for the key individual screens of each surface. Produced to document the structure already settled on through development, rather than drafted upfront before templates existed.
Visual Design (implemented)
- Warm, cream gallery atmosphere — white cards and form elements float on a soft cream background, rather than a stark white/black contrast.
- Large, consistently rounded corners on cards, images and form elements; fully pill-shaped buttons.
- Generous whitespace between elements.
- A serif display face (headings, with italic used for emphasis within a heading) paired with a clean sans-serif for body text and UI.
- Small, uppercase, letter-spaced "eyebrow" labels above page titles (e.g. "Collection", "Dashboard") for wayfinding.
- The RaigonOS logo mark: a bold "R" monogram (simplified from an earlier circular-seal design, which read as illegible noise at the sizes the logo is actually displayed).
- Tagline: Create · Collect · Legacy — mirroring the actual user flow (create an Artwork, collect it into a Collection, build a lasting portfolio).
Visual language is inspired by raigon.ch — for aesthetic direction only; no content, copy, or functionality from that site is used here.
Dashboard (owner-only) visual system — deliberately distinct from the public gallery above
- App-like rather than editorial: a persistent left sidebar (Collections, All Artworks), a top bar with a breadcrumb, and a small hand-drawn line-icon set (in the spirit of Notion/Linear/macOS) instead of text buttons.
- Light by default — the same cream palette as the public site — with an explicit dark-mode toggle, remembered per browser. The public gallery itself has no dark mode; only the dashboard does.
- Sidebar and breadcrumb are present on every dashboard screen, including the Artwork preview page — nothing (not even a "quick look") ever covers or hides them, so the owner always knows which workspace they're in and can navigate away at any time.
- Grid/List is one underlying list rendered two ways (CSS only, same markup), not two different interfaces — the owner's choice is remembered per browser too.
Warm, neutral palette — cream background with white cards, so elements feel like they're placed on the page rather than boxed in:
| Token | Value |
|---|---|
| Background | #f7f4ee |
| Surface (cards, inputs) | #ffffff |
| Text | #121212 |
| Muted text / labels | #6b6657 |
| Border | #e6e1d6 |
Accessibility note: every text colour pairing above meets WCAG AA
(4.5:1) against both the background and surface colours. The muted
tone was deliberately darkened from an earlier, lighter draft
(#8a8578, 3.35:1) after checking contrast ratios directly, since the
lighter version failed AA for body-sized text. The one deliberate
exception is the Border colour against the background (~1.2:1) —
it's a decorative divider, not a text colour, and card/input
boundaries are primarily conveyed through the white-surface-on-cream
background contrast rather than the border line itself.
- Playfair Display — used for page headings, including an italic weight for emphasis within a heading (e.g. "My Collections"), on public pages only.
- Inter — used for body text, navigation, forms and UI labels everywhere, and for headings in the dashboard (kept sans-serif there to read as an app, not a gallery page). A light (300) weight is used for the small captions under public gallery cards.
Both are sourced from Google Fonts. The
pairing gives the same editorial, gallery-catalogue feel as the
raigon.ch reference: a confident serif voice for titles, a clean
sans-serif for everything functional.
Two passes per surface: a hand-drawn sketch covering the full screen set first, then higher-fidelity wireframes for the key individual screens.
Sketch — Grid, Collections, Exhibition and the shared top navigation:
Exhibition (free view) with the site menu open:
Grid view:
Sketch — thumbnail/list layouts and the shared header controls:
Collections list, with one row expanded to show its Artworks inline:
A single Collection's own page (its Artworks):
| Target | Expectation | Outcome |
|---|---|---|
| As the site owner | I want to sign up and log in securely | So only I can manage my gallery content |
| As the site owner | I want to create a Collection | So I can organise my Artworks by theme or series |
| As the site owner | I want to edit or delete a Collection | So I can keep my gallery accurate and current |
| As the site owner | I want to add an Artwork to a Collection | So visitors can see it presented in context |
| As the site owner | I want to edit or delete an Artwork | So I can correct mistakes or retire pieces |
| As a visitor | I want to browse public Collections | So I can view an artist's body of work |
| As a visitor | I want to view an Artwork's detail | So I can see its title, medium, year and description |
| As a visitor | I want the site to work on any device | So I have a consistent experience on mobile and desktop |
Full backlog (including could-have / won't-have items) is tracked as GitHub Issues using MoSCoW prioritisation — see Agile Development Process.
Public gallery
- Home page (
/) lists every published Artwork across all Collections in a uniform 4:5 portrait grid (1/2/4 columns depending on screen width) — the default a visitor lands on. It is the artist's storefront, so the whole page is deep black and opens as an exhibition: three rows of the published artworks, in random order, drifting slowly past each other in a mostly empty room, with the ones far from the centre softened, faded and shrunk for depth. Drag, swipe or scroll to move them, or just watch. There are no controls on the artwork: everything lives in one site menu, a single round button fixed at the top-right of every public page (exhibition, grid, Collections, Artwork pages). It opens a panel with the exhibition tools (pause/play, a direction switch — rows drifting sideways, or columns falling top to bottom — and full screen, shown only while the exhibition is on screen), a light/dark theme switch, the views (Exhibition, Grid, Collections) and the account links (Dashboard when signed in; Log in / Sign up otherwise). Theme and direction are remembered in the browser. The motion is a small vanilla JavaScript file (static/js/exhibition.js) whose concept follows an earlier Raigon Lab project, raigon-mmxi, rewritten for RaigonOS. Without JavaScript, or after choosing Grid, the page is the plain grid, so every published artwork is always reachable. /collections/lists published Collections instead, for browsing by series rather than a flat feed. It is one Collection per row, each with its cover, title, how many artworks it holds, the year (or span of years, taken from its artworks) and a short note (the Collection's description, shortened), so a visitor knows what they are about to open.- Collection and Artwork detail pages.
- A persistent path bar fixed to the bottom of every public page
except the home (
Gallery / Collections / <Collection> / <Artwork>) shows exactly where you are, in the spirit of the Finder path bar — deliberately not an inline breadcrumb, which shifted page content between pages of different depth. The home has none: it is the root, so the bar would only repeat the page you're already on. - The public header is deliberately bare: just the logo. Everything else (views, theme, Dashboard / Log in / Sign up) is in the site menu, one button at the top-right, so it is always in the same place. The public gallery pages are deep black by default with a light (cream) option; Log in and Sign up keep the cream look. The dashboard has its own shell and no site menu.
- Artwork detail: image and metadata/description side by side on wider screens (image capped at 70vh so it never dominates the page). Previous / next arrows (and the left / right keys) step through the Collection's artworks, with a "2 / 6" position; on narrow screens they become two inline links.
- There is no page footer: the slogan and copyright line added nothing to a gallery whose pages should be the work itself.
Authentication
- Signup, login, logout, with every mutating view behind
@login_requiredand an ownership check.
Dashboard (owner-only)
- Full CRUD on
CollectionandArtwork, always scoped torequest.user— editing or deleting someone else's content 404s rather than 403s. - A dedicated page per Collection (
/dashboard/collections/<slug>/) showing just its Artworks — reached by clicking anywhere on the Collection's card, not just a small icon. - All Artworks — every Artwork the owner has, across Collections, flattened into one list.
- Archive — a third Collection status alongside Draft and Published, for work stored in the catalogue but never published. Archived Collections disappear from the main Collections list and from the public site, and live in their own sidebar section; restoring one returns it to Draft (never straight to Published, so nothing goes public without a deliberate re-publish).
- Artwork preview — a full page (not a modal) showing one Artwork large with its metadata, plus Previous/Next links to browse the rest of its Collection without returning to the list. The Escape key and the Collection link in the breadcrumb both lead back to the Collection.
- Title search (
?q=, server-rendered, no JS) on Collections, All Artworks, and within a single Collection. - Grid/List toggle, remembered per browser, available everywhere Artworks or Collections are listed — both are the same underlying list, rendered two ways in CSS. List view is Finder-style: borderless rows that alternate between the page colour and a faint tint (zebra striping), so no outline competes with the artwork; Grid view keeps its cards.
- Bulk management — a "Select" button next to the search box turns the current list (Collections, All Artworks, or one Collection's Artworks) into selectable cards with a contextual action bar: Move to Draft / Publish / Archive for Collections, Delete for both. Deleting goes through the usual server-rendered confirmation page (no JS confirm dialog), and every selected id is re-checked against the logged-in owner, so other users' items are silently ignored. Artworks have no status of their own (it comes from their Collection), so Draft/Publish apply to Collections only.
- Light/dark theme toggle for the dashboard (light by default,
matching the public site's warm palette). The public gallery has its
own theme switch in the site menu (dark by default, so the artwork
stands out). Each choice is remembered separately in the browser.
Three tiny scripts are the only JavaScript kept inline: two in the
<head>that apply a saved theme (base.html, dashboard_base.html), and one placed right after the dashboard's list (_view_init_script.html) that applies a saved Grid/List preference. This is a deliberate exception to keeping JS in external files linked at the end of<body>. Each one has to run before the page is first painted, otherwise a returning user sees the default theme or layout flash before switching to their saved one. Everything else lives instatic/js/. - A persistent left sidebar and a breadcrumb in the top bar are present on every dashboard screen, including the Artwork preview — nothing ever hides them.
- A small hand-drawn SVG icon set replaces text buttons for repeated row actions, keeping rows usable on small screens. Artwork cards stay image-first: they show no persistent Edit/Delete icons, only a subtle "..." overflow menu (Edit artwork / Delete artwork) revealed on hover or focus.
- The Edit Artwork and Edit Collection forms show the current
image as a thumbnail (a custom
ImagePreviewInputwidget) instead of Django's raw "Currently: path" text, so the owner can see what they are replacing. - The public home opens with the project's sentence as its centrepiece — large editorial type, no stock imagery — plus one line on the problem it solves (work scattered across drives, phones and feeds). Public Artwork/Collection cards are frameless: no box around the work, just the image and a quiet caption, so the art is what you see.
| Priority | Feature |
|---|---|
| Must-have | Django project, PostgreSQL and initial deployment |
| Must-have | Collection model with full CRUD (owner only) |
| Must-have | Artwork model with full CRUD (linked to Collection) |
| Must-have | Authentication: login, logout, signup and ownership restriction |
| Must-have | Public gallery pages: browse Collections and Artworks |
| Should-have | Templates and CSS: warm editorial identity (public) plus a separate app-like dashboard system (sidebar, path bar, icons, light/dark) |
| Should-have | Title search across Collections and Artworks |
| Could-have | Invitation system for private Collections |
| Could-have | Contact/inquiry form for direct messages to the artist |
| Won't-have (this cycle) | Multi-artist public sign-up (marketplace mode) |
| Could-have | Public home as a moving exhibition (built for the home page; other public pages still to follow) |
| Won't-have (this cycle) | "The Maker" profile page (shown as "Coming soon" in the sidebar) |
| Won't-have (this cycle) | Street-artist onboarding, toolbox and storefront |
| Won't-have (this cycle) | Advanced filtering (by medium, year, status) beyond title search |
Two related models, deliberately kept minimal for the MVP:
Collection belongs to a User (owner), and Artwork belongs to a
Collection — a one-to-many relationship in each case. Deleting a
Collection cascades to delete its Artworks.
erDiagram
USER ||--o{ COLLECTION : owns
COLLECTION ||--o{ ARTWORK : contains
USER {
int id PK
string username
string email
string password
}
COLLECTION {
int id PK
int owner_id FK
string title
string slug
text description
image cover_image
string status
datetime created_at
datetime updated_at
}
ARTWORK {
int id PK
int collection_id FK
string title
image image
string medium
int year
text description
int display_order
datetime created_at
datetime updated_at
}
Collection
| Field | Type | Notes |
|---|---|---|
owner |
ForeignKey → User |
on_delete=CASCADE; who manages this Collection |
title |
CharField | |
slug |
SlugField (unique) | Auto-generated from title; used in public URLs |
description |
TextField | Optional |
cover_image |
ImageField | Optional; stored on Cloudinary in production |
status |
CharField (choices) | draft (owner-only, not ready yet), published (public) or archived (kept on record in the owner's Archive section, never public) |
created_at / updated_at |
DateTimeField | Auto-managed |
Artwork
| Field | Type | Notes |
|---|---|---|
collection |
ForeignKey → Collection |
on_delete=CASCADE |
title |
CharField | |
image |
ImageField | Required; stored on Cloudinary in production |
medium |
CharField | Optional, e.g. "Digital painting" |
year |
PositiveIntegerField | Optional |
description |
TextField | Optional |
display_order |
PositiveIntegerField | Controls ordering within a Collection |
created_at / updated_at |
DateTimeField | Auto-managed |
User is Django's built-in auth model — no custom user model was
needed for this domain.
- Secrets —
SECRET_KEY,DATABASE_URL, Cloudinary credentials and all other secrets are read from environment variables, never hardcoded..env(local secrets) is listed in.gitignoreand has never been committed;.env.exampledocuments the required keys with empty/placeholder values only. DEBUGisFalsein production, confirmed by two real incidents during development: with it correctly off, a misconfiguration once produced only a generic error page rather than a stack trace; separately, theDEBUGenvironment variable on Render was later found set back toTrue(rendering Django's technical error page, URLconf and all, on every 404) and corrected (see TESTING.md for both accounts).SECRET_KEYis env-var-only in every commit that matters, but the very first scaffold commit (4a73b9f) contains Django's auto-generateddjango-insecure-...placeholder fromstartproject— never the real production key, which has only ever lived in Render's environment variables, but still technically a key sitting in git history. Left as-is rather than rewriting commit history this close to submission, which risks breaking the granular commit trail the assessment itself asks to see.- Authentication & ownership — every create/edit/delete view is
behind Django's
@login_required. Editing or deleting another user's Collection or Artwork returns404 Not Foundrather than403 Forbidden, so a logged-in user can't even confirm that another user's private content exists. Covered by automated tests (DashboardPermissionTests). - Passwords are never stored in plain text — Django's default PBKDF2 password hashing is used unchanged.
- CSRF protection is active project-wide via Django's
CsrfViewMiddleware(enabled by default, never disabled); every form includes{% csrf_token %}, including the logout action, which is a POST rather than a plain link. ALLOWED_HOSTSis restricted to the actual production hostname, not left open — this was verified the hard way when a typo in it caused every production request to be rejected (see TESTING.md).- File uploads are validated as real images by Django's
ImageField(backed by Pillow) before being accepted, and are stored on Cloudinary rather than the app server's own disk.
- Python
- Django
- PostgreSQL (production) / SQLite (local development)
- HTML5
- CSS3 (custom)
- JavaScript
- Gunicorn, WhiteNoise (production serving)
- Pillow (image handling)
- Git & GitHub
- Render (deployment)
- Google Fonts
GitHub Issues are used to plan and track development, prioritised with
MoSCoW labels (must have, should have, could have, wont-have).
All eight must-have issues (project setup; the Collection and Artwork
models with CRUD; authentication; the public gallery pages; visual
identity, responsive layout and custom error pages; password reset;
and this README itself) are closed, matching the features actually
shipped; should/could/won't-have issues record everything considered
beyond the MVP, including the long-term ideas discussed under
Rationale.
Project board → — the same 20 issues tracked on a Kanban-style board (Todo / In progress / Done).
Full manual and automated testing procedure, plus a log of bugs found and fixed during development, is documented in TESTING.md.
Deployed on Render, with a managed PostgreSQL instance in the same region.
Deployment steps:
- Create a PostgreSQL instance on Render and copy its Internal Database URL.
- Create a Web Service on Render, connected to this GitHub repository
(
mainbranch). - Set the Build Command:
pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate - Set the Start Command:
gunicorn raigonos.wsgi:application - In the Web Service's Environment settings, set:
SECRET_KEY— generated via Render's own secure value generatorDEBUG=FalseALLOWED_HOSTS— the Render service hostname (e.g.raigonos.onrender.com)CSRF_TRUSTED_ORIGINS—https://raigonos.onrender.comDATABASE_URL— the Internal Database URL from step 1ARTIST_NAME— optional; the name in the home page's welcome line (defaults to the site owner's)CLOUDINARY_CLOUD_NAME,CLOUDINARY_API_KEY,CLOUDINARY_API_SECRET— copied individually from your Cloudinary dashboard's Account Details. Required so uploaded Collection/Artwork images persist — Render's filesystem is wiped on every deploy, so without these, uploaded images work until the next deploy and then disappear.
- Deploy. Render builds the app, runs migrations, and starts Gunicorn
automatically on every push to
main.
A Procfile is also kept at the repo root (web: gunicorn raigonos.wsgi:application, release: python manage.py migrate) for
Heroku-style platforms that read it directly. Render's own Web Service
doesn't use it — it runs the Build/Start Commands set in step 3–4
above instead — but the Procfile documents the same two commands in
the platform-agnostic format the assessment brief expects, and keeps
the option open to redeploy on a Procfile-based host without changes.
Live link: https://raigonos.onrender.com
Availability: the production PostgreSQL database is on a paid Render plan, paid until 2026-11-06. The live site is guaranteed to be available, with all its data, until that date. Render's free database plan expires after 90 days, which is why it was upgraded (see Known Issues in TESTING.md).
To run the project locally:
- Clone the repository:
git clone https://github.com/raigonlab/raigonos.git - Navigate into the project folder:
cd raigonos - Create and activate a virtual environment:
python3 -m venv venv source venv/bin/activate - Install dependencies:
pip install -r requirements.txt - Copy
.env.exampleto.envand fill in local values (a localSECRET_KEY,DEBUG=True, etc.). - Apply migrations:
python manage.py migrate - Run the development server:
python manage.py runserver - Open
http://127.0.0.1:8000in your browser.
- Markdown.2bn.dev — documentation
- MDN Web Docs — DOM API reference
- Code Institute materials
- Django documentation
- Claude — coding assistant, used throughout development for planning, debugging and project support, and specifically to help design and build the owner-only dashboard's UI/UX system in collaboration with the developer: the persistent sidebar/breadcrumb navigation model, the light/dark theme, the Grid/List toggle, the per-Collection and Artwork preview pages, the fixed path bar on public pages, search, and the hand-drawn icon set. This involvement is reflected in the project's commit history.
- ChatGPT — debugging and explanations
- Gemini — image generation
- fonts.google.com — Playfair Display and Inter
- fireship.dev
- TinyPNG — image compression
- ResponsivelyApp — managing screenshots
- All artwork pieces by Railson Gonçalves (© Raigon Lab, MMXXIII)
- Logo and symbol images are original works by Railson
Special thanks to my mentor, Marko Tot, for guidance and support throughout the project, and to Tim Nelson and Fernando for their support and help debugging along the way.





