Starlamp started because a friend asked me for something simple. He wanted image galleries where he could drop images into folders and have a website reflect the structure they were dropped in. No admin panel. No uploads. No database content. He did not want to learn a tool. He wanted to use his file manager and be done.
So that became the rule: filesystem is the interface.
There is no gallery model. A gallery exists because a directory exists under MEDIA_ROOT. Subgalleries exist because there are subdirectories. the code looks at disk and assumes whatever is there is correct.
Django was far too heavy for what this system actually did. Most of the stack existed to serve templates around what was effectively a filesystem mirror. There were no models, no relational data behavior, no dynamic state that justified a full web framework. The core logic lived in directory walks and path manipulation, not in Django itself.
That said, the choice was not irrational at the time. I deliberately avoided a static site generator because I did not want my friend to have to think about build steps. No recompiling the site. No deploy pipeline. Drop files into a directory and refresh the page was the entire promise. Django made that trivial.
path = settings.MEDIA_ROOT
def get_sg_path(fp=""):
galfp = os.path.join(path, fp)
base_sg = [
sdir for sdir in os.listdir(galfp)
if os.path.isdir(os.path.join(galfp, sdir))
]
At the time, this felt obvious. If a folder is there, it should show up. If it isn't, it shouldn't. There was nothing to synchronize and nothing to get out of sync.
Top level folders became galleries. Subfolders became subgalleries. Directory names became titles by implication. Nesting came for free because folders already nest.
Even the values returned to the templates were just reformatted disk paths.
for splits in uf_sg:
fm_sg.append("/".join(splits.strip("/").split('/')[4:]))
Ordering worked the same way. There was nowhere to store order, so filenames did the work.
Images are listed from the directory, filtered by extension, and returned. If you wanted something first, you named it so it sorted first.
prim_gallery = [
img for img in os.listdir(galfp)
if os.path.isfile(os.path.join(galfp, img))
if img.split(".")[-1] in exts
]
There was one rule and it was easy to explain: the site shows what's on disk. If something doesn't look right, try renaming it.
Thumbnails
Thumbnails followed the same logic.
There is no "cover image" concept in Starlamp. The first file in each top-level gallery becomes the thumbnail. Subgalleries are explicitly excluded so only the top layer produces thumbnails.
def get_thumbs():
gallery_folder_scan = os.scandir(path)
for gdirs in gallery_folder_scan:
if gdirs.is_dir():
gallery_folder_names.append(gdirs.name)
Subdirectories are removed from the thumbnail search so they do not interfere.
for root, subdirectories, files, in os.walk(items, topdown=True):
subdirectories[:] = [d for d in subdirectories if d not in items]
unformatted_exclusion_list.append(subdirectories)
Then the first sorted file wins.
for root, subdirectories, files, in os.walk(path, topdown=True):
for file in sorted(files):
unformatted_image_filepath.append(os.path.join(root, file))
break
If you wanted control, you controlled the sort order. So prefixes like 01_ and cover_ became the norm.
If I were rebuilding this today while keeping the same simplicity promised, I would add an optional index.json or gallery.yaml. Not required. Only read if present. Cover image. Optional title override. Optional order. Everything else defaults to the existing behavior.
Django
Django was heavier than this problem needed.
Most of the code does not use Django's strengths. There are no meaningful models. No relational data driving behavior. The interesting logic lives in directory walks and path slicing. Django is mostly there to serve templates and URLs.
At the time, I avoided static site generators on purpose. I did not want my friend to have to think about rebuilding a site. No build step. No pipeline. Drop files into a directory, refresh the page.
If I were doing this now, I would still protect that experience, but I would use something lighter. Either a tiny server that serves a cached filesystem index, or a static generator paired with a file watcher that rebuilds automatically when directories change.
Same external behavior. Much less weight.