Before diving into the technical analysis, I want to provide some context that shaped this project's development. This was built just four months into my degree and four months into learning any form of computer programming, with a six-week deadline from conception to deployment. I understand this might read as an excuse for some of the technical decisions and debt discussed throughout this post-mortem, but I believe it's important context for understanding the learning trajectory and the challenges encountered along the way. Interestingly, this project scored near-perfect marks at university, which speaks to the relatively low expectations in an academic environment. While the project met and exceeded the assignment criteria, I understand there are substantial issues with the codebase that would be unacceptable in a professional setting. This reflection is as much about acknowledging what could have been done differently as it is about recognising how far the journey has come.
Seek Music emerged as an ambitious full-stack web application designed to bridge the gap between music enthusiasts and live
concert experiences. Built using Flask and SQLAlchemy, this platform aimed to provide a comprehensive
marketplace where event organisers could list concerts and attendees could discover, browse, and book tickets for their
favourite artists. What started as a conceptually straightforward CRUD application evolved into a complex system
that taught me invaluable lessons about web development, user experience design, and the hidden challenges that lurk beneath
seemingly simple features.
The Feature Landscape: What Was Built
At its core, Seek Music implements a full-featured event management and ticketing system. The authentication system allows
users to register and login with their email or username, with passwords securely hashed using bcrypt. Users can
maintain profiles complete with profile pictures, contact information, and editable personal details. The platform supports
role-based functionality where users can be both event organisers and ticket purchasers, creating a dynamic two-sided
marketplace.
The event creation and management system represents the heart of the application. Organisers can create detailed event listings with multiple form fields capturing everything from artist names and event categories to street addresses, cities, and Australian states. Each event supports file uploads for both a featured image and a carousel of preview images, allowing organisers to showcase their events visually. The system automatically generates event-specific directories in the file structure to organise uploaded media. Events are categorised by music genre including Pop, Rap, Rock, Indie, Alternative, Country, and Other, with filtering capabilities built throughout the interface.
The homepage features an immersive experience with an autoplay video background showcasing concert footage, overlaid with a sophisticated search bar that accepts text queries while also providing dropdown filters for genre and location. Below this, users encounter a carousel showcasing the first ten events in the database, displayed with diagonal tile animations that create visual interest. The category section provides quick navigation tiles for each music genre, routing users to filtered event listings.
Event detail pages represent some of the most feature-rich components of the application. Each event page displays comprehensive information including the featured image banner, artist name, event name, date and time formatted in readable strings, complete address details, and a collapsible ticket purchasing interface. The booking system validates ticket availability in real-time, calculates total costs based on quantity requested, and prevents overselling by tracking available tickets. An image carousel with thumbnail previews allows users to browse through event photos, while a comment section enables community engagement around events.
The booking management system provides users with a dedicated "My Booking" page where they can view all active bookings with details about the organiser, artist, event name, cost, and ticket quantity. Users can cancel bookings, which automatically returns the tickets to the available pool and updates the event status accordingly. Similarly, a "My Listing" page allows organisers to manage their created events, view event statuses, and cancel listings. An administrative panel exists with the capability to delete users, events, and comments, providing moderation capabilities.
The application implements dynamic status management where event statuses automatically transition from "Active" to "Inactive" when the event date passes. When all tickets are sold, events automatically update to "Sold Out" status. The search and filtering system allows users to query events by name, filter by category, city, state, or status, and combines multiple filters for refined searches.
Implementation Challenges: The Battles Beneath the Surface
The journey to implement these features was fraught with challenges that tested both technical skills and problem-solving
abilities. File upload management emerged as one of the most complex systems to implement correctly. The decision to support
multiple image uploads for event carousels introduced significant complexity around file handling, storage organisation, and
retrieval. The code had to securely handle filenames using werkzeug's secure_filename function, create
event-specific directories dynamically, and copy the featured image to a separate homepage directory for performance
optimisation. The most frustrating aspect was implementing the template logic to dynamically detect image file extensions,
resulting in those unwieldy conditional statements that check for .jpg, .jpeg, .png,
.gif, and .webp formats in sequence.
Form validation presented another layer of difficulty, particularly with the CreateEventForm that includes a
multitude of required fields with different data types. Implementing custom validators for the AnyOf constraints on
categories and states required careful coordination between the Python forms and the frontend interface. The date and time
validation needed to ensure events couldn't be created in the past, though this validation appears to be missing from the
current implementation (a gap I didn't notice until thoroughly reviewing the code).
The database relationship management, while utilising SQLAlchemy's ORM, still required careful
planning to avoid cascading delete issues. The relationships between User, Event,
Booking, and Comment models needed foreign key constraints while ensuring that deleting
an event wouldn't orphan bookings or leave users without reference to their purchase history. The decision to store carousel
image filenames as comma-separated strings in a single database column rather than creating a separate
EventImage table represents a pragmatic but non-normalised approach that simplified implementation at the cost of
database design purity.
URL routing and parameter handling introduced frustration with space-to-hyphen conversions for SEO-friendly URLs. The
view_event route requires four parameters including event_username, event_id,
event_artist_name, and event_name, all of which must be converted from spaces to hyphens in URLs and
converted back for database queries. This created opportunities for bugs, particularly with JavaScript's
replace functionality that had to be invoked inline in onclick handlers.
The real-time ticket availability system required careful transaction management to prevent race conditions. When a user books
tickets, the system must decrement event_available_tickets, add a Booking record, and potentially
update the event status to "Sold Out" (all within a single transaction). The current implementation lacks sophisticated locking
mechanisms, meaning concurrent bookings could theoretically oversell tickets, though this would only manifest under significant
load.
State management across the application, particularly for event statuses, required logic scattered across multiple files. The
event_categorise.py file contains a loop that checks every event's date against the current date and updates
statuses to "Inactive" whenever the filter_events function is called. This approach works but creates unnecessary
database writes and doesn't update statuses until someone visits the events page. A more elegant solution would involve database
views or scheduled background tasks.
The Bad: Technical Debt and Design Flaws
Reflecting honestly on this project reveals several areas where the implementation falls short of production-quality standards.
The security posture of the application presents significant concerns. The secret key is hardcoded directly in the source code
as 'secretkeyofseekmusic', which should instead be stored in environment variables. While I knew this was poor
practice at the time, it was permitted by the tutor for the purposes of the assignment, though it's certainly not something I'd
do in a real-world application. The admin panel at /admin_panel has no authentication or authorisation checks,
meaning any user who discovers the URL can delete any user, event, or comment. The remember me functionality creates a cookie
with a placeholder token 'some_token' that isn't actually validated on subsequent requests, rendering the feature
cosmetic rather than functional.
The code exhibits substantial repetition and violations of the DRY principle. The state abbreviation conversion
logic appears identically in both create_event.py and edit_event.py with lengthy
if-elif chains that should be extracted into a utility function or dictionary mapping. The template files duplicate
the entire navigation bar structure in view-specific-event.html rather than extending it from
base.html, creating maintenance headaches. The diagonal button styling in home.html includes ten
separately defined CSS classes (.diagonal-btn-1 through .diagonal-btn-10) that differ
only minimally and should be parameterised.
Error handling is conspicuously minimal throughout the application. Most routes lack try-except blocks, meaning
database errors, file system issues, or unexpected input could crash the application with exposed stack traces. The file upload
functionality doesn't validate file sizes, potentially allowing users to upload gigabyte-sized images that would exhaust server
storage. The ticket booking system doesn't display user-friendly error messages when attempting to purchase more tickets than
available; it simply prints to console and silently continues.
The database design includes several normalisation violations beyond the comma-separated carousel images. The
Event model duplicates the user's email and phone number from the User model, storing them as
event_email and event_phone_number. If a user updates their contact information, all their existing
events retain outdated information. The event_featured_image column stores relative file paths as strings, tightly
coupling the database to the filesystem structure.
The frontend implementation suffers from inconsistent user experience patterns. Some forms use Flask-WTF rendering
with form.field notation while others manually construct form elements. JavaScript code is embedded
directly in template files rather than being extracted into separate .js files, making debugging and maintenance
difficult. The responsive design works on desktop but several elements don't adapt well to mobile viewports, particularly the
search bar and diagonal carousel tiles.
Performance optimisation is essentially absent from the application. The home.py route queries individual events
with Event.query.filter_by(id=1).first() through Event.query.filter_by(id=10).first(), resulting in
ten separate database queries when a single query with a WHERE id IN (1,2,...,10) clause would suffice. The
filter_events function loads ALL events into memory before filtering them in Python, rather than constructing
SQL WHERE clauses. Images aren't compressed or resized, and there's no lazy loading implementation.
The URL structure for events includes redundant information. The route requires both the event_id and the
event_username, event_artist_name, and event_name, but the ID alone would be sufficient
to uniquely identify an event. This creates longer URLs and multiple valid URLs for the same event, which is problematic for SEO
and bookmarking.
Testing is completely absent from the codebase. There are no unit tests for models, no integration tests for routes, and no
end-to-end tests for user flows. This makes refactoring dangerous and regressions likely. The debug=True flag in
app.py should never be deployed to production, yet there's no environment-based configuration system to change this
behaviour.
The Path Forward: How It Could Be Improved
Transforming Seek Music from a functional prototype into a production-ready platform would require systematic improvements
across multiple dimensions. The security enhancements should be prioritised first. Implementing proper environment variable
management using python-dotenv would allow sensitive configuration like secret keys and database URLs to be stored
securely. The admin panel needs authentication decorators checking for an admin role stored in the User model. The
remember me functionality should generate secure random tokens, store them in a new RememberToken table with
user_id and expiration timestamps, and validate them on each request. Implementing rate limiting with
Flask-Limiter would prevent brute force attacks on login endpoints.
The codebase structure would benefit from a comprehensive refactoring into a more maintainable architecture. Creating a
utils.py module to house the state abbreviation conversion, file extension detection, and other helper functions
would eliminate repetition. Extracting the navigation bar into a separate nav.html template that's included rather
than duplicated would ensure consistency. The static JavaScript should be moved into separate files in the
static/js/ directory with proper module organisation. Implementing a service layer between routes and database
models would separate business logic from HTTP handling, making the code more testable and maintainable.
The database schema deserves a redesign pass. Creating an EventImage table with columns for event_id,
image_type (featured vs carousel), image_path, and display_order would properly normalise
the image storage. Removing the duplicated email and phone number from the Event model and instead accessing them
through the relationship to User would eliminate data inconsistency. Adding database indexes on frequently queried
fields like event_category, event_city, event_date, and event_status would
dramatically improve query performance.
Error handling and validation should be implemented comprehensively. Each route should be wrapped in
try-except blocks catching specific exceptions like SQLAlchemyError, FileNotFoundError,
and ValidationError, with user-friendly flash messages and appropriate HTTP status codes. File uploads should
validate MIME types, restrict file sizes, and scan for malicious content. The ticket booking process should use
database transactions with proper isolation levels and display clear error messages explaining why a booking failed.
The frontend experience needs modernisation to meet contemporary standards. Implementing a proper responsive design system,
perhaps using Bootstrap's grid more consistently or switching to a modern framework like Tailwind CSS,
would ensure mobile compatibility. Extracting JavaScript into modules and implementing a build process with tools
like Webpack would enable better code organisation. Adding loading states, skeleton screens, and optimistic UI
updates would improve perceived performance. Implementing client-side form validation that matches server-side rules would
provide immediate feedback.
Performance optimisation should focus on database query efficiency and asset management. Replacing the N+1 query
patterns with eager loading using SQLAlchemy's joinedload or subqueryload would reduce
database round trips. Implementing pagination for event listings and bookings would prevent loading entire tables into memory.
Adding image resizing with libraries like Pillow to create thumbnail and web-optimised versions would reduce
bandwidth. Implementing caching with Flask-Caching for frequently accessed data like the category list and featured
events would reduce database load.
The booking system needs additional business logic to handle real-world scenarios. Implementing a temporary reservation system
that holds tickets for a user during checkout would prevent them from being sold while someone is completing their purchase.
Adding a refund workflow rather than just cancellation would make the booking status more sophisticated. Implementing email
notifications for booking confirmations, event reminders, and cancellations would improve communication. Adding a payment
gateway integration with Stripe or PayPal would move this from a prototype to a functional
marketplace.
Testing infrastructure should be established as a foundation for future development. Creating pytest fixtures for
test users, events, and bookings would enable comprehensive unit testing. Implementing factory patterns with
Factory Boy would make test data generation simple. Writing integration tests for each route that verify response
codes, database changes, and redirects would catch regressions. Implementing end-to-end tests with Selenium or
Playwright would verify critical user flows like registration, event creation, and booking.
The deployment architecture needs consideration for production environments. Creating separate configuration objects for
development, testing, and production would allow environment-specific settings. Implementing proper logging with Python's
logging module and external services like Sentry would enable debugging production issues. Setting up
a production WSGI server like Gunicorn behind Nginx would provide proper request
handling. Implementing database migrations with Flask-Migrate would allow schema evolution without data loss.
Reflections on the Learning Journey
Building Seek Music provided invaluable insights into the complexities of full-stack web development. The most significant lesson was understanding that feature implementation is only a fraction of building a real application; the majority of effort should go into error handling, security, testing, and performance optimisation. The tendency to prioritise new features over refactoring existing code created technical debt that compounded over time, making later features more difficult to implement cleanly. This project made me realise that product velocity must be balanced with code quality. While the pressure to deliver features quickly is real, especially under tight deadlines, the shortcuts taken to move faster ultimately slow down development as the codebase becomes harder to understand, modify, and debug.
The importance of planning database schemas thoroughly before implementation became evident when relationship management created complications. Starting with a more normalised design would have prevented data duplication and made the application more maintainable. Similarly, establishing coding patterns and conventions at the project's outset rather than evolving them organically would have resulted in more consistent code.
The challenge of balancing feature richness with code quality emerged as a constant tension. The desire to implement carousel tiles, comment sections, and sophisticated booking systems was exciting, but each added feature introduced new surface area for bugs and maintenance burden. A more iterative approach (building a minimal viable product first, then adding features incrementally while maintaining high quality) would have resulted in a more robust application.
The frontend development highlighted the importance of design systems and component libraries. Building everything from scratch
with custom CSS resulted in inconsistent spacing, colours, and interaction patterns. Adopting a design system early
would have accelerated development while ensuring visual consistency. Similarly, the scattered
JavaScript demonstrated the value of frontend frameworks like React or Vue that enforce
component architecture and state management patterns.
Security considerations were consistently underestimated throughout development. Features like authentication, file uploads, and the admin panel were implemented with a focus on functionality, with security as an afterthought. This pattern is dangerous and demonstrated the necessity of integrating security considerations from the first line of code rather than attempting to bolt them on later.
Perhaps most importantly, this project illuminated the gap between "working code" and "production-ready code." Seek Music functions; users can register, create events, book tickets, and leave comments. However, the absence of proper error handling, testing, security hardening, and performance optimisation means it falls far short of what would be acceptable in a professional context. Understanding this gap and learning what it takes to bridge it represents the most valuable outcome of this project.
The experience of building Seek Music transformed theoretical knowledge about web development frameworks, database design, and
user authentication into practical understanding earned through debugging sessions, refactoring cycles, and discovering edge
cases. While the codebase contains numerous flaws and architectural decisions I would change with hindsight, it represents an
authentic learning journey (one where mistakes were made, lessons were learned, and the foundation was laid for building better
applications in the future). Every hardcoded secret key, every N+1 query, and every duplicated code block serves as
a reminder of what not to do next time, making this project an invaluable stepping stone toward mastery of web development.
Throughout this post-mortem, I've discussed numerous improvements that could transform this codebase into production-ready software. While I haven't yet spent time implementing these solutions as I'm currently preoccupied with Valgo (my current project), the lessons learned have fundamentally changed my approach to new projects. Moving forward, I've adapted by committing to research heavily before implementing code. Rather than diving straight into development and discovering issues along the way, I now invest time upfront in understanding architectural patterns, security best practices, and potential pitfalls. This shift from "learn by breaking things" to "learn before building things" represents perhaps the most valuable transformation that emerged from this project's challenges.
