Fat Model in a Skinny View

Working with Django can be pretty fun.  I’m not quite as familiar with the patterns and recommended practices as with Javascript and it’s associated frameworks, but there is a cool mantra I learned recently: Fat Models – Skinny Views.

I’ve been working on creating an API for a product website/blog.  The front end is latest Angular, the back end uses Django to return JSON (in as close to a REST API as we can get).  Because my Python’s a bit rusty (our latest project used .NET as a backend), I was catching up with some Pluralsight videos.

Side note: Pluralsight is a great resource for learning more about many programming languages, frameworks, best practices, and so on.  I’m not an employee there, just a satisfied customer.  The series I was using to brush up on Django was called Django Fundamentals.

Anyway, the instructor mentioned a phrase that resonated with me: Fat models and skinny views.  Basically, keep your logic in methods on your models, and make your views responsible only for returning data as much as possible.  Until this point, we’d been haphazardly filling up our views with all the logic and it was a bit of a mess.

The fat models/skinny views pattern allows for more abstraction and reusability.  Example time!

One of the views is to simply get the blog data in JSON format for display.  If the request passes a ‘slug’ (the identifying string for an individual blog), we get just that blog entry.  Otherwise, we get all.  Nothing super complicated- but joins on a database table can be tricky in Django’s ORM (the layer on top of any SQL transaction).  The ORM is useful, but I just couldn’t get it to join our main blogs table with the category table and the authors table.  As a result, the frontend was just getting the author id and category id instead of the actual string name/title (because only the id was stored in the blogs table- in a Foreign Key relationship).

From a bit of digging into Django, I found that you can ‘prefetch’ that related info using the select_related method.  So our view started as:

def blogs(request, slug=""):
    if request.method == 'GET':
        if slug != "":
            entry = Blog.objects.select_related('category', 'author').get(slug=slug)
            return JsonResponse(json.dumps({"result": True, "msg": "Blogs Loaded", "data": data}), safe=False)
        else:
            blog_list = Blog.objects.filter(posted__lte=datetime.today()).order_by('-posted').select_related('category', 'author')
            return JsonResponse(json.dumps({"result": True, "msg": "Current Blogs Loaded", "data": data}), safe=False)

We check the request (only GET is allowed on this unprotected route- there’s a different one with JWT auth for updating/adding entries) and then check for existence of a slug.  If one is passed, get the individual entry with the related info prefetched.

But there was an issue.  On the frontend, we logged the JSON response, but no author or category info was included.  Just the data from the blog table.  I went back to the Django side and printed out my “entry” and “blog_list” variables- everything was there!  From a little more research, it seems that the above method works just fine if you’re using the default Django view template, but we weren’t- we just wanted JSON data passed to our Angular components for display.  It looks like when the entry/blog_list collection was converted to JSON, only the original object info was coming along for the ride.

But the related data (category and author) was available on the original entry/blog_list object.  So, we could just loop through and grab all the properties we need, add them to a simple Python dict, and return that.  With that solution, our view officially passed from slightly chubby into full on fat.  The loop logic was repeated in multiple places and multiple views- not good for future developers who might have to update this code (probably me!).

So, we made a slight update.  As an example, for the above code, just after we get the Blog.objects collection (either the individual blog or group of blogs), we call to a method on the model:

data = Blog.get_blog_and_join(entry)

That one’s for the individual entry. On the model, we created a new method to handle that logic:

def get_blog_and_join(entry):
    data = {"pk": entry.id, "title": entry.title, "slug": entry.slug, "author": entry.author.username, "body": entry.body, "posted": entry.posted.strftime("%Y-%m-%d")}
    if entry.category:
        data["category_name"] = entry.category.title
        data["category"] = entry.category.id
    else:
        data["category_name"] = "No Category"
    return data

This just loops through the entry passed (the Blog object returned by Django’s ORM with related author and category data) and assigns the values to properties in a new dict.  Note the author prop- it references entry.author.username.  This didn’t work in the original method of just returning a JSON encoded version of the Blog.Object returned by the ORM.  A little extra logic to check if a category exists (they’re optional in our setup), and now we have a simple object with all the info we need.  It can be returned as JSON and the front end team has their precious data!

Advertisements

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