Staying DRY(er)

By Emmanuelle Delescolle

when working with Djang and frontend frameworks/libraries

Who am I?

  • An average developper
  • Who loves working with Django
  • A woman who codes
  • Someone prone to burnouts
  • Works at LevIT

Disclaimer

You know what this means

💖 DJango Admin 💖

from django.contrib import admin

from .models import Product, Category


@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    pass


@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    pass

API and frontend

from restframework import viewsets, serializers

from .model import Product, Category


class ProductSerializer(serializers.ModelSerializer):

    class Meta:
        model = Product
        fields = ('id', 'name', 'category', 'in-stock')

class ProductViewSet(viewsets.ModelViewSet):

    serializer_class = ProductSerializer
    queryset = Product.objects.all()


class CategorySerializer(serializers.ModelSerializer):

    class Meta:
        model = Category
        fields = ('id', 'name', 'products')

class CategoryViewSet(viewsets.ModelViewSet):

    serializer_class = CategorySerializer
    queryset = Category.objects.all()
import DS from 'ember-data';

export default DS.Model.extend({
  name: DS.attr('string'),
  category: DS.belongsTo('category', {
    async: true,
    inverse: 'products',
  }),
  in_stock: DS.attr('number'),
});
from catalog.api import ProductViewSet, CategoryViewSet

router.register(ProductViewSet)
router.register(CategoryViewSet)
import DS from 'ember-data';

export default DS.Model.extend({
  name: DS.attr('string'),
  products: DS.hasMany('product', {
    async: true,
    inverse: 'category',
  }),
});



from .models import Product, Category


@register
class ProductEndpoint(Endpoint):
    model = Product


router.register(Category)
from drf_auto_endpoint.router import router, register
from drf_auto_endpoint.endpoints import Endpoint
$ magically export --all
$ ./manage.py export --all

It's not magic,
it's DRF-schema-adapter

Fast prototyping/concise code

The Endpoint class

from drf_auto_endpoint.endpoints import Endpoint

from .models import Product


class ProductEndpoint(Endpoint):
    
    model = Product
    filter_fields = ('category_id', )
    search_fields = ('name', )
    ordering_fields = ('in_stock', )
from drf_auto_endpoint import Endpoint
from rest_framework import viewsets

from .models import Product
from .serializers import ProductSerializer


class ProductViewSet(viewsets.ModelViewSet):

    def create(self, request, *args, **kwargs):
        # do something fancy
        return super(ProductViewSet, self) \
            .create(request, *args, **kwargs)


class ProductEndpoint(Endpoint):
    
    model = Product
    base_viewset = ProductViewSet
    base_serializer = ProductSerializer

Fast prototyping/concise code

Router and auto-discovery

from drf_auto_endpoint.endpoint import Endpoint
from drf_auto_endpoint.router import register, router

from .models import Product, Category


@register
class ProductEndpoint(Endpoint):
    
    model = Product
    filter_fields = ('category_id', )
    search_fields = ('name', )
    ordering_fields = ('in_stock', )


router.register(Category)

catalog/endpoints.py

from django.conf.urls import include, url

from drf_auto_endpoint.router import router


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

project/urls.py

INSTALLED_APPS = (
    ...
    'drf_auto_endpoint',
)

settings.py

Demo

Ok, but what about my frontend?

INSTALLED_APPS = (
    ...
    'export_app',
)


EXPORTER_ADAPTER = 'export_app.adapters.MobxAxiosAdapter'
EXPORTER_ROUTER_PATH = 'react_ango.api_urls.router'
EXPORTER_FRONT_APPLICATION_PATH = os.path.join(BASE_DIR, '..', 'front', 'app')

settings.py

$ ./manage.py export --all
front/app/stores
├── _base.js
├── todolist.js
└── todotask.js

front/app/models
├── base
│   ├── _base.js
│   ├── todolist.js
│   └── todotask.js
├── todolist.js
└── todotask.js

What's in there?

import TodoTaskBase from './base/todotask';

class TodoTask extends TodoTaskBase {}

export default TodoTask;
import todoTaskStore from '../../stores/todotask';
import Model from './_base';   
import { observable } from 'mobx';
              
class TodoTaskBase extends Model {  
          
  store = todoTaskStore;
              
  @observable id;
  @observable description;
  @observable done;
  @observable __str__;
  @observable lst;
}

export default TodoTaskBase;
import Store from './_base';
import TodoTask from '../models/todotask';

export class TodoTaskStore extends Store {
  endpoint = 'todo/tasks';
  result = 'results';

  transform(record) {
    return new TodoTask(record);
  }

};

export default new TodoTaskStore;

front/app/models/todotask.js

front/app/models/base/todotask.js

front/app/stores/todotask.js

Demo

We are still missing information to be able to build a form

DRF_AUTO_METADATA_ADAPTER = \
    'drf_auto_endpoint.adapters.AngularFormlyAdapter'

settings.py

$ ./manage.py export --adapter_name MetadataAdapter --all
$ ./manage.py export --adapter_name MetadataES6Adapter --all

Demo

Building custom adapters

How complex can we get?

Demo

More?

Come find me in the hallways or during the sprints!

Questions

Links

Staying DRY(er) when working with Django and frontend libraries/frameworks

By Emma

Staying DRY(er) when working with Django and frontend libraries/frameworks

DjangoCon Europe 2017 talk

  • 2,432