DetailView on Django REST – multiple methods

Long time since I wrote something on Django, but today it surprised me again with its flexibility. Let me put it down here. Remember the traditional DetailView ?

Example from: https://docs.djangoproject.com/en/1.10/ref/class-based-views/generic-display/#detailview

from django.views.generic.detail import DetailView
from articles.models import Article<img src="blob:https://wordpress.com/64cb7829-8030-43aa-91de-8e4cf958aad0" alt="Selection_407.png" class="alignnone size-full wp-image-media-2"/>

class ArticleDetailView(DetailView):

    model = Article

    def get_object(self): 
        try: 
            return Article.objects.get(pk=self.kwargs.get('pk'))

Setting this thing up in Angular to retrieve details of an Article, I used to follow something similar:

from articles.models import Article
from django.http import Http404

class ArticleDetailView(generics.RetrieveAPIView):

    model = Article
    serializer_class = ArticleDetailsSerializer
    queryset = Article.objects.all() 

    def get_object(self): 
        try: 
            return Article.objects.get(pk=self.kwargs.get('pk'))
        expect ValueError:
            return Http404

I was looking through the documentation agian , and I see that the entire thing can be done in like 3 lines. Take a look:

from articles.models import Article

class ArticleDetailView(generics.RetrieveAPIView):

    model = Article
    serializer_class = ArticleDetailsSerializer
    queryset = Article.objects.all() 

Note: This should work only if you have Article primary key received on urls.py as pk. ie, something like this in your urls.py:

from django.conf.urls import url

from article.views import ArticleDetailView

urlpatterns = [
    url(r'^article/(?P<pk>[\w]+)/$', ArticleDetailView.as_view(), name='article-list'),
]

In case you do not want to send in the primary key, you can provide the custom slug name using the attribute lookup_fields in your view definition. Read more here: http://www.django-rest-framework.org/api-guide/generic-views/#attributes

An example request to the URL would be http://127.0.0.1:8000/article/50. Django for life, Enjoy humans.

Surprise from Python on new years eve, 2016

Alright, so another usual night and I was trying to figure out a strange behavior with a Python class I was working. Looks like I had OOP lectures only in C++, and things are little bit different here. So here you go!

I had this strange class method here described as:


class BenchmarkResults(object):

    @classmethod
    def _generate_db_entries(cls, tests, skip_aggregated=False, parent=None, test_table = []):
         # This function recursively calls _generate_db_entries with
         # test_table param set to a list 

and this was processing in a JSON file from one of my POST API and doing some crazy stuff. The API calls this class like

class BotReportView(APIView):
    def post(self, request, format=None):
        test_data_results = BenchmarkResults(json.load(self.request.FILES.get('test_data')))
        results_table = test_data_results._generate_db_entries(test_data_results)

Now the funny part. I found that on subsequent calls to the API, the test_data_results kept on multiplying 😮 like the data from the previous POST also got appended to the current JSON which-should-be-processed. Thinking it was a problem with the client, I tried switching to curl, but with little luck.

The problem
Finally pycharm got me figuring out what went wrong, as it showed a warning on the test_table = [] something like:
The default method argument is mutable. Bah, taking at look at http://stackoverflow.com/questions/9039191/mutable-default-method-arguments-in-python I see that when an empty list [] is kept as a default formal argument for a function, the list is not killed in between, and keeps on growing on subsequent calls – surprise :\

Quoting http://stackoverflow.com/a/9039224/3355893, when we have something like:

def status(options=[]):
    options.append('new_option')
    return options

print status()
print status()
print status()

will print something like

['new_option']
['new_option', 'new_option']
['new_option', 'new_option', 'new_option']

Tough luck finding it out, but it took couple of hours 😀 I got my code easily fixed by not allowing [] to stay as a default param, but separately passing an empty list to the function during the call. Like

class BenchmarkResults(object):

    @classmethod
    def _generate_db_entries_helper(cls, tests, skip_aggregated=False, parent=None):
        test_table = []
        self._generate_db_entries(cls, tests, test_tables skip_aggregated=False, parent=None):

    @classmethod
    def _generate_db_entries(cls, tests, test_table, skip_aggregated=False, parent=None):
         # This function recursively calls _generate_db_entries with test_table param set to a list 

Yay. that was it, and latter called in the helper function, and things started working. You can see the change here, and finally, Happy New year 2017! Make every commit count!

[Angular JS] Setting a combobox based on selection of another one

This might be my first post which touch Angular JS, and wanted to let you guys know that I too joined the backend to frontend club! Currently Angular JS is powering https://browserperfdash.igalia.com/ with Django REST at the backend – and I think its super fast, easy to develop and allows for a cleam responsive UI – WOW is the word.

Problem
You have a data model, which looks something like:

bots = [{ 'bot1' : {'platform': "Linux", id:1}}, { 'bot2' : {'platform': "Windows", id:2}}];
platforms = [{ 'name' :'Linux', 'id' :1},{ 'name' :'Windows', 'id' :2}, { 'name' :'MacOSuX', 'id' :3}];     

You have a select box bots and another select box Platforms and you need the following logic to happen:

  1. When no bot is selected,the Platforms combo should show All all the platform options
  2. When a bot is selected, it should force the Platform combo to show the platform property of bot selected, and should disable them to make no further changes.

Solution
You will need the following Angular script to make this work. Add this to your scripts.js or wherever place and include it in your html file.

app.controller('AppController', function($scope) {
    $scope.bots = [{ 'name': 'bot1', 'platform': "Linux", id:1}, { 'name': 'bot2', 'platform': "Windows", id:2}];
    $scope.platforms = [{ 'name' :'Linux', 'id' :1},{ 'name' :'Windows', 'id' :2}, { 'name' :'MacOSuX', 'id' :3}];     
});

see that you can even add this directly to the html file with the tag. Now you can add the following to the HTML file so that the data gets correctly rendered to the browser.

<div class="form-group">
  <label for="bot">Bot</label>
  <select name="bot" class="form-control" ng-model="selectedBot"
	  ng-options="bot.name for bot in bots">
      <option value="">--All bots--</option>
  </select>
</div>
<div class="form-group">
  <label for="platform">Platform</label>
  <select name="platform" class="form-control" ng-model="selectedPlatform" ng-disabled="selectedBot">
      <option value="">--All platforms--</option>
      <option ng-repeat="x in platforms" value="{{ x.name }}">{{ x.name }}</option>
      <option value="{{ selectedBot.platform }}" ng-selected="selectedBot" ng-show="selectedBot">
	  {{ selectedBot.platform }}</option>
  </select>
</div>

How it works?

  • The {{ x.name }} makes sure that all the platforms are shown initially making sure our condition (1) above.
  • The {{ selectedBot.platform }} makes sure that when the bot select is clicked, this option value is set for the combo box – adding to (2) above.
  • The ng-selected="selectedBot" ng-show="selectedBot" makes sure that the combo gets disabled when someone selects in some value for bots contributing to (2) above
  • You can see the dropdowns live in action at https://browserperfdash.igalia.com/ and do take a look and comment if something seems weird.

    Update: It seems like there was a problem with this approach as the selectedPlatform is not getting set correctly, even though the combo gets set. You can fix this by
    adding in the following action on ng-change on the select combo

    <select name="bot" class="form-control" ng-model="selectedBot"
          ng-options="bot.name for bot in bots" ng-change="updateOtherCombos()">
      <option value="">--All bots--</option>
    </select>
    

    and now defining the function updateOtherCombos() to your controllers.js

    app.controller('AppController', function($scope) {
        $scope.bots = [{ 'name': 'bot1', 'platform': "Linux", id:1}, { 'name': 'bot2', 'platform': "Windows", id:2}];
        $scope.platforms = [{ 'name' :'Linux', 'id' :1},{ 'name' :'Windows', 'id' :2}, { 'name' :'MacOSuX', 'id' :3}];     
        $scope.updateOtherCombos = function () {
          if ( $scope.selectedBot ) {
    	  $scope.selectedPlatform = $scope.selectedBot.platform;
    	  $scope.selectedCPU = $scope.selectedBot.cpu;
    	  $scope.selectedGPU = $scope.selectedBot.gpu;
          }
       };
    });