django+angular
— 13 min read

End to end web app with Django-Rest-Framework & AngularJS.

[Part 4].

In this part we will create a tags app for our project.

>  git checkout part4

Backend

Let’s create a simple model for our tags,

 1 from django.db import models
 2 from django.utils.translation import ugettext_lazy as _
 3 from django.template.defaultfilters import slugify
 4 
 5 
 6 class Tag(models.Model):
 7     slug = models.SlugField(max_length=100, verbose_name=_('slug'), blank=True)
 8     name = models.CharField(max_length=100)
 9 
10     def __unicode__(self):
11         return "%s" % self.name
12 
13     def save(self, *args, **kwargs):
14         if not self.pk:
15             self.slug = slugify(self.name)
16         super(Tag, self).save(*args, **kwargs)

Create post_syncdb signal handler to create some tags

 1 from django.db.models import signals
 2 from tags.models import Tag
 3 
 4 
 5 def create_tags(app, created_models, **kwargs):
 6         Tag.objects.get_or_create(name="Architecture")
 7         Tag.objects.get_or_create(name="Art")
 8         Tag.objects.get_or_create(name="Books")
 9         Tag.objects.get_or_create(name="Cars & Motorcycles")
10         Tag.objects.get_or_create(name="DIY & Crafts")
11         Tag.objects.get_or_create(name="Design")
12         Tag.objects.get_or_create(name="Drink")
13         Tag.objects.get_or_create(name="Education")
14         Tag.objects.get_or_create(name="Fashion")
15         Tag.objects.get_or_create(name="Film, Music")
16         Tag.objects.get_or_create(name="Food")
17         Tag.objects.get_or_create(name="Games")
18         Tag.objects.get_or_create(name="Gardening")
19         Tag.objects.get_or_create(name="Geeg")
20         Tag.objects.get_or_create(name="Hair & beauty")
21         Tag.objects.get_or_create(name="Health & fitness")
22         Tag.objects.get_or_create(name="History")
23         Tag.objects.get_or_create(name="Holidays & Events")
24         Tag.objects.get_or_create(name="Home Decor")
25         Tag.objects.get_or_create(name="Humor")
26         Tag.objects.get_or_create(name="Illustrations")
27         Tag.objects.get_or_create(name="Kids")
28         Tag.objects.get_or_create(name="Men")
29         Tag.objects.get_or_create(name="Outdoors")
30         Tag.objects.get_or_create(name="Photography")
31         Tag.objects.get_or_create(name="Products")
32         Tag.objects.get_or_create(name="Quotes")
33         Tag.objects.get_or_create(name="Science a Nature")
34         Tag.objects.get_or_create(name="Sports")
35         Tag.objects.get_or_create(name="Technology")
36         Tag.objects.get_or_create(name="Travel")
37         Tag.objects.get_or_create(name="Weddings")
38         Tag.objects.get_or_create(name="Women")
39         Tag.objects.get_or_create(name="Videos")
40 
41 signals.post_syncdb.connect(create_tags, dispatch_uid=Tag)

the app contains a migration, use south to migrate this app.

Let’s add views to query tags, list tags, search and autocomplete

 1 from rest_framework import generics
 2 from rest_framework import permissions
 3 from tags.models import Tag
 4 from tags.serializers import TagSerializer
 5 
 6 
 7 class TagList(generics.ListAPIView):
 8     model = Tag
 9     serializer_class = TagSerializer
10     permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
11 
12     def get_queryset(self):
13         """
14         This view should return a list of
15             if q, all tags that contain q
16             else, all tags
17         """
18         queryset = Tag.objects.all()
19         query = self.request.QUERY_PARAMS.get('q', None)
20         if query is not None:
21             queryset = queryset.filter(name__icontains=query)
22         return queryset
23 
24 
25 class TagDetail(generics.RetrieveUpdateDestroyAPIView):
26     """
27     Retrieve, update or delete a tag instance.
28     """
29     model = Tag
30     serializer_class = TagSerializer
31     permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

Now we should update the post model to include tags field

1 tags = models.ManyToManyField('tags.Tag', related_name='posts')

We should also add a tags field in the post serializer

 1 from rest_framework import serializers
 2 from posts.models import Post
 3 from tags.serializers import TagSerializer
 4 
 5 
 6 class PostSerializer(serializers.HyperlinkedModelSerializer):
 7     author = serializers.Field(source='author.username')
 8     tags_details = TagSerializer(source='tags', read_only=True)
 9     api_url = serializers.SerializerMethodField('get_api_url')
10 
11     class Meta:
12         model = Post
13         fields = ('id', 'title', 'description', 'created_on', 'author', 'tags',
14                   'tags_details', 'url', 'api_url')
15         read_only_fields = ('id', 'created_on')
16 
17     def get_api_url(self, obj):
18         return "#/post/%s" % obj.id

Frontend

We should start first by creating a service to manage our tags

 1 Blog.factory('TagService', function ($http, $q) {
 2     var api_url = "/tags/";
 3     var api_suffixe = "/posts/";
 4     return {
 5         get: function(tag_id){
 6             var url = api_url + tag_id+ "/";
 7             var defer = $q.defer();
 8             $http({method: 'GET', url: url}).
 9                 success(function(data, status, headers, config) {
10                     defer.resolve(data);
11                 }).
12                 error(function(data, status, headers, config) {
13                     defer.reject(status);
14                 });
15             return defer.promise;
16         },
17         list: function(){
18             var defer = $q.defer();
19             $http({method: 'GET', url: api_url}).
20                 success(function(data, status, headers, config) {
21                     defer.resolve(data);
22                 }).
23                 error(function(data, status, headers, config) {
24                     defer.reject(status);
25                 });
26             return defer.promise;
27         },
28         query: function(text){
29             var url = api_url + '?q=' + text;
30             var defer = $q.defer();
31             $http({method: 'GET', url: url}).
32                 success(function(data, status, headers, config) {
33                     defer.resolve(data);
34                 }).
35                 error(function(data, status, headers, config) {
36                     defer.reject(status);
37                 });
38             return defer.promise;
39         }
40     }
41 });

Now we can use the tags service in the controllers to query tags, make an autocomplete, and associate tags to posts

 1 Blog.controller('PostController', function ($scope, $routeParams, $location, PostService, TagService, GlobalService, post) {
 2     $scope.post = post;
 3     $scope.globals = GlobalService;
 4     var failureCb = function (status) {
 5         console.log(status);
 6     }
 7     //options for modals
 8     $scope.opts = {
 9         backdropFade: true,
10         dialogFade: true
11     };
12     //open modals
13     $scope.open = function (action) {
14         if (action === 'edit'){
15             $scope.postModalEdit = true;
16         };
17     };
18     //close modals
19     $scope.close = function (action) {
20         if (action === 'edit'){
21             $scope.postModalEdit = false;
22         };
23     };
24     //calling board service
25     $scope.update = function () {
26         PostService.update($scope.post).then(function (data) {
27             $scope.post = data;
28             $scope.postModalEdit = false;
29         }, failureCb);
30     };
31     $scope.getTag = function (text) {
32         return TagService.query(text).then(function (data) {
33             return data;
34         }, function (status) {
35             console.log(status);
36         });
37     };
38     $scope.selectTag = function () {
39         if (typeof $scope.selectedTag === 'object') {
40             $scope.post.tags.push($scope.selectedTag.url);
41             $scope.post.tags_details.push($scope.selectedTag);
42             $scope.selectedTag = null;
43         }
44     };
45     $scope.removeTag = function (category) {
46         var index = $scope.post.tags_details.indexOf(category);
47         $scope.post.tags_details.splice(index, 1);
48         var index = $scope.post.tags.indexOf(category.url);
49         $scope.post.tags.splice(index, 1);
50     };
51 });

Finnaly we can update our templates to show the list of tags for each post.

1 <p>
2    <ul class="inline">
3       <li ng-repeat="tag in post.tags_details">
4           <span class="label"><i class="icon-white icon-tag"></i> {[{tag.name}]}</span>
5        </li>
6    </ul>
7 </p>