Fighting Homelessness with Django with Benjamin "Zags" Zagorsky
Published December 6, 2024
This video features Benjamin "Zags" Zagorsky at DjangoCon US 2021 in Online.
Building a prototype in a weekend is the dream of startups and established businesses alike. But if you cut corners and the product takes off, you will be stuck with your mistakes for years. This talk will show you the tools and techniques to build a minimal-regrets rapid prototype in Django.
This talk was presented at: https://2021.djangocon.us/talks/rapid-prototyping-in-django/
LINKS:
Follow Benjamin "Zags" Zagorsky 👇
On GitHub: https://github.com/zags
Website: https://zagaran.com
Follow DjangCon US 👇
https://twitter.com/djangocon
Follow DEFNA 👇
https://twitter.com/defnado
https://www.defna.org/
Video production by the speaker and DjangoCon US 2021 Volunteers.
Rapid prototyping should aim to validate a product quickly without creating a foundation that becomes painful if the idea succeeds. The speaker recommends researching existing products, narrowing the MVP to its essential value, mapping the data model and near-term roadmap, and choosing tools that reduce code, including Django Forms and ModelForms, Crispy Forms, template inheritance, messages, environment-based settings, Django Storages, OAuth libraries, and Django Extensions. He suggests postponing nonessential styling, JavaScript complexity, migrations before launch, elaborate object permissions, and over-customized admin interfaces, while warning that SQLite should be tested against PostgreSQL before production. He argues that some foundations should not be skipped: managed hosting, databases, and file storage; a swappable user model; shared model timestamps and possibly UUIDs; and database constraints that enforce assumptions early.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Hello DjangoCon. Today we're going to talk about rabbit prototyping in Django, or how to build a website in a weekend and not regret it later. So first, who am I? I'm Zags. As you can see, that's short for Benjamin, and I'm the Chief Technology Officer at Zagoran. My company, Zagaran, is a contract software company. It's named after its founders, of which I am one, and we do full-stack web and mobile software development Now, at the heart of most of our systems is a Django backend, which means I have seen dozens of Django projects and I have seen things that have worked well across many Django projects. On top of that, most of our projects start with a rapid prototype of some sort, either because that is the deliverable that's needed, getting something out there
the door quickly or because that's just good software development practice is getting something up for testing fast while we're building out the rest of the system. What I want to do today is share with you some of the tips and tricks that I've learned over the years for how to do a rapid prototype well in Django. Noting that many of these are also useful tools that can be used on mature Django projects as well. Before we get into it, I want to motivate this talk a little more. Obviously rapid prototyping is important when you want to get something out the door fast. I want to make the case that that's all the time. The reason that's all the time is because time is limited. You have many ideas, not all of which will work, and all of them have opportunity cost.
Anything you do takes away from known ways of making money. If you're an established company trying out a new product, that new thing is taking away from focusing on your core business. And if you're an entrepreneur starting a new company, that new company is coming at the expense of you taking a salaried position at an established company. So with all of these things, you want to take the lean startup approach, even if you're not a startup. That is building an MVP, a minimum viable product, getting it out as fast as possible, and testing it. So that if it doesn't work, you find out fast. This is known as failing fast. But the gotcha is if it does work. You also want to not regret what you've built because what you've built is going to become the foundation for a product that you may end up working on for years.
So the goal is to build a website in a weekend so that if it doesn't work, you haven't lost very much, but also not regret it later in case it does. This talk is going to have four sections. First, things to do before you start. It's very tempting to just dive in and start writing code, but it's important to do some planning before you do that. Second, key tools. These are tools in the Python and Django ecosystem that are going to give you enormous leverage in terms of building your product very quickly. Number three, corners to cut. These are things that matter, but not right away. These are things that are not good uses of your time initially and you shouldn't be doing unless you absolutely have to. And then finally, regrets to avoid.
These are the things that you might be tempted to skimp on, but you really shouldn't. Before you start, you need to know what you're going to build ahead of time. Having a clear product vision is essential to you building a coherent product. One of the first things you should do when you have a product idea is to do user and market research. Odds are something like your idea already exists. And what you need to figure out is what are the meaningful differentiators between what you're planning to build and the things that already exist. You need to find those things that are going to provide enough value for users that they want to use your product instead of only the existing products because those are the defining features. That's what makes your product worth building.
So to take a real world example, there is a website Blackle. It is Google, but with a black background on the search box, and then kicks you off immediately to a custom Google search. Is that a meaningful difference? That black background on the search box? Only research can tell. Another thing to think about when planning your product is how to pare down the scope as much as possible. The M in MVP stands for minimal, not maximal. When you look at your initial feature list, you want to only consider the features that are necessary for proving the value of your product. There may be other features that are good and would add value, but aren't necessary to actually prove the product, in which case those should get put off for later. Beyond that, you should even consider the form factor of what you're building.
I can tell you from experience that a website is much more complicated than a script, and an app more complicated yet. See if there's a way to reduce the form factor so that you don't have to deal with things like the app store, servers, databases Any of those things that aren't immediately necessary for proving the value proposition of your product should get put off for later. Next, you want to map out your product. That means both figuring out the details of what you're building now and what you're going to build soon Because in order to build a no regrets prototype, you need your foundations to be able to support your product roadmap, at least short term, if not long term. You want to think about your users and how they're going to interact with your application. You want to plan your database structure.
Think about what data you want to store and how it relates to each other. And most importantly, you want to pick the technologies that you're going to use as the foundations for your product. This can be both Python frameworks and libraries like Django itself, as well as third-party services With any technology, the thing to look at is how will this save me time? Either now or later, or ideally both. This talk has a number of recommendations for technologies that will do just that. Let's do an example. In this example, we're going to build a to-do list application. Now we've done our market research and found a number of to-do list applications already exist. Our core differentiator is going to be that users can create tasks for the future, either for some fixed point in time in the future
or recurring tasks that show up every specified number of days. Our initial task list is devoted entirely to proving this value. So we have these two tasks, the two things I just mentioned, that are our core value, and a number of other tasks necessary to support that. Editing those scheduled tasks, having a to-do list that those scheduled tasks get inserted to, and basic login. Everything else goes in the backlog. But the backlog is important to have defined so that we can think about how it impacts our design now. If we long term want this to be an enterprise application, we should have an authentication aggregator so it's easy to add other types of logins. If we want users to be able to see deleted tasks, we should do soft deletion of tasks now so those tasks still exist
when we implement that feature later. Finally, if we want users to be able to share tasks with other users, that's something really important to take into account into our data modeling and our permissioning scheme. As a final note on planning, getting your data model right is really important. As I like to say, interface follows from database All of your code is built assuming a data model. And if you have to change that data model after you've built a lot of code on top of it, that's going to be a pretty expensive change. So planning your data model up front. And being able to think through how you're going to access it and debug that early can save you a lot of code changes later. This is my favorite planning resource for doing that, an entity relationship diagram or ERD for short. In this each box is a database model, which Django implements as a SQL table,
and these connectors are the relationships between your models, the many-to-ones, many-to-manys, or optional many-to-one relationships. For example, this connection between user and task says a user has many tasks, which you would implement by having a foreign key go from task to user. Building one of these diagrams can be a really helpful planning tool to save you a bunch of code rewrites later. With the planning done, it's tempting to just dive in and start writing code. But again, I'm gonna advocate doing the opposite. Discipline is key to both speed and quality. The less code that you have to write The faster you'll be done and the better your final product will be. You should be actively searching for tools or libraries that reduce the amount of code you have to write.
So now we're going to look at a set of tools that do just that. We're going to start with one of the highest leverage tools, Django Forms. Django forms go a long way to eliminating the boilerplate of rendering, validating, and saving HTML forms. A Django form is a backend class where you define your inputs, your validation rules, which can be defined inline on the inputs or as standalone functions. and your save function, what you do with the data that's been submitted. Having done that, a Django form can render the form on your front end for you. It can parse the data past to the back end for you. It can run the validation rules that you've defined, and then either re-render the form with validation errors on the data that was submitted if validation fails, or save the data if validation succeeds.
To illustrate the code saving power of Django Forms, we're going to do an example of what the back end and front end look like pre and post-Django Forms. To start with the backend, here we have a typical function-based view for parsing, validating, and saving form input. So in the event that the user has posted the form, we want to extract the parameters from the POST request. We want to run validation. This is just basic validation here, just checking if required fields have been passed. And if validation fails, send an error to the user. If validation passes, create the object, notify the user that the object was successfully created, and finally render the page either in the event that the object was created successfully or in the case that they're loading the form in the first place
With Django Forms, our backend is much simpler because Django Forms takes care of so much of it for us. This line here. is extracting the data from our post parameters. Django Forms knows what parameters it's looking for and it goes through the post payload and pulls the data out for us. This line here runs form validation. All you have to do is call form is valid, and Django Forms runs all of its validation rules and returns a Boolean, true or false, as to whether or not the payload passes. If the form is valid, calling form is save, saves the data to the database. That's all you have to do with Django Forms. On top of that, this version of code actually has more functionality than the previous version. In particular, Django's form validation is much more robust.
In the previous version, all we were doing was checking whether or not required fields had been passed in. In this version, even with just the simple example form I showed you, Django Forms is also going to check for length limits on character fields and whether or not date fields are valid dates. If we want more than that, it's very simple to add extra validation rules to a Django form, and it's no more code with Django forms than it would be without them. On the front end, we're gonna see similar savings. This is what it looks like to render a form without Django Forms In addition to our form tag, CSRF token and input, we have a label and input for every single form field as well as the ability to pass in a value from the backend
in the event that the form fails validation or that the user is editing an existing database object. Much like the back end, Django Forms is going to give us huge code savings on the front end. This line, this one line, form as p renders every single input label pair for the form as well as fills in those optional initial values from the backend And that's not all. Django Forms also gives us inline error rendering for free. So rather than having your errors go at the top of the page, field-specific errors can go right next to the offending field. What that means is that if somebody misses a required field or they fill in a value that's too long, they'll get an error message right next to that field telling them that's the one that's the problem.
There is one thing I don't want to glaze over here. Which is that this form as p is going to render our form completely unstyled. As p means that every input label pair gets put in an HTML paragraph element, and there's no other styling given to the form. So that's what we're gonna look at fixing. To fix styling, we're gonna use a third-party library called Django Crispy Forms. Django Crispy Forms can be installed with PIP and requires very few settings to be effective. The key one here is the Crispy template pack, which says which of the various template packs do you want to use to style your form? In this case, we're using Bootstrap 4. And that setting alone, along with a crispy template tag, is going to let our forms be styled with Bootstrap.
So that's the styling problem fix. But Crispy Forms doesn't stop there. Crispy Forms also lets you embed a number of other aspects of form behavior and layout into the form class itself. So for example here we can add the submit button to the form definition. We can add the form action and method to the form definition. As part of this Crispy Form helper, you can also add things like field sets and other aspects of complex layout. Having done that, our front end goes away almost entirely. This single call to CRISPY Form will render not just the form, its inputs, and its labels, but also the form tag itself. the CSRF token and the submit button because all of those are now backend specified. And on top of that, the whole reason we use CRISPYforms in the first place, our form will be formatted with beautiful bootstrap
Django model forms takes eliminating boilerplate a step further. Using a model form, you can have Django Forms auto-generate a form, its fields, and its save method for you from one of your database models. All you have to do is specify which database model you want it to use and which fields on that model you want it to use. And then Django Forms will go generate in this case a char field based on the database field name, a date field based on the database field due date. as well as a save method that will go and either create or update an instance of this database model based on whether or not an instance was passed into the form. Beyond that, this is a class. So if you want to change various aspects of the auto-generated functionality, you can do that using simple object-oriented programming
and overriding. any of the methods or properties that you want to customize. Let's move on from forms now and talk about frontend One of the big principles in good programming is dry, or don't repeat yourself. The thinking behind dry is that repeated code is both error-prone and unmaintainable. Now HTML frontends are a place that definitely have repeated code. You're going to have assets like CSS and JavaScript libraries. that are going to be used across all of the pages of your website. And you're going to want things like a header with a top bar with links and a logo used on all of your pages. Having a base template is a great way to put all of those things in one place.
And then your specific pages can just go implement the blocks, those blanks that you've created in your base template that are page specific. This inheritance doesn't have to stop at two tiers. You can have a base template for your site, several children template off of that, each one of which is extended by a group of pages with similar layout. Having implemented a base template, we can then use other cool features of Django like Django messages. With Django messages, we can send notifications to the user, either of successes or errors. from deep in our back end to the front end without needing to pass them all the way up through the call stack. So notice here that we aren't doing anything with the return value of this function.
and that the message being passed is not actually in the context we're passing to our template, and yet it's displayed anyways. The way that this works is that Django, when you send a message, adds it to the user's session. There's then a middleware that goes through those messages and adds them to the context of the next template you render, whatever template that happens to be. So the best way to use Django messages is to render these messages on every single template, which is really easy to do if you have a base template. One final note, these messages don't have to be sent within a view function like this one here. This can be much further down in the call stack, such as in a method off of the form
or in several methods deep in your backend. And where would we be without Bootstrap formatting? If you're using Bootstrap for your site, all you have to do is add this payload to your settings, mapping Django message types to Bootstrap alert classes, and your messages will be formatted with this. bootstrap. Let's move on to Django settings. Django Settings is a great aspect of the framework because it gives you a single place where you can configure runtime options for both Django itself and a number of libraries in the Django ecosystem. system. The downside to Django settings is that there's a number of settings that you frequently don't want hard-coded. And that could be either because you want those settings to be configurable, let's take debug as an example.
You want debug to be on for local development because that's really convenient. But you definitely don't want debug on for your production deployment. Or alternatively, there are Django settings that you could not want hard coded because they contain sensitive data. That's things like your database password or API keys. But you don't want to commit to your repository. You don't want those committed to git because once they're part of Git, they're part of Git's history. They're there forever, and you lose control over those secret values. The general solutions to both configurable values and secret values are the same. You either have a file that contains configurable and secret values, or you use environment variables. You may also want to do both. Having a file is convenient for local development, whereas using environment variables tends to be supported by a number of server environments.
You also probably want to have type wrangling for some of these things if you're trying to configure Booleans You're gonna need to wrangle an environment variable or a file payload, which is fundamentally a string, into a Boolean, and also defaults. Django Environ is a third-party library that does all of this for you. It has support for both configuration files and environment variables. It looks for a configuration file that lives in the same directory as your settings. py file when you call read env, if it doesn't find one of those. It's going to go use environment variables, and it also lets you configure types and defaults for any of these configurable parameters. And you can then go configure things like debug configurable parameters. as well as secrets like your secret key or your database password without checking those things into Git and having them be different for different deployments.
The best practice when using Django Environ is to create a. en file locally that has your secrets, add that to your git ignore so that it doesn't get committed to git. But then do commit to get an example. n file showing the layout and containing any non-sensitive examples that you might want another developer to use for local development. Let's run an example. This example will both show off the power of Django Environ while also showcasing a great tool if you need file storage as part of your prototype. Django comes with a built-in way to store files. The file field as part of your database. Now a file field is a way to store files in your database. Now I say store in quotes because it doesn't actually store the files to the database
It just stores in the database the path to the file and then stores the file itself in what Django calls a file storage backend. Django ships with a file storage backend that saves files to the local file system on which your system is running. This is really convenient for local development, but not at all what you want for server deployments. There are three problems with this on a server. First, servers are ephemeral, and if your server dies, your files are gone. Second, server file storage isn't scalable. If your server runs out of disk space, it's just down. Third, those files aren't shareable between servers. If you upload files to one server, another server can't access those files by default unless you build a way to make that happen.
The way to fix this is to use a technology called block storage, something like Amazon S3, which is durable. Files saved to S3 have an incredibly high retention guarantee. Scalable, S3 will store as many files as you want, and they just charge you for the volume you're storing. And shareable, files saved to an S3 bucket are trivially shareable across servers. Django Storages is a third-party library that comes with a drop-in replacement for the Django storage backend that will save your files to an S3 bucket. To use it, all you need to do is configure an Amazon API key pair and what bucket you want to save those files to. So here's where Django Environ
really shines. You don't want to commit your Amazon API keys to Git. As we mentioned, that's really bad from a security standpoint. So you can make those configurable. Your bucket, you may want to be different for different deployments. You can have a different bucket for production versus staging. Again, make that configurable. And finally. You might only want to use Django storages for your servers and not for local development. If you switch all of that in your settings based on whether or not debug is true and debug is a configurable setting, you can do all of that, and Django Environ makes it really easy Another technology that really benefits from configurable secrets is OAuth. OAuth is the technology powering the login with Google and login with Facebook buttons that you may have seen on a number of other sites. It's a technology that lets users create accounts and log into your site without needing a username and password.
And the third-party service is responsible for authenticating them. If you are building username and password for your site, forget about OAuth until post-MVP. That's a nice to have. And instead, you should use Django's built-in functionality that makes creating users, logging in, and resetting passwords very convenient. However, that I've found with the right library, OAuth can actually be even easier than doing all of that, especially because you don't need to manage the reset password workflow. That right library for me is social off app Django. I don't love the name, but I love what it does. And what it does is with only a handful of lines to your settings. And one line to your base URLs file allows you to add a button very simply to your site to let users log in with, say, Google.
The last tool I want to cover is Django Extensions. Django Extensions is a third-party library that doesn't modify your code, but does give you a couple extra really helpful management commands. In particular, Shell Plus and RunServer Plus, which replaces, as you might expect, Shell and RunServer respectively. Shell Plus is a much better version of Django Shell because when you launch it, it automatically imports all of your database models into the shell, saving you tons of typing on import statements over the lifetime of a project. Run Server Plus, on the other hand, gives you an enhanced error page when you hit a 500 error. When you have debug on with Django and you hit a 500 error, it gives you a stack trace right there on the screen. This is really convenient and also why you definitely don't want debug on on
production. However, the stack trace doesn't tell you you everything and if you need more context or you want to run another experiment you typically have to edit your code maybe add some print statements reload the page etc Run server plus gives you an interactive terminal on every single line of that stack trace right there in your browser and you can in your browser go run whatever Python code you want to inspect variables and run functions and help debug that crash faster. In the spirit of writing less code, it's important to examine what things can you avoid entirely. The previous section was about ways to build the things you know you need to with less code. This section is going to be about things that you should consider not doing at all.
Now this section is not universal. Depending on your product, you may need some of these, but all of these should be things that you put pressure on during product planning. The first thing I'd advocate skipping or at least skimping on is style. That's not to say style isn't important. It's just to say style probably isn't essential to your product. If your product is sufficiently compelling, early adopters will be willing to overlook quite a bit to use it. And even if you do want to style, give it some brand, the 80% solution, the good enough, is much easier than the 100% solution, the looks perfect. We've already done some examples for how Bootstrap can add styling to make it look good enough very easily.
And to add in brand, some generic customizations can get added to all of your pages very easily using a base template. Then later, once your product is proven, you can bring in fancier styling. And the less that you've done and the fewer custom layouts you need to preserve, the easier that fancier styling will be to do. Another thing that I'm going to advocate skimping on is JavaScript. If your application is largely data input and data display, you don't need that much front-end dynamism. That's not to say you don't want it. That's not to say that a single-page JavaScript app, say something in React, wouldn't give you a better user experience. That's just to say that it's not necessary to prove the value of your product.
If you do use React to build your front end, you have to go implement form rendering, form error display. injecting your form with variables from the back end. All of those things that Django Forms and Django Crispy Forms will take care of for us. By skipping that, we can instead rely on Django Forms and Django Crispy Forms to auto-generate the bulk of our front end. And then there isn't all that much front end there if we want to replace that later with a single page JavaScript app. There's very little sunk cost. Looking at the back end, it's also not committing us very hard to an HTML-based front end to use Django Forms. Django Rest framework and its serializers have a very similar interface to Django Forms, and so switching from one to the other, if you want to move from an HTML-based frontend to a JavaScript-based frontend, is fairly simple.
Another corner to cut is not using Django Migrations before you go live. Once you go live, Django migrations are an incredible tool. Django migrations allow you to track schema changes to your database and which databases those have been applied to and apply those safely as part of deployment, as part of local development. Phenomenal tool, one of the best parts of Django. But pre-launch, you don't have a production database whose state and schema you need to maintain. So don't waste time trying to maintain that. Pre-launch you may be making dramatic database changes, in which case make them throughout your database, throughout your migrations, start over. And then, before you go live, delete all of your migrations and remake them
so that you're starting with a clean migration slate and you're not burdened with all of the database changes you've been making during initial development and then once you are alive use Django migrations extensively next I'm going to suggest that you skimp on permissioning Now that's not to say that you should skimp on authentication. Authentication is important. Making sure your users are logged in is important. Making sure your users are authenticated on every page is important, and Django comes with great built-ins. authentication mix-ins or decorators that you can use to do that. Django also comes with great built-ins if you want to do role-based authentication. If you want to say my users have three different roles and you need to have this particular role to access this page, Django's got great things to do that. What I'm talking about here is object-based permissioning.
So for example, if we have a task, it's owned by a user and we want to check on every page. This user owns this task in order to be able to access the page. That's object-based permissioning. And there's a number of libraries that you can install and configure to do this for you. But they're complicated and there's a lot of different ways to configure object-based permissioning, and you don't necessarily know which one is going to be appropriate for your use case up front. So as a result, I would advocate skip that. Do something simple. Make a class method that's going to go check if an object exists. if a user has permission for it. And just use this class method every time you go to get a task and it'll check permissions for you. Not that much code to get started and not that much code that you have to replace when it's actually
Time to move to a proper library and you understand what your permissions model needs to be. My next recommendation is going to come with a warning. The Django admin is a great tool for some things, but not everything. The Django Admin is a Django auto-generated GUI on top of your database. For those of you who've been programming for a while, it harkens back to PHP MyAdmin. It's a great way for you to access your database to create or edit objects in the database without needing to go connect a Postgres or Python shell to the database directly. But it is raw database access. There's nothing in between the admin and the database and anything you save will get sent to the database directly. So this is really convenient for technical and semi-technical users, and it can be much faster to set up than a full create read
update interface for models that only need to be accessed by that type of user. But it is raw access, so that's not good for non-technical users because they won't understand why certain fields shouldn't be touched or why the thing that they put in a specific field has unintended consequences. The other big downside of the Django admin is that it can be customized. That's a really weird thing to have as a downside. The reason it's a downside is because customizing the Django admin is hard. And you rapidly hit the point at which it would have been less effort for you to just build the interface yourself from scratch rather than try and customize it through the Django admin. So I highly recommend
if you're using the Django admin, do not customize it beyond specifying which models and which fields should show up. At the point at which you want to do more than that, go build that interface from scratch. Using SQLite locally is a trick that I love mostly because I hate installing and configuring Postgres. It's obnoxious. Let's take a step back and talk about what this even means. Django interacts with the database on a fairly abstract level. You have your models, you have your queries, all of those are getting translated to SQL dynamically by Django. And which database engine you use is something you configure in settings. Now, if you're using Django Environ in settings and configuring your database dynamically from your environment variables or your environment file,
Changing databases as simple as changing an environment parameter, your database URL. There's not actually any code that you need to do to use SQLite locally and Postgres in production. You can just do that by configuring the database URL differently for each. Now the advantage of this is that the SQLite database is just a file. So you can backup and restore your database much easier. You just copy that file. You can delete your database if, for example, you're making dramatic schema changes and Throwing away your migrations, you can just delete the file. Downsides is that SQLite is not Postgres in a couple key ways. It doesn't enforce limits on char fields. So There are potentially bugs that you'll only see once you actually make it to staging or production if you're using SQLite locally. It doesn't have identical schema change behavior to Postgres, so for migrations you need to test those on Postgres before actually pushing those to production.
and SQLite isn't compatible with some Postgres specific features like JSON fields. So SQLite can be great for early development, just help you get up and started faster. But once you actually get into a mature production environment, make sure that you are testing your changes on Postgres before pushing them to production because of these reasons. That's all we're going to cover on things that you should cut. There's plenty more that you could cut, but the rest of that list are things that you need to discover for yourself for your project. We're going to finish with things that you shouldn't cut because if you do, you almost certainly regret it later. First up, I want to talk about servers and hosting. It's very tempting when setting up your production environment to just spin up a virtual server, say on Amazon EC2, and then put everything on it.
Put your code, put your database, put your files, because that's what you did for local development. And it's simple. It appears to work. And it does work, at least temporarily. The problem is that disaster can strike at any point. We already talked about why storing your files on the server is a bad idea, why you want to use a block storage system like Amazon S3, but all of the same arguments apply to your database storage as well. You want a database storage technology that's durable, that's scalable, and that can be accessed by multiple servers. Managed database technologies like Amazon's RDS do just that. Amazon RDS will backup your database for you, it will scale your database storage when you run out, and an Amazon RDS instance can be easily accessed by multiple servers.
On the server side, I strongly recommend using platform as a service, something like Elastic Beanstalk or Heroku, for the same durability and scalability reasons we've talked about. With platform as a service, if a server dies, it'll spin up a new one. And if you need to scale, have more application servers, that's a simple configuration change. On top of this, using managed services for hosting gives you a number of security benefits. We'll talk a bit more about security later, but the two points that I want to bring up now are that platform as a service makes system updates much easier to install. can be done automatically and using managed database and file storage allows you to encrypt your files by just checking a box. Overriding the Django user model
is something that Django themselves actually recommend doing. The reason to do this is so that you can customize the user model ever. And I say ever because it is very hard to switch from the default Django user model to a custom Django user model once you are live. It's very challenging from a database migration standpoint. So even if you don't want to customize the user model right now, overriding the user model like this means that you have a custom user model that looks exactly like the Django default user model but still leaves you the flexibility to change or add more fields later. Speaking of databases, I highly recommend having a base database model. A base model is one that has this meta abstract true, which means Django does not actually instantiate this as a table itself.
This is merely used for inheritance. And having this class be the parent of all of your other models, so having your other models descend from timestamp model instead of Django model. This in and of itself is just a convenience. The reason to have a base database model is because of these two things that you want to have on all of your models. So, first of all, This created on and updated on timestamps. These are really helpful for debugging and reporting. And it's important to have them in from the beginning because if you add them later. . You're gonna be missing that created on and updated on data from before you added these fields. So having these in from the beginning, not very expensive and can be really useful. The other recommendation I have here for things that you want on all of your models is a little more controversial. This is not necessarily for every project.
I think most projects would benefit from it, and that is having a UUID primary key instead of an auto-incrementing integer. UUIDs are better, especially if you're going to be exposing your data to users. So if you have a UUID, it's much safer to have the ID embedded in a URL or exposed through an API. But it is slightly less convenient in some cases, especially if you need to type an ID into the terminal, for example, fairly frequently. I think overall the benefits of a UUID outweigh the downsides Database constraints are important because the principle of fail early applies to both software development and business development. You have assumptions about your data and you've written application logic based on those assumptions. If you have data that doesn't meet those assumptions coming into your database, you want to fail when
that data is hitting your database. Right then so that you can know where that data is coming from and debug that source of data, figure out why that data isn't valid. If instead you let that data into your database, your application is gonna fail later at some point in the future and it's gonna be much harder to debug. So you want to have the assumptions of your data baked into your database so that your database can help enforce them for you and not just have you rely on your own application logic. The big three that I'm going to recommend are no false, should be your default. Only set no true if your data is actually knowable. Foreign keys protect as a default. You don't want your data getting deleted in a cascading delete by accident. Only set cascade if that's actually the behavior you want. And finally, unique together constraints.
If you have a table where rows are unique based on some pair or triplet of columns, add a unique together constraint. Enforce your assumptions in the database. Requirements. You have other libraries that your code depends on. For example, Django, and hopefully several of the other libraries I've recommended to you in this talk. It's important to have those documented for a healthy code base so that you can do proper deployment onto production servers, so you can onboard other developers onto your project at some point, etc. Python has a way to do this. There is a Python format for a file called requirements. txt that Pip understands. It can install and update your requirements in bulk from this file if you formatted it properly. That's the easy part. The hard part is
a good requirements. txt file will actually have the full versions of all of your libraries specified so that updates to those libraries don't suddenly break your production server without you having changed anything. That's a hassle to maintain, so I would recommend here having the library pip tools do it for you. When you use pip tools, you write a requirements. in file where you just specify here are the libraries that I want to use. You then run pip compile and pip tools will go generate the fully specified version requirements. txt file for you. Error handling is really important to have in from the beginning. Because you need to know if your site is breaking, how it's breaking, so that you can fix it. If your site is external facing, you need to know proactively that your site is broken so that you can fix it and stop alienating your users.
And even if your site is internal facing, good error handling is going to make that debugging process much easier for you. I recommend Sentry because it's this easy to install and it'll send you email alerts when an error, a new error shows up, and contains a ton of extra useful information along with the stack trace such as the variables at each layer in the stack. trace. Security is important from the beginning because data breaches can happen at any time. I can tell you that when we launch a new website, bots are crawling that site within hours looking for vulnerabilities. Security is a whole topic unto itself, so I'm just gonna give you my top six recommendations. Number one, have SSL from the start. There are free ways to get SSL certificates now, like Amazon Certificate Manager or Let's Encrypt.
So there's not really any excuse to not have this, and it's essential for protecting your user data in transit. Number two, don't commit secrets to Git. The problem with doing this is that they're then part of the Git history forever, and I've heard of data breaches happening because secrets made it into a Git repository. There's much better ways of handling secrets, which we covered earlier, such as Django Environ. Number three, make sure Django's CSRF protection is enabled. It is by default when you generate a new Django project. And don't modify data through GET requests, through HTTP GET requests. The reason for that is because GET requests are not covered by Django's CSRF protection. Number four, don't use RawSQL. Django has a great ORM. Raw SQL is unmaintainable
in general, and SQL injection attacks have been one of the biggest security vulnerabilities in the web for its entire history So with an LRM as good as Django's, there's not really any reason to use RawSQL. I'd recommend not. Number five, install updates automatically. on your servers and on your databases. This is very easy if you're using platform as a service and managed databases. And this is also really important because vulnerabilities in those packages Can cause your server to be vulnerable even if your code is perfect. Not installing server updates is how Equifax got breached several years ago. which was one of the largest data breaches in human history and caused about half of America's social security numbers to get leaked to hackers. So don't be Equifax, update your servers.
Finally, encrypt your database and file storage. If you're using managed databases and block storage, this is just a checkbox and really improves your your security by having those things encrypted at rest. So now let's take a step back and assume that you've finished. You've built your prototype. What next? Put it out in the world. Get people using it and determine whether or not it does what you want, whether or not it is as valuable as you expect. And if it is, if your project is a success, then from there you can do iterative development focusing on the things that will add more value to your product. The two things that I want to call out that might not be obvious is first, if your data model is wrong, change it early. I hope that you put upfront planning into your data model, but there will still be things that you didn't get right.
And changing a data model gets harder the more complex a project becomes. So the earlier you make those changes, the happier you will be given your product is successful. And then second. There may be corners that you cut in development, such as some of the ones that I recommended, that you need to put back on if your product is successful. Those are only things that matter if your product's actually getting traction, getting users. But once it is Then is the time to put those corners back on. To help you implement some of these suggestions, I invite you to use Zagoran's own Django project template. It's open source and available on GitHub here. That's github. com slash Zagaran slash sample Django app. And it comes with configuration for many of the technologies I've talked about in this talk, such as this list here.
If you're starting a new project, feel free to fork it. That's what we do for almost all of our new projects. And even if you have an existing project, it can be useful to look at this as a reference. I personally find it's much easier when trying to use a new technology to look at a project that's already used it as a reference. That's all I have for you today. If you have any questions or if you want to hire Contract Software, my email is here. I look forward to hearing from you. And to all of you, I want to thank you for joining me for this talk.
Research existing products and identify a meaningful differentiator, reduce the MVP to only what proves the product’s value, map the short-term roadmap, and choose technologies that save time. The speaker also recommends planning the data model early, ideally with an entity-relationship diagram.
Discussed at 3:38An ERD helps you plan models and their relationships before building on top of them. Since changing the data model later can require extensive code changes, mapping it early can prevent expensive rewrites.
Discussed at 7:30Django Forms can render fields, extract submitted data, validate it, display errors, and save valid data. They provide more robust validation than manually checking required fields, while reducing both backend and frontend code.
Discussed at 9:11Django Crispy Forms can render a form with Bootstrap styling using a small amount of configuration and a template tag. The form class can also define the action, method, submit button, and more complex layout, leaving very little template code.
Discussed at 13:09A ModelForm generates form fields and a save method from a Django model, including support for creating or updating model instances. You specify the model and fields, then override generated methods or properties when customization is needed.
Discussed at 14:49Django stores a message in the user’s session, and middleware adds it to the context of the next rendered template. Rendering messages in a base template makes success and error notifications available throughout the site, including messages created deep in backend code.
Discussed at 16:22Keep configurable values and secrets out of source control by using a local configuration file, environment variables, or both. Django Environ supports these sources along with type conversion and defaults; a local `.env` should be ignored by Git, while an example file can document the required settings.
Discussed at 18:38Local server storage is ephemeral, difficult to scale, and not automatically shared across multiple servers. A service such as Amazon S3 provides durable, scalable, shareable storage, and Django Storages offers a drop-in backend for it.
Discussed at 21:08OAuth services let users sign in without your application managing usernames, passwords, or password resets. With Social Auth App Django, adding an OAuth provider can require only a handful of settings changes and a URL configuration line, although the speaker recommends postponing OAuth until after the MVP if basic authentication is sufficient.
Discussed at 23:29Its `shell_plus` command automatically imports database models, reducing repetitive imports. `runserver_plus` provides an interactive terminal alongside traceback lines, making it easier to inspect variables and debug errors.
Discussed at 24:18The speaker recommends initially minimizing visual polish, JavaScript complexity, pre-launch migration history, and sophisticated object-level permission libraries. Bootstrap, server-rendered Django Forms, simple permission checks, and rebuilding migrations before launch can provide a faster path without much sunk cost.
Discussed at 25:52Before launch, when the schema may change dramatically and there is no production database to preserve, the speaker says migrations can be treated as disposable. Before going live, delete and recreate them for a clean starting slate; after launch, use migrations extensively.
Discussed at 28:18The admin is useful for technical or semi-technical users who need direct database access, but it is unsuitable for nontechnical users. The speaker recommends limiting customization to choosing models and fields; beyond that, a custom interface may require less effort.
Discussed at 30:39Yes. If the database URL is configurable, you can use SQLite locally and PostgreSQL in production without code changes, making early development and database resets easier. However, you should test against PostgreSQL before deployment because SQLite differs in field limits, schema behavior, and PostgreSQL-specific features.
Discussed at 33:03Use managed services: durable object or block storage such as Amazon S3 for files, a managed database such as Amazon RDS, and a platform-as-a-service host such as Heroku or Elastic Beanstalk. These services improve durability, backups, scalability, multi-server access, updates, and security.
Discussed at 34:43Django recommends overriding the user model early because switching from the default user model after going live is difficult from a database-migration perspective. A custom model can initially match Django’s default while preserving the option to add or change fields later.
Discussed at 36:20Constraints make invalid data fail when it reaches the database, where its source is easier to identify and debug, rather than allowing a later application failure. The speaker highlights non-null fields by default, protective foreign keys unless cascading is intended, and uniqueness constraints as important safeguards.
Discussed at 38:43Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 14, 2026