Deep Rapid Django Prototyping

Lightning Fast Scaffolding from the DB to the DOM using Django

Django has long been known as a secure and reliable backend with a robust set of libraries and features built in, but what if I told you with a couple extra packages and a little knowhow it can be the best prototyping framework in the world? Rapidly getting the scaffolding up can be a huge boon to a small team looking to show off a new idea. It means that a front end dev can be consuming actual API endpoints almost the moment the database table is created and the business logic can be added on iteratively later.

With a prototype you want to get off and running fast with functionality right away, often time even before you know what you're building. Business requirements change quickly after stakeholders are able to see and play around with it and you don't want to have wasted lots of time perfecting every serializer and view only to find out only 10% is needed. An approach that prioritizes speed and fast functionality can sometimes be just what you need, especially with a prototype. So let me show you how I, as part of a team of 2, created a deep prototype rapidly.

Laying Groundwork

The first great speed improvement everyone who has followed the basic polls app tutorial from Django knows about are the start-up commands. Getting started with our new app just takes a couple commands. I'm going to sprinkle in some pyenv commands to make sure we are operating on the same python version across the whole team and can easily change versions together, more info here: https://realpython.com/intro-to-pyenv/#virtual-environments-and-pyenv.

# Install Django
$ python -m pip install Django

# Start a new Django project
$ django-admin startproject prototype
$ cd prototype

$ curl https://pyenv.run | bash
$ pyenv install 3.9.0
$ pyenv virtualenv 3.9.0 prototype
$ pyenv local prototype
$ cd prototype
$ python manage.py startapp polls

There we are, now we have the basic structure of out app with all the files we need. Make sure to add these new apps to the installed apps:

INSTALLED_APPS = [
...
    'prototype',
    'prototype.polls'
]

Magic Prototyping Packages

With the setup done let’s add in our magic prototyping packages and after I'll show you how simple it is to go from a DB model to data in the frontend.

If you know Django you almost certainly know Django Rest Framework is an indispensable tool for API creation, but there are a couple tools that can super speed the development. Go ahead and add these lines to a requirements.txt file in the root directory:

Django>=4.0.1,<4.1.0
djangorestframework>=3.13.1,<4.0.0
django-filter>=21.1,<22.0
drf-url-filters>=0.5.1,<1.0.0
drf_writable_nested>=0.6.3,<1.0.0
django-extensions>=3.1.5,<4.0.0

Then run this inside your virtual environment to install all these packages (note: there might be higher versions at the time of you reading this, check the cross compatibility before you just go for the latest):

$ pip install -r requirements.txt

I'll go through what each of these do in a bit more detail when I give an example, but I'll give a quick rundown here. django-filter and drf-url-filters add a super slick way to add fast and easy filtering on your REST endpoints with zero hassle. drf_writable_nested will make complicated nested objects a breese to make editable with almost no upfront work. Finally django-extensions is an “always install” package for me. I find shell_plus a necessary tool in development and there are several other features in there that just ought to be core Django in my opinion.

Now I'll show you a quick example of making a model and getting it CRUD and listable quick. First off here is out model which we are borrowing from the Django tutorial so I can show how to make that even faster and more powerful:

polls/models.py

from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

    def __str__(self):	
        return self.question_text


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='choices')
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

    def __str__(self):
        return self.choice_text

Then these commands to generate the migration for these models and then run them against the database (which I haven't mentioned how to setup but which is quite easy).

$ python manage.py makemigrations polls
$ python manage.py migrate

That’s all right from the tutorial, but now I'll show you how those packages really shine.

polls/serializers.py

from drf_writable_nested import WritableNestedModelSerializer
from rest_framework import serializers
from polls.models import Question, Choice

class ChoiceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Choice
        fields = '__all__'

class QuestionSerializer(WritableNestedModelSerializer):
	choices = ChoiceSerializer(many=True)
    class Meta:
        model = QuestionAddress
        fields = ['choices', 'question_text', 'pub_date']

This file does two things, it creates placeholder passthrough serializers that will just serialize all the fields in the simple case of Choice. For the case of Question which has nested fields we have to make it a WritableNestedModelSerializer and also list the fields manually. But you'll see we get a lot of flexibility with the REST endpoint because of it.

Now onto the views and the urls.

polls/views.py

from filters.mixins import FiltersMixin
from rest_framework import filters, permissions, viewsets

from polls.models import Question, Choice
from polls.serializers import QuestionSerializer, ChoiceSerializer

class QuestionViewSet(FiltersMixin, viewsets.ModelViewSet):
    """
    API endpoint that allows questions to be viewed or edited.
    """
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer
    # One of the next steps after this you'll want to make sure these endpoints are behind authentication
    # permission_classes = [permissions.IsAuthenticated]
    filter_backends = (filters.OrderingFilter,)
    ordering_fields = ('id', 'question_text', 'pub_date')
    ordering = ('-pub_date',)
    filter_mappings = {
        'question_text': 'question_text__icontains',
        'pub_date': 'pub_date_date',
        'pub_date_gte': 'pub_date__gte',
        'pub_date_lte': 'pub_date__lte',
    }


class ChoiceViewSet(FiltersMixin, viewsets.ModelViewSet):
    """
    API endpoint that allows choices to be viewed or edited.
    """
    queryset = Choice.objects.all()
    serializer_class = ChoiceSerializer
    # permission_classes = [permissions.IsAuthenticated]
    filter_backends = (filters.OrderingFilter,)
    ordering_fields = ('id', 'votes')
    ordering = ('-votes',)
    filter_mappings = {
        'question_id': 'question_id',
    }

These views allow for a ton of power and flexibility for the API right out of the box. You can see that we can get a list of questions and filter by the text of the question and the pub_date with a few simple lines. It also allows for an ordering question param to be sent in so we have sorting by question_text and pub_date and a default ordering by descending pub_date. Incidentally because we added WritableNestedModelSerializer to the Question serializer in the last step we can not only edit the question, but all the choices in one POST request to this endpoint. But if you want to list and edit a single choice then we have the view for that too with its own lines for custom filtering and ordering.

The last step is hooking these views up to the urls file which we'll do here:

polls/urls.py

from django.urls import path, include
from rest_framework import routers
from polls.views import QuestionViewSet, ChoiceViewSet

router = routers.DefaultRouter()

router.register(r'question', QuestionViewSet)
router.register(r'choice', ChoiceViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

We used viewsets for our views so behind the scenes the DefaultRouter and the Django and DRF viewsets will take care of all the request types and the routing and we don't have to worry about that at all unless we need to, which in prototyping we usually don't right away.

Conclusion

And that's it, you're up and running with models in the database that can be edited from the frontend via rest endpoints and you have all the right structure to start laying in the fine details, business logic, security, and limits on field visibility. I hope this helps you get off the ground fast and get your prototype in front of eyes rapidly. Then once you know what works and what doesn't you can fill out the rest based on this loose, powerful, and flexible scaffolding.

BONUS:

As another speed improvement to this if it's applicable, check out this blogpost about generating a ton of fake test data to put in your DB to play around with and display on your frontend. Factory Boy and Faker are great assets for Unit Testing, but can be used to a great result in setting up quick data for a prototype. https://mattsegal.dev/django-factoryboy-dummy-data.html

The JBS Quick Launch Lab

Free Qualified Assessment

Quantify what it will take to implement your next big idea!

Our assessment session will deliver tangible timelines, costs, high-level requirements, and recommend architectures that will work best. Let JBS prove to you and your team why over 24 years of experience matters.

Get Your Assessment