Building a Simple Reporting App in Django: Part 1

“Let’s parse the data and free the facts” – Someone wiser than me

My previous posts on “How I am Learning Python from Scratch” is becoming increasingly inaccurate. Based on what I have been doing the last 3 months or so, a title such as “How I am Learning Web Development from Scratch” or “How I am Learning Django from Scratch” is probably more apt.

Anyways, I’ve been busy working on a reporting app for Insync the last few weeks. This is my de facto project for my internship. The app has gone through many iterations and I’ll do my best to show the before/after results. You can read about Part2 here.

Overall User Flow

  1. Click on the metric you want to view
  2. Enter date range and submit form
  3. Page returns an ugly time series graph

Under the Hood

  1. Laying out the project
    1. I’m using Dave McClure’s excellent AARRR metrics guideline to breakdown the application. Each one of the AARRR is its own app within the project. Previously, I had everything all in one app which was a big mistake! My views.py file was overwhelming. I wasn’t sure when was a good time to break out the app into many apps. Protip: If you’re already unsure or thinking about it, then it’s probably a good idea to do it. Each app should perform a different function and it doesn’t matter how big or how small this function is. In this case, each metric is addressing a different part of the business so it’s intuitive to break it out into its own app. My main urls file looks like this to give you a better idea:
      urlpatterns = patterns('',
          (r'^', include('reporting.mainapp.urls')),
          (r'^', include('reporting.activation.urls')),
          (r'^', include('reporting.acquisition.urls')),
          (r'^', include('reporting.retention.urls')),
          (r'^', include('reporting.referral.urls')),
          (r'^', include('reporting.revenue.urls')),
          (r'^', include('reporting.engagement.urls')),
          (r'^admin/doc/', include('django.contrib.admindocs.urls')),
          (r'^admin/', include(admin.site.urls)),
          (r'^media/(?P<path>.*)$', 'django.views.static.serve',
                       {'document_root': settings.MEDIA_ROOT}),
          (r'^css/(?P<path>.*)$', 'django.views.static.serve',
                       {'document_root': settings.CSS_ROOT}),
          (r'^js/(?P<path>.*)$', 'django.views.static.serve',
                       {'document_root': settings.JS_ROOT})
      )
    2. Each app has a base template that is inherited from a main base template. For example, activation_signups.html inherits from base_activation.html which in turn inherits from base.html.
    3. Each app also contains a utility.py file where all the helper functions are located. This is the file where I write my SQL queries to generate the data and format them accordingly to the kind of output that I desire like a linegraph or a piechart. The library I am using to generate the graphs is Matplotlib.
  2. Forms, gotta love them
    1. I went from this in my template:
      {% if usersegment == "non-paying" %}
      <input type="radio" name="usersegment" value="non-paying" checked="yes"/> Non-paying Users <br />
      <input type="radio" name="usersegment" value="paying" /> Paying Users <br />
      <input type="radio" name="usersegment" value="all" /> All Users<br />
      {% endif %}
      
      {% if usersegment == "paying" %}
      <input type="radio" name="usersegment" value="non-paying" /> Non-paying Users <br />
      <input type="radio" name="usersegment" value="paying" checked="yes"/> Paying Users <br />
      <input type="radio" name="usersegment" value="all" /> All Users<br />
      {% endif %}
      
      {% if usersegment == "all" or not usersegment %}
      <input type="radio" name="usersegment" value="non-paying" checked="yes"/> Non-paying Users <br />
      <input type="radio" name="usersegment" value="paying" /> Paying Users <br />
      <input type="radio" name="usersegment" value="all" checked="yes"/> All Users<br />
      {% endif %}

      to this:

      forms.py

      usersegment = [['non-paying','Non-paying (Coming soon)'],['paying','Paying (Coming soon)'], ['all', 'All']]
      
      class SegmentForm(forms.Form):
          usersegment = forms.ChoiceField(label="", widget=RadioSelect(renderer=HorizontalRadioRenderer), choices=usersegment, initial='all')

      template

      <form method="post" action="">{% csrf_token %}
      <h3>Choose Segment</h3>
      {% if segment_form.errors %}
      <p style="color: red;">
      Please correct the error{{ form.errors|pluralize }} below.</p>
      {% endif %}
      {{ segment_form.as_p }}
      <p><input type="submit" value="Submit" /</p>
      </form>

      views.py

      def activation_signupcount(request):
          if request.method == 'POST':
              segment_form = SegmentForm(request.POST)
          else:
              segment_form = SegmentForm()
          return render_to_response('activation_signupcount.html',
                                  {   'segment_form': segment_form })

      It’s so beautiful, Django forms take care of everything!

  3. Datepicker
    1. The first date picker I used was a basic version from jQuery UI. Later, I found a souped-up version that was later put together by the folks at Filament. You can find it here. Basically, the newer version has preset dates which really helps with the usability. The preset dates are also customisable.

In my next post, I will talk about how the graphs are generated using Matplotlib and how the data is retrieved to populate the graphs. Thanks to the guys are /r/django for helping me out too!

Advertisements

One thought on “Building a Simple Reporting App in Django: Part 1

  1. Pingback: Building a Simple Reporting App in Django: Part 2 | pragmaticstartup

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s