View on GitHub

ember-django

TUTORIAL. Ember.js + Django REST framework

WIP // TUTORIAL. Ember.js + Django REST framework

To-dos:

Building minimal Ember.js client and Django server

This is a minimal guide to setting up basic Django backend and making it work with Ember.js

Contents

Step 1 Backend

Some description on what we’ll do and how we’ll do that goes here.

Resources:

Install Python version manager, virtual environment manager and everything they need to work correctly:

Step 1.1 Prerequisites

Installing git:

 sudo apt-get install git 

Installing pyenv and plugins using pyenv-installer:

 curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash

Adding lines to the end of ~/.bashrc file located in the root folder:

export PATH="/home/mint/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

Installing requirements for using pyenv:

sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \
libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev libffi-dev

Step 1.2 Configuration and requirements

According to current documentation the latest python version supported is 3.6

Type the following code to install Python:

pyenv install 3.6.6

Create and open our project directory:

mkdir ember-django && cd ember-django

Create and switch to our backend directory:

mkdir backend && cd backend

Let’s create a virtual environment for the project and name it, say an ember-django-3.6.6:

pyenv virtualenv 3.6.6 ember-django-3.6.6

It would be nice to automatically switch virtual environment on opening current folder. Running command below will create .python-version file with the name of our virtualenv.

pyenv local ember-django-3.6.6

if you wish echo "ember-django-3.6.6" > .python-version will also do the thing

Let’s install latest Django version:

pip install Django==2.0.7

Initialize project in the current directory:

django-admin.py startproject backend .

NOTE mention “.” character at the end of the line above.

Once we’ve done that let’s create an app that we’ll use to create Web API.

python manage.py startapp posts

Now we can install Django REST Framework: a “powerfull and flexible toolkit for building WEB APIs”.

pip install djangorestframework

and additional package Django REST Framework JSON API:

pip install djangorestframework-jsonapi

As suggested by DJA (Django REST Framework JSON API) let’s add some code to backend/settings.py

REST_FRAMEWORK = {
    'PAGE_SIZE': 10,
    'EXCEPTION_HANDLER': 'rest_framework_json_api.exceptions.exception_handler',
    'DEFAULT_PAGINATION_CLASS':
        'rest_framework_json_api.pagination.PageNumberPagination',
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework_json_api.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ),
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework_json_api.renderers.JSONRenderer',
        # If you're performance testing, you will want to use the browseable API
        # without forms, as the forms can generate their own queries.
        # If performance testing, enable:
        # 'example.utils.BrowsableAPIRendererWithoutForms',
        # Otherwise, to play around with the browseable API, enable:
        'rest_framework.renderers.BrowsableAPIRenderer'
    ),
    'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata',
}

Also, we’ll need to add our new apps:

INSTALLED_APPS = (
    ...
    'rest_framework',
    'posts',
)

And we want Django to pluralize and camelize types:

JSON_API_PLURALIZE_TYPES = True
JSON_API_FORMAT_TYPES = 'camelize'

Step 1.3 Model

We’re going to start by creating a simple Post model that will store our posts. Add the following code to posts/models.py

from django.db import models


class Post(models.Model):
	title = models.CharField(max_length=100, blank=True, default='')
	body = models.TextField(default='')
	url = models.URLField(default='')

	class Meta:
		ordering = ('id',)

We’ll need to create a migration for posts model:

python manage.py makemigrations posts

and syncronize the database for the first time:

python manage.py migrate

Step 1.4 Serialization

First of all, let’s create posts/serializers.py file.

We’ll provide a way of serializing and deserializing our posts instances into JSON representation by creating PostSerializer class. Let’s use base serializer class so kindly provided by rest_framework_json_api

from rest_framework_json_api import serializers
from posts.models import Post


class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ('id', 'title', 'body', 'url', )

Step 1.5 Views

We’ll start by adding classes for handling JSON API requests and responses. Add the code below to posts/views.py

import rest_framework.parsers
import rest_framework.renderers
from rest_framework import exceptions

import rest_framework_json_api.parsers
import rest_framework_json_api.metadata
from rest_framework_json_api.utils import format_drf_errors
from rest_framework_json_api.views import ModelViewSet


from posts.models import Post
from posts.serializers import PostSerializer


HTTP_422_UNPROCESSABLE_ENTITY = 422

class JsonApiViewSet(ModelViewSet):
    """
    This is an example on how to configure DRF-jsonapi from
    within a class. It allows using DRF-jsonapi alongside
    vanilla DRF API views.
    """
    parser_classes = [
        rest_framework_json_api.parsers.JSONParser,
        rest_framework.parsers.FormParser,
        rest_framework.parsers.MultiPartParser,
    ]
    renderer_classes = [
        rest_framework_json_api.renderers.JSONRenderer,
        rest_framework.renderers.BrowsableAPIRenderer,
    ]
    metadata_class = rest_framework_json_api.metadata.JSONAPIMetadata

    def handle_exception(self, exc):
        if isinstance(exc, exceptions.ValidationError):
            # some require that validation errors return 422 status
            # for example ember-data (isInvalid method on adapter)
            exc.status_code = HTTP_422_UNPROCESSABLE_ENTITY
        # exception handler can't be set on class so you have to
        # override the error response in this method
        response = super(JsonApiViewSet, self).handle_exception(exc)
        context = self.get_exception_handler_context()
        return format_drf_errors(response, context, exc)


class PostViewSet(JsonApiViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

you can find more examples at DJAs github page

Step 1.6 Routes

Let’s register appropriate view sets at posts/urls.py

from django.conf.urls import include, url
from rest_framework import routers

from posts.views import PostViewSet

router = routers.DefaultRouter(trailing_slash=False)

router.register(r'posts', PostViewSet)


urlpatterns = [
    url(r'^', include(router.urls)),
]

and don’t forget to make migrations:

python manage.py makemigrations

and:

python manage.py migrate

If you’ll run django server:

python manage.py runserver

you’ll see an empty Posts List at http://127.0.0.1:8000/posts/

_NOTE JSON API conventions suggest us to avoid using trailing slashes (trailing_slash=False). So pay attention if you’re using trailing slashes in your browser or not.

If you’ll scroll to the bottom of the page you’ll notice an HTML form that allows us to PUT some information to the server. Try to add your first post.

Now, if you’ll go to http://127.0.0.1:8000/posts/ you’ll see a list of posts and if you’ll go to http://127.0.0.1:8000/posts/1/ you’ll see your first post.

Step 2 Frontend

We want our application to:

Resources:

Step 2.1 Prerequisites

Before starting our ember project we’ll need to set up virtual environment and install Node.js. All you need to do is just to follow zoltan-nz’s recomendations.

Step 2.2 Creating app

After you installed node.js run the following command in your terminal. Make sure you’re in our projects root directory. Add --yarn to the line following if you’re using yarn as dependency manager.

ember new frontend

And to simplify our development process let’s install ember-cli-sass. If you’ve used yarn in the previous step, then ember will automatically install addon using your preferred package manager.

ember install ember-cli-sass

Let’s turn on some dev options and pods folder structure in config/environment.js. Just uncomment the following.

ENV.APP.LOG_ACTIVE_GENERATION = true;
ENV.APP.LOG_TRANSITIONS = true;
ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
ENV.APP.LOG_VIEW_LOOKUPS = true;

and add this line

let ENV = {
  ...
  podModulePrefix: 'frontend/pods',
  ...

Now we can start our backend from ember-django/backend folder

python manage.py runserver

And proxy our ember server to http://127.0.0.1:8000/ running the following from ember-django/frontend folder

ember s --proxy http://127.0.0.1:8000/

It’s that easy!

Step 2.3 Navigation

Let’s start with a simple navigation for the blog. We are going to create a ‘main-header’ component that will include ‘nav-bar’ and ‘sub-menu’ components. To not to get lost in that amount of directories and subdirectories we’ll use ember pods structure.

ember g component main-header --pod

and

ember g component main-header/nav-bar --pod

This will create a folder pods in our app directory. Each pod component will have component.js and template.hbs generated. Let’s also make it possible to add scss files so we could style our components.

ember install ember-component-css

To make it work you must app/styles/app.css to app/styles/app.scss and add one single import.

@import 'pod-styles';

Now we can style each component separately. Let’s create a styles.scss file in pods/components/main-header/nav-bar folder, and add this styling to it.

& {
    ul {
        list-style-type: none;
        margin: 0;
        padding: 0;
        overflow: hidden;
        border: 1px solid #e7e7e7;
        background-color: #f3f3f3;

        li:last-child {
            float: right;
        }

        li {
            float: left;

            a {
                display: block;
                color: #666;
                text-align: center;
                padding: 14px 16px;
                text-decoration: none;
            }

            a:hover:not(.active) {
                background-color: #ddd;
            }

            a.active {
                color: white;
                background-color: #4CAF50;
            }
        }
    }
}

And a template with our index and funture about and posts link.

<ul>
  <li>Home</li>
  <li>Posts</li>
  <li>About</li>
</ul>

Now we have to include nav-bar to the main-header component. Open main-header/template.hbs file and add the following.


And don’t forget to add our main-header component to the templates/application.hbs file.


  



Now you should be able to see a simple nav-bar and with one ‘Home’ button and an error in console saying that there’s no such routes.

Step 2.4 Routes

Now let’s add the missing about route!

ember g route about

And posts route in a separate folder.

ember g route posts/index

and

ember g route posts/new

Now if you start the server you should be able to navigate to Posts and About page.

Step 2.5 Model

Let’s generate a model for our posts.

ember g model post title:string body:string url:string

And add the following to the routes/posts/index.js

import Route from '@ember/routing/route';

export default Route.extend({
  model() {
    return this.store.findAll('post');
  }
});

Step 2.6 Posts

Now let’s create post-container component that will include post-card component and be a wrapper for all postcards we’ll have.

ember g component post-container --pod

and

ember g component post-container/post-card --pod

Let’s edit post-card component first. Add followinf to the template.hbs

<div class="card margin">
    <img src="" style="width:100%">
    <div class="container">
      <h3><b></b></h3>
    </div>
    <div class="container">
      <p></p>
    </div>
</div>

Now let’s style it - create file styles.scss and add

& {
  .card {
    box-shadow: 0 4px 10px 0 rgba(0,0,0,0.2), 0 4px 20px 0 rgba(0,0,0,0.19);
  }

  .margin{
    margin: 16px;
  }

  .container {
    padding: 0.01em 16px;
  }

  .opacity {
    opacity: 0.60;
  }
}

Now let’s edit post-container/template.hbs


And add a grid too. Create post-container/styles.scss

& {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  grid-gap: 20px;
  align-items: start;
}

We’ll aslo need a submenu for our navigation to access the list of posts and to create a new post.

Let’s generate new pod component.

ember g component main-header/sub-menu --pod

Add to sub-menu/template.hbs

<ul>
  <li>List</li>
  <li>New</li>
</ul>

And create sub-menu/styles.scss with the following code.

& {
    ul {
        list-style-type: none;
        margin: 10px 0px 20px 15px;
        padding: 0;
        overflow: hidden;

        li {
        border: 1px solid #e7e7e7;
        background-color: #f3f3f3;
            float: left;

            a {
                display: block;
                color: #666;
                text-align: center;
                padding: 14px 16px;
                text-decoration: none;
            }

            a:hover:not(.active) {
                background-color: #ddd;
            }

            a.active {
                color: white;
                background-color: #4CAF50;
            }
        }
    }
}

And of course don’t forget to include it in the main-header components template.hbs. Now it should look like this:


The last thing to do is to add our pod component to the template. So open file templates/posts/index.hbs and add the code.


  
    
  



Congratulations! Now you should be able to see your posts list.

Step 2.7 Posts Delete

Let’s add a delete button to our post-card pod component. I’ll use ember-concurrency task for doing that. First, let’s install it.

ember install ember-concurrency

Then add a delete button to the app/pods/components/post-container/post-card/template.hbs file.

<div class="card margin">
    <img src="" style="width:100%">
    <div class="container">
      <h3><b></b></h3>
    </div>
    <div class="container">
      <p></p>
    </div>
    <button type="button" onclick=>Delete</button>
</div>

Now let’s edit our component.js file.

import Component from '@ember/component';
import { task } from 'ember-concurrency';

export default Component.extend({
  model: null,

  deletePost: task(function* () {
    yield this.model.destroyRecord();
  })
});

Now you should be able to delete posts from the server.

Step 2.8 Posts New

Step 2.9 Posts Edit