django-rest-witchcraft 0.12.1
Django REST Framework and SQLAlchemy integration
License: MIT
Keywords: sqlalchemy django rest framework drf rest_framework

Django REST Witchcraft
======================

|Build Status| |Read The Docs| |PyPI version| |Coveralls Status| |Black|

**Django REST Framework integration with SQLAlchemy**

django-rest-witchcraft is an extension for Django REST Framework that adds support for SQLAlchemy. It aims to provide
a similar development experience to building REST api's with Django REST Framework with Django ORM, except with
SQLAlchemy.

Installation
============

::

    pip install django-rest-witchcraft

Quick Start
===========

First up, lets define some simple models:

.. code:: python

    import sqlalchemy as sa
    import sqlalchemy.orm  # noqa
    from sqlalchemy.ext.declarative import declarative_base

    engine = sa.create_engine('sqlite:///:memory:', echo=True)
    session = sa.orm.scoped_session(sa.orm.sessionmaker(bind=engine))

    Base = declarative_base()
    Base.query = session.query_property()


    class Group(Base):
        __tablename__ = 'groups'

        id = sa.Column(sa.Integer(), primary_key=True, autoincrement=True)
        name = sa.Column(sa.String())


    class User(Base):
        __tablename__ = 'users'

        id = sa.Column(sa.Integer(), primary_key=True, autoincrement=True)
        name = sa.Column(sa.String())
        fullname = sa.Column(sa.String())
        password = sa.Column(sa.String())

        _group_id = sa.Column('group_id', sa.Integer(), sa.ForeignKey('groups.id'))
        group = sa.orm.relationship(Group, backref='users')


    class Address(Base):
        __tablename__ = 'addresses'

        id = sa.Column(sa.Integer(), primary_key=True, autoincrement=True)
        email_address = sa.Column(sa.String(), nullable=False)

        _user_id = sa.Column(sa.Integer(), sa.ForeignKey('users.id'))
        user = sa.orm.relationship(User, backref='addresses')

    Base.metadata.create_all(engine)


Nothing fancy here, we have a ``User`` class that can belongs to a ``Group`` instance and has many ``Address``
instances

This serializer can handle nested create, update or partial update operations.

Lets define a serializer for ``User`` with all the fields:

.. code:: python

    class UserSerializer(serializers.ModelSerializer):

        class Meta:
            model = User
            session = session
            fields = '__all__'

This will create the following serializer for us:

::

    >>> serializer = UserSerializer()

    >>> serializer
    UserSerializer():
        id = IntegerField(allow_null=False, help_text=None, label='Id', required=True)
        name = CharField(allow_null=True, help_text=None, label='Name', max_length=None, required=False)
        fullname = CharField(allow_null=True, help_text=None, label='Fullname', max_length=None, required=False)
        password = CharField(allow_null=True, help_text=None, label='Password', max_length=None, required=False)
        group = GroupSerializer(allow_null=True, is_nested=True, required=False):
            id = IntegerField(allow_null=False, help_text=None, label='Id', required=False)
            name = CharField(allow_null=True, help_text=None, label='Name', max_length=None, required=False)
        addresses = AddressSerializer(allow_null=True, many=True, required=False):
            id = IntegerField(allow_null=False, help_text=None, label='Id', required=False)
            email_address = CharField(allow_null=False, help_text=None, label='Email_address', max_length=None, required=True)
        url = UriField(read_only=True)

Lets try to create a ``User`` instance with our brand new serializer:

.. code:: python

    serializer = UserSerializer(data={
        'name': 'shosca',
        'password': 'swordfish',
    })
    serializer.is_valid()
    serializer.save()

    user = serializer.instance

This will create the following user for us:

::

    >>> user
    User(_group_id=None, id=1, name='shosca', fullname=None, password='swordfish')

Lets try to update our user ``User`` instance and change its password:

.. code:: python

    serializer = UserSerializer(user, data={
        'name': 'shosca',
        'password': 'password',
    })
    serializer.is_valid()
    serializer.save()

    user = serializer.instance

Our user now looks like:

::

    >>> user
    User(_group_id=None, id=1, name='shosca', fullname=None, password='password')

Lets try to update our ``User`` instance again, but this time lets change its password only:

.. code:: python

    serializer = UserSerializer(user, data={
        'password': 'swordfish',
    }, partial=True)
    serializer.is_valid()
    serializer.save()

    user = serializer.instance

This will update the following user for us:

::

    >>> user
    User(_group_id=None, id=1, name='shosca', fullname=None, password='swordfish')

Our user does not belong to a ``Group``, lets fix that:

.. code:: python

    group = Group(name='Admin')
    session.add(group)
    session.flush()

    serializer = UserSerializer(user, data={
        'group': {'id': group.id}
    })
    serializer.is_valid()
    serializer.save()

    user = serializer.instance

Now, our user looks like:

::

    >>> user
    User(_group_id=1, id=1, name='shosca', fullname=None, password='swordfish')

    >>> user.group
    Group(id=1, name='Admin')

We can also change the name of our user's group through the user using nested updates:

.. code:: python

    class UserSerializer(serializers.ModelSerializer):

        class Meta:
            model = User
            session = session
            fields = '__all__'
            extra_kwargs = {
                'group': {'allow_nested_updates': True}
            }

    serializer = UserSerializer(user, data={
        'group': {'name': 'Super User'}
    }, partial=True)
    serializer.is_valid()

    user = serializer.save()

Now, our user looks like:

::

    >>> user
    User(_group_id=1, id=1, name='shosca', fullname=None, password='swordfish')

    >>> user.group
    Group(id=1, name='Super User')

We can use this serializer in a viewset like:

.. code:: python

    from rest_witchcraft import viewsets

    class UserViewSet(viewsets.ModelViewSet):
        queryset = User.query
        serializer_class = UserSerializer

And we can register this viewset in our ``urls.py`` like:

.. code:: python

    from rest_witchcraft import routers

    router = routers.DefaultRouter()
    router.register(r'users', UserViewSet)

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


.. |Build Status| image:: https://github.com/shosca/django-rest-witchcraft/workflows/Build/badge.svg?branch=master
   :target: https://github.com/shosca/django-rest-witchcraft/actions?query=workflow%3ABuild+branch%3Amaster
.. |Read The Docs| image:: https://readthedocs.org/projects/django-rest-witchcraft/badge/?version=latest
   :target: http://django-rest-witchcraft.readthedocs.io/en/latest/?badge=latest
.. |PyPI version| image:: https://badge.fury.io/py/django-rest-witchcraft.svg
   :target: https://badge.fury.io/py/django-rest-witchcraft
.. |Coveralls Status| image:: https://coveralls.io/repos/github/shosca/django-rest-witchcraft/badge.svg?branch=master
   :target: https://coveralls.io/github/shosca/django-rest-witchcraft?branch=master
.. |Black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
   :target: https://github.com/ambv/black