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!

Defining the Json

There’s a new project in town.  We’re trying to hook up a homepage contact form to an existing Django-driven database via AngularJS (that’s a lot of buzzwords!).  The form has a few required fields, and some more optional fields.  The first couple tests didn’t work- turns out the culprit was the models- the non-required fields didn’t have the blank=True, null=True settings enabled, so the form was getting blocked there.

As an aside- troubleshooting a form submission using Django and Angular can be tricky.  Normally, Django gives you a nice, long error page with what went wrong.  It’s not always easy to decipher, but it always at least points you in the right direction.  But when sending it back and forth via an $http request, it can be more difficult to get the actual issue.  I settled for logging the response.data on the failure callback function to the console- that way I could at least see where in the chain I was going wrong.

So, I changed the models to allow blank entries on those fields that aren’t required.  Boom- no more error message.  Status 200, data being properly sent, good to go!

Not quite- now everything said it was working, but nothing was being saved to the test database.  I tried changing the function within my Angular controller.  I was really proud of my solution originally- to loop through all the form fields dynamically on submission, using a for-in (with the hasOwnProperty check) loop to get all the form ids and values (so if we want to change the form, we don’t have to change the Angular function- it will just get the new fields).  But maybe that was an issue (though it didn’t throw any errors), so I switched and manually grabbed the value of each form field.

Still no luck.  To make a long story short (too late), any field left blank when the form was submitted was being registered as ‘undefined’.  JSON (the format data is transferred in via an $http request) doesn’t like undefined, and was failing whenever it saw that in the object.  The solution was easy- I reinstated the for-in loop and added a check on each item (data is an empty object initialized higher up in the code that would be passed as data in the $http call):

for(var item in form) {
if(form.hasOwnProperty(item) {
data[form[item].id] = form[item].value || 'Info Not Submitted';
}
}

This prevented any undefined values from being submitted via the form, and everything started working.  Next time: I extol the virtues of Django’s built in JSON encoding functions.  They’re really great!