A step-by-step guide to creating a Django project from scratch with Pinax starter projects and apps.


This post documents my step-by-step creation of cloudspotting, a demonstration project built with the Pinax ecosystem. I show the ease of building a full-featured application using Pinax starter projects and apps. Links throughout this text provide git repository source and commit references on GitHub, including goofs I made along the way.

The idea for cloudspotting is simple: Users create cloud image collections organized by cloud type, i.e. "cumulonimbus" and "cirrus". Each cloudspotting user can view everyone else's collections, and "like" any specific collection.

Cloudspotting development begins with a Pinax starter project and adds example usage of pinax-images, pinax-likes, pinax-announcements, and pinax-testimonials.

Our Audience

  • Somewhat familiar with Django
  • Unfamiliar with Pinax starter projects
  • Eager to leverage Pinax apps

Choosing a Starter Project

My first goal is determining where to begin. The Pinax ecosystem contains starter projects in separate branches of pinax-starter-projects. Some branches are basic building blocks, "foundational" projects such as zero and account. These projects form a quick-start base for building your own site. Other starter projects are "demo" projects, which show how a particular Pinax app works. Examples include documents, stripe, and blog. These starter projects incorporate zero and account and provide enhanced functionality from Pinax apps pinax-documents, pinax-stripe, and pinax-blog respectively.

Since Pinax doesn't have a demo starter project for pinax-images, I must decide between a foundational and a demo project. I choose the Pinax documents demo starter project because pinax-documents uses AJAX for document management and pinax-images conforms to the same eldarion-ajax standard as pinax-documents. Document management is not cloudspotting's focus but image management is, and hopefully pinax-images is a drop-in replacement. Furthermore it seems the documents starter project contains all the pieces needed for a "standard" Django app. I will rip out pinax-documents related files, integrate pinax-images, create cloudspotting models and views, and add tests. With that decision made it's time to get coding!

Development

Initial Configuration

First I read the Pinax Starter Project directions and create a virtual environment:

virtualenv cloudspotting source cloudspotting/bin/activate cd cloudspotting

Following the same directions I install and invoke the "pinax" command line utility:

pip install pinax-cli pinax start --dev documents cloudspotting

FYI, "pinax-cli" is the package name, which stands for "Pinax Command Line Interface". "pinax" is the installed executable you invoke to create projects.

The "pinax start" command magically creates cloudspotting source code based on the Pinax documents starter project. Since the documents starter project does not have an official release, I add the --dev option to get the latest development branch of documents. (Without that option, the "pinax" command line tool would try to obtain the latest official release.) Following best practices I initialize a git repository and make an initial commit from the newly created project.

Renovation

Now it's time to renovate, ripping out code I don't need and adding new functionality. First I replace "pinax-documents" with "pinax-images" in requirements.txt, then install project requirements:

pip install -r requirements.txt

Reading pinax-images documentation reveals a front-end development shortcut. I install pinax-images-panel, a Javascript library for communicating with pinax-images and managing image collections. My npm invocation includes the --save option to add this requirement to the cloudspotting packages.json file.

npm install pinax-images-panel --save

Browsing the source code I find and remove unneeded "documents" references, replacing with "images" as required.

Building Cloudspotting

Most of the unneeded code is gone, so I create the primary cloudspotting model, CloudSpotting. There isn't much to a CloudSpotting, just the cloud type, the user who created the collection, and a collection of images. The image_set field points to an instance of ImageSet, defined in pinax-images.

class CloudSpotting(models.Model): cloud_type = models.CharField(max_length=100) user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="+") image_set = models.ForeignKey(ImageSet, blank=True, null=True)

New models need migrations, so I create an initial migration.

$ ./manage.py makemigrations cloudspotting Migrations for 'cloudspotting': 0001_initial.py: - Create model CloudSpotting - Alter unique_together for cloudspotting (1 constraint(s)) (cloudspotting) graham@Grahams-MacBook-Pro ~/venv/cloudspotting/src/cloudspotting $

Cloudspotting will support standard views like "create image collection" and "list all image collections". My design says users must be signed up to use cloudspotting views, and may only manipulate their own images and collections. I create Django generic class-based views and urls which satisfy these requirements using Django's LoginRequiredMixin and a custom get_queryset() mixin.

Next I add cloudspotting templates to accompany the new views. The use of Django generic class-based views allows me to simplify views.py by removing explicit template names and relying on the default Django CBV template naming pattern.

Pinax contributor Anna Ossowski helps me out and kindly creates a nice README for the project. Thanks Anna!

Shakedown Cruise

Cloudspotting now has minimal models, views, urls, and templates and is ready to run. I could write tests and prove the code works as expected (always good practice), but I'm eager to see if cloudspotting runs smoothly or belches smoke. I create database tables and invoke the Django development server to see what happens.

First I create database tables via migration:

$ ./manage.py migrate Operations to perform: Apply all migrations: auth, sites, account, admin, pinax_images, sessions, eventlog, contenttypes Running migrations: Rendering model states... DONE Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying account.0001_initial... OK Applying account.0002_fix_str... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying pinax_images.0001_initial... OK Applying cloudspotting.0001_initial... OK Applying eventlog.0001_initial... OK Applying eventlog.0002_auto_20150113_1450... OK Applying eventlog.0003_auto_20160111_0208... OK Applying sessions.0001_initial... OK Applying sites.0001_initial... OK Applying sites.0002_alter_domain_unique... OK (cloudspotting) graham@Grahams-MacBook-Pro ~/venv/cloudspotting/src/cloudspotting $

and then start the development server:

$ ./manage.py runserver

Success, of sorts. I am able to sign up and create a "CloudSpotting" instance, but quickly discover a problem. The image collection detail view, which allows image addition and removal from our CloudSpotting, does not work. The Javascript code for this page came from pinax-images-panel and a quick check with the author reveals a build process incompatibility. Apparently I need to "add React support to the gulp build process", whatever that means. I reach out for help and move on to writing tests.

Adding Tests

All projects need tests and cloudspotting is no exception. I add simple smoke tests proving CloudSpotting works as expected. Invoking the tests I see:

$ ./manage.py test Creating test database for alias 'default'... EEEEE ====================================================================== ERROR: test_create (cloudspotting.tests.tests.TestViews) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/graham/venv/cloudspotting/src/cloudspotting/cloudspotting/tests/tests.py", line 10, in setUp super(Tests, self).setUp() NameError: name 'Tests' is not defined <four more identical failures> ---------------------------------------------------------------------- Ran 5 tests in 0.002s FAILED (errors=5) Destroying test database for alias 'default'...

Oops, a typical cut-n-paste error, I did not specify the super() inheritance properly in setUp(). I fix the problem and run tests again.

$ ./manage.py test Creating test database for alias 'default'... ..... ---------------------------------------------------------------------- Ran 5 tests in 0.387s OK Destroying test database for alias 'default'...

Much better!

All tests pass now, but the detail page still doesn't allow image addition or deletion. Pinax contributor Patrick Altman helps me with a thorough review. He fixes my CloudSpotting model, changing a ForeignKey field to a OneToOneField and revising migration 0001_initial.py. How did Patrick change the initial migration? I guess he updated the model definition, then deleted "cloudspotting/migrations/" directory, then recreated migrations for cloudspotting. Patrick also realizes each new CloudSpotting instance needs an empty ImageSet, so he creates one in the CloudSpottingCreateView.form_valid method. His entire pull request includes a few other useful template updates and file changes/deletions, including updates for the newly-fixed pinax-images-panel. Now the cloudspotting detail pages works great. I can add images, set an image as the "title" image for a collection, and delete images. Thanks Patrick!

I continue exercising the site, trying to find other problems, but everything works fine. On a whim I decide to verify tests are still passing... and see five failures! If the site works, why are tests failing? I remember Patrick's previous CloudSpotting model change requires an ImageSet for each new CloudSpotting. The view tests manually create CloudSpotting instances for checking detail and list views, so the solution is simple: fix CloudSpotting creation in TestViews.setup(). One small update later and all tests pass. Phase 1 complete!

Lessons Learned

  • Building a project from scratch using Pinax starter projects is quite enjoyable. You get a solid pre-built framework as a base for your code in just a few seconds using the pinax command line tool.
  • Pinax starter project account could easily have been the base for cloudspotting. When comparing demo starter project documents with starter project account it turns out the difference is quite slim. Since I removed most of the document-related code during renovation, I may as well have started with account. Nevertheless my path to success worked just fine.
  • Peer code review is wonderful! Ask a trusted developer to review your code and plan to return the favor another day. You'll be glad you did.
  • Most importantly, using Pinax starter projects frees you from thinking about Django configuration and basic account handling. With the basics covered you are free to concentrate on your project goals. Pinax starter projects FTW!

Wrapping Up

You can view the current Pinax cloudspotting source code in the cloudspotting repository on GitHub.

If you have questions or comments about this post, the cloudspotting project, or Pinax in general, please join our Pinax Slack team and post your thoughts in the #cloudspotting or #general channels. The Pinax community is welcoming and helpful and it's a great place to get started. My Slack username is "grahamullrich". Feel free to ping me using "@grahamullrich" in any Pinax channel.

Inspiration for this project came from one of my favorite books, "The Cloudspotter's Guide" by Gavin Pretor-Pinney.

Many thanks to Eldarion for supporting this project, and to Anna Ossowski and Patrick Altman for their assistance.

Stay tuned for "Building Cloudspotting, Part 2" when I integrate pinax-likes, pinax-announcements, and pinax-testimonials with cloudspotting. See you soon!