Matt Camilli
@mlcamilli
matt@trackmaven.com
We wanted a fast and fluid UI/UX while working with a lot of variable data.
We would serve all of our data from the backend in a Rest API and have the front end asynchronously interact with it.
https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3
Authentication includes OAuth1 and OAuth2
Serialization support
Customizable
Extensive Documentation
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __unicode__(self):
return self.question_text
class Choice(models.Model):
question = models.ForeignKey(Question, related_name='choices')
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __unicode__(self):
return self.choice_text
from rest_framework import serializers
from .models import Question, Choice
class ChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = Choice
fields = ('choice_text', 'id', 'votes')
class QuestionSerializer(serializers.ModelSerializer):
choices = ChoiceSerializer(many=True)
class Meta:
model = Question
fields = ('question_text', 'choices', 'id')
from rest_framework import generics, permissions
from .models import Question, Choice
from .serializers import QuestionSerializer, ChoiceSerializer
class QuestionList(generics.ListCreateAPIView):
model = Question
serializer_class = QuestionSerializer
permission_classes = [permissions.AllowAny]
class QuestionDetail(generics.RetrieveAPIView):
model = Question
serializer_class = QuestionSerializer
lookup_url_kwarg = 'question_pk'
permission_classes = [permissions.AllowAny]
class ChoiceUpdate(generics.UpdateAPIView):
model = Choice
serializer_class = ChoiceSerializer
lookup_url_kwarg = 'choice_pk'
permission_classes = [
permissions.AllowAny
]
class ChoiceList(generics.ListCreateAPIView):
model = Choice
serializer_class = ChoiceSerializer
permission_classes = [
permissions.AllowAny
]
from django.conf.urls import patterns, url, include
from .views import QuestionList, ChoiceList, QuestionDetail, ChoiceUpdate
urlpatterns = patterns('polls.views',
url(r'^questions$', QuestionList.as_view(),
name='questions_list'),
url(r'^questions/(?P[0-9]+)/$', QuestionDetail.as_view(),
name="questions_detail"),
url(r'^choices$', ChoiceList.as_view(), name='choices_list'),
url(r'^choices/(?P[0-9]+)/$', ChoiceUpdate.as_view(),
name='choices_update'),
url(r'^$', 'index', name='questions_index'),
)
It is a very good idea to decouple DOM manipulation from app logic. This dramatically improves the testability of the code.
It is a really, really good idea to regard app testing as equal in importance to app writing. Testing difficulty is dramatically affected by the way the code is structured.
It is an excellent idea to decouple the client side of an app from the server side. This allows development work to progress in parallel, and allows for reuse of both sides.
It is very helpful indeed if the framework guides developers through the entire journey of building an app: from designing the UI, through writing the business logic, to testing.
It is always good to make common tasks trivial and difficult tasks possible.
app = angular.module('pollApp', ['ui.router','pollApp.controllers',
'pollApp.services', 'pollApp.directives'])
app.config(($interpolateProvider, $stateProvider, $urlRouterProvider) ->
$interpolateProvider.startSymbol('[[')
$interpolateProvider.endSymbol(']]')
app.config(($httpProvider) ->
getCookie = (name) ->
for cookie in document.cookie.split ';' when cookie and
name is (cookie.trim().split '=')[0]
return decodeURIComponent cookie.trim()[(1 + name.length)...]
null
$httpProvider.defaults.headers.common['X-CSRFToken'] = getCookie("csrftoken")
)
<body ng-app="pollApp">
<div class="container">
<div class="page-header">
<h1>Polls</h1>
</div>
<div ui-view></div>
</div>
<script src="/static/js/angular.js"> </script>
<script src="/static/js/angular-ui-router.min.js"></script>
<script src="/static/js/app.js"></script>
<script src="/static/js/controllers.js"></script>
<script src="/static/js/directives.js"></script>
<script src="/static/js/services.js"></script>
</body>
services = angular.module('pollApp.services', [])
services.factory('Questions', ($log, $http, Question) ->
questions = {all : []}
fromServer: (data) ->
questions['all'].length = 0
for question in data
questions['all'].push(new Question(question))
fetch: ->
$http({method: 'GET', url: '/polls/questions'})
.success (data) =>
@fromServer(data)
$log.info("Succesfully fetched questions.")
.error (data) =>
$log.info("Failed to fetch questions.")
data : ->
return questions
)
services.factory('Question', (Choice, $http, $log) ->
class Question
constructor : (data) ->
if data != null
@init(data)
init : (data) ->
@question_text = data.question_text
@id = data.id
@choices = []
@totalVotes = 0
for choice in data.choices
c = new Choice(choice)
@totalVotes += c.votes
@choices.push(new Choice(choice))
get : (questionId) ->
$http({method: 'GET',
url: '/polls/questions/' + questionId + '/'})
.success (data) =>
@init(data)
$log.info("Succesfully fetched question")
.error (data) =>
$log.info("Failed to fetch question.")
return Question
)
services.factory('Choice', ($http, $log)->
class Choice
constructor: (data) ->
@choice_text = data.choice_text
@id = data.id
@votes = data.votes
update : ->
data = {'votes' : @votes, 'choice_text' : @choice_text}
$http({method: 'PUT',
url: '/polls/choices/' + @id + '/', data:data})
.success (data) =>
$log.info("Succesfully voted")
.error (data) =>
$log.info("Failed to vote.")
return Choice
)
$urlRouterProvider.otherwise('/');
$stateProvider
.state('questionList'
url: '/'
templateUrl: 'questionList'
controller: 'questionListController'
resolve:
questions : (Questions)->
Questions.fetch()
return Questions.data()
)
.state('questionDetail'
url: '/{questionId:[0-9]+}/'
templateUrl: 'questionDetail'
controller: 'questionDetailController'
resolve:
question : ($stateParams, $log, Question)->
question = new Question(null)
question.get($stateParams.questionId)
return question
)
controllers = angular.module('pollApp.controllers', [])
controllers.controller('questionListController',
($scope, $state, $log, questions) ->
$scope.questions = questions.all
)
controllers.controller('questionDetailController',
($scope, $state, $log, question) ->
$scope.question = question
$scope.voted = false
$scope.voteChoice = 0
$scope.vote = ->
for choice in $scope.question.choices
if choice.id == parseInt($scope.voteChoice)
choice.votes+=1
$scope.question.totalVotes+=1
choice.update()
break
$scope.voted = true
)
<script type="text/ng-template" id="questionList">
</script>
<ul>
<li ng-repeat="question in questions">
<a ui-sref="questionDetail({questionId:question.id})">
[[question.question_text]]
</a>
</li>
</ul>
<form class="form" ng-submit="vote()" ng-show="!voted">
<h2>[[question.question_text]]</h2>
<div class="radio" ng-repeat="choice in question.choices">
<label>
<input type="radio" ng-model="$parent.voteChoice"
name="voteChoice" value="[[choice.id]]">
[[choice.choice_text]]
</label>
</div>
<input type="submit" class="btn btn-info" />
</form>
<div ng-show="voted">
<h2>[[question.question_text]]</h2>
<div ng-repeat="choice in question.choices" style="width: 50%">
[[choice.choice_text]] : [[choice.votes ]]
<div class="progress progress-striped">
<div class="progress-bar progress-bar-info"
choice-percentage votes="choice.votes"
total="question.totalVotes" >
</div>
</div>
</div>
</div>
<a ui-sref="questionList"> << Back to list</a>
directives = angular.module('pollApp.directives', [])
directives.directive('choicePercentage', ->
restrict: 'A'
scope:
votes: '='
total: '='
link: (scope, element, attrs) ->
update = ->
if scope.total > 0
percentage = scope.votes / scope.total * 100
else
percentage = 0
element.css('width', percentage + '%')
scope.$watch 'total', (value) -> update()
scope.$watch 'votes', (value) -> update()
)