django-rest-models


Namedjango-rest-models JSON
Version 3.0.1 PyPI version JSON
download
home_pagehttps://github.com/Yupeek/django-rest-models
Summarydjango Fake ORM model that query an RestAPI instead of a database —
upload_time2024-11-27 13:15:02
maintainerNone
docs_urlNone
authorDarius BERNARD
requires_pythonNone
licenseBSD
keywords django rest models api orm
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI
coveralls test coverage No coveralls.
            ==================
django-rest-models
==================

Allow to query a **django** RestAPI with same interface as the django ORM. **(the targeted API must use django-rest-framework + dynamic-rest libraries)**
In fact, it works like any other database engine. You add the rest_models engine in an alternate database and the rest_models databe router.
Then add APIMeta class to the models querying the API, voilà !

Stable branch

.. image:: https://img.shields.io/travis/Yupeek/django-rest-models/master.svg
    :target: https://travis-ci.org/Yupeek/django-rest-models

.. image:: https://readthedocs.org/projects/django-rest-models/badge/?version=latest
    :target: http://django-rest-models.readthedocs.org/en/latest/

.. image:: https://coveralls.io/repos/github/Yupeek/django-rest-models/badge.svg?branch=master
    :target: https://coveralls.io/github/Yupeek/django-rest-models?branch=master

.. image:: https://img.shields.io/pypi/v/django-rest-models.svg
    :target: https://pypi.python.org/pypi/django-rest-models
    :alt: Latest PyPI version

.. image:: https://requires.io/github/Yupeek/django-rest-models/requirements.svg?branch=master
     :target: https://requires.io/github/Yupeek/django-rest-models/requirements/?branch=master
     :alt: Requirements Status

Development status

.. image:: https://img.shields.io/travis/Yupeek/django-rest-models/develop.svg
    :target: https://travis-ci.org/Yupeek/django-rest-models

.. image:: https://coveralls.io/repos/github/Yupeek/django-rest-models/badge.svg?branch=develop
    :target: https://coveralls.io/github/Yupeek/django-rest-models?branch=develop

.. image:: https://requires.io/github/Yupeek/django-rest-models/requirements.svg?branch=develop
     :target: https://requires.io/github/Yupeek/django-rest-models/requirements/?branch=develop
     :alt: Requirements Status


Installation
------------

1. Install using pip:

   ``pip install django-rest-models``

2. Alternatively, you can download or clone this repo and install with :

    ``pip install -e .``.

Requirements
------------

This database wrapper work with

- python 3.9, 3.10, 3.11, 3.12
- django 4.2, 5.0, 5.1

On the api, this is tested against

- django-rest-framework 3.14, 3.15
- dynamic-rest-bse 2.4 (It's a fork because dynamic-rest is not compatible with django 4.2 at this day)


Examples
--------

settings.py:

.. code-block:: python

    DATABASES = {
        'default': {
            ...
        },
        'api': {
            'ENGINE': 'rest_models.backend',
            'NAME': 'https://requestb.in/',
            'USER': 'userapi',
            'PASSWORD': 'passwordapi',
            'AUTH': 'rest_models.backend.auth.BasicAuth',
        },
    }

    DATABASE_ROUTERS = [
        'rest_models.router.RestModelRouter',
    ]


models.py:

.. code-block:: python

    class MyModel(models.Model):
        field = models.IntegerField()
        ...

        class Meta:
            # basic django meta Stuff
            verbose_name = 'my model'

        # the only customisation that make this model special
        class APIMeta:
            pass


    class MyOtherModel(models.Model):
        other_field = models.IntegerField()
        first_model = models.ForeignKey(MyModel, db_column='mymodel')
        ...

        class Meta:
            # basic django meta Stuff
            verbose_name = 'my other model'

        # the only customisation that make this model special
        class APIMeta:
            pass



Targeted API requirements
-------------------------

To allow this database adapter to work like a relational one, the targeted API must respect some requirements :

- dynamic-rest installed and all serializers/views must respectively inherit from Dynamic* (DynamicModelSerializer, etc...)

Each API serializer must :

- Provide the id field
- Provide the related field (ManyToMany and ForeignKey on Models) as DynamicRelationField
- Provide the reverse related field. We must, for each ForeignKey and ManyToMany, add a field on the related model's
  serializer.

.. code-block:: python

    class MenuSerializer(DynamicModelSerializer):
        pizzas = DynamicRelationField('PizzaSerializer', many=True)     # Menu.pizza = ManyToMany

        class Meta:
            model = Menu
            name = 'menu'
            fields = ('id', 'code', 'name', 'pizzas')
            deferred_fields = ('pizza_set', )


    class PizzaSerializer(DynamicModelSerializer):

        toppings = DynamicRelationField(ToppingSerializer, many=True)
        menu = DynamicRelationField(MenuSerializer)                     # Add this because Menu.pizza = ManyToMany

        class Meta:
            model = Pizza
            name = 'pizza'
            fields = ('id', 'name', 'price', 'from_date', 'to_date', 'toppings', 'menu')

django-rest-models provide a way to check the consistency of the api with the local models via the django check framework.
At each startup, it will query the api with OPTIONS to check if the local models match the remote serializers.


Caveats
-------

Since this is not a real relational database, all feature cannot be implemented. Some limitations are inherited by
dynamic-rest filtering system too.

- Aggregations : is not implemented on the api endpoint, maybe in future releases
- Complex filtering using OR : all filter passed to dynamic-rest is ANDed together, so no OR is possible
- Negated AND in filtering: a negated AND give a OR, so previous limitation apply
- Negated OR in filtering: since the compitation of nested filter is complexe and error prone, we disable all OR. in
  fact, only some nested of AND is accepted. only the final value of the Q() object can be negated

    for short, you **CANNOT** :

.. code-block:: python


        Pizza.objects.aggregate()
        Pizza.objects.annotate()
        Pizza.objects.filter(Q(..) | Q(..))
        Pizza.objects.exclude(Q(..) & Q(..))
        Pizza.objects.exclude(Q(..) | Q(..))

    but you can :

.. code-block:: python

        Pizza.objects.create
        Pizza.objects.bulk_create
        Pizza.objects.update
        Pizza.objects.bulk_update
        Pizza.objects.select_related
        Pizza.objects.prefetch_related
        Pizza.objects.values
        Pizza.objects.values_list
        Pizza.objects.delete
        Pizza.objects.count()
        Pizza.objects.filter(..., ..., ...)
        Pizza.objects.filter(...).filter(...).exclude(...)
        Pizza.objects.exclude(..., ...).exclude(...)
        Pizza.objects.filter(Q(..) & Q(..))
        Pizza.objects.none()
        pizza.toppings.add(...)
        pizza.toppings.remove(...)
        pizza.toppings.set(...)
        pizza.toppings.clear(...)

.. note::

    prefetch_related work as expected, but the performance is readly bad. As a matter of fact, a ``Pizza.objects.prefetch_related('toppings')``
    will query the toppings for all pizzas as expected, but the query to recover the pizza will contains the linked pizza in the response.
    If the database contains a great number of pizzas for the given toppings, the response will contains them all, even if it's
    useless at first glance, the linked pizza for each topping is mandotary to django to glue topping <=> pizza relationships.

    So, be careful when using prefetch_related.



Specific behaviour
---------------------

Some specific behaviour has been implemented to use the extra feature of a Rest API :

- When inserting, the resulting model is returned by the API. the inserted model is updated with the resulting values.
  This imply 2 things:

  * If you provided default values for fields in the api, these data will be populated into your created instance if it was ommited.
  * If the serializer have some computed data, its data will always be used as a replacement of the one you gave to your
    models. (cf example: Pizza.cost which is the sum of the cost of the toppling. after each save, its value will be updated)


Support
-------

This database api support :

- select_related
- order_by
- only
- defer
- filter
- exclude
- delete
- update
- create
- bulk create (with retrive of pk)
- ManyToManyField
- ForeignKey*

.. note::

    ForeignKey must have db_column fixed to the name of the reflected field in the api. or all update/create won't use
    the value if this field

.. note::

		Support for ForeignKey is only available with models on the same database (api<->api) or (default<->default).
		It's not possible to add a ForeignKey/ManyToMany field on a local model related to a remote model (with ApiMeta)

OAuthToken auth backend
-----------------------

grant_type is provided to the API get_token view by a GET parameter.

Recent framework updates like Django OAuth Toolkit enforce that no GET parameters are used.

Use ENFORCE_POST setting in OPTIONS of api's DATABASE :

.. code-block:: python

    DATABASES = {
        'default': {
            ...
        },
        'api': {
            ...
            'OPTIONS': {
                'OAUTH_URL': '/oauth2/token/',
                'ENFORCE_POST': True,
            }
        },
    }


Documentation
-------------

The full documentation is at http://django-rest-models.readthedocs.org/en/latest/.


Requirements
------------

- Python 3.9, 3.10, 3.11, 3.12
- Django >= 4.2

Contributions and pull requests are welcome.


Bugs and requests
-----------------

If you found a bug or if you have a request for additional feature, please use the issue tracker on GitHub.

https://github.com/Yupeek/django-rest-models/issues

known limitations
-----------------

JSONField from postgresql and mysql is supported by django-rest-models, but not by the current dynamic-rest (1.8.1)
so you can do `MyModel.objects.filter(myjson__mydata__contains='aaa')` but it will work if drest support it

same for DateField's year,month,day lookup.

License
-------

You can use this under GPLv3.

Author
------

Original author: `Darius BERNARD <https://github.com/ornoone>`_.
Contributor: `PaulWay <https://github.com/PaulWay>`_.


Thanks
------

Thanks to django for this amazing framework.



            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/Yupeek/django-rest-models",
    "name": "django-rest-models",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": "django rest models API ORM",
    "author": "Darius BERNARD",
    "author_email": "darius@yupeek.com",
    "download_url": "https://files.pythonhosted.org/packages/7e/89/cbc9cabdd5af501e135e4f0714c93490a75a8be5a79d982944a7fabb5dd0/django-rest-models-3.0.1.tar.gz",
    "platform": null,
    "description": "==================\ndjango-rest-models\n==================\n\nAllow to query a **django** RestAPI with same interface as the django ORM. **(the targeted API must use django-rest-framework + dynamic-rest libraries)**\nIn fact, it works like any other database engine. You add the rest_models engine in an alternate database and the rest_models databe router.\nThen add APIMeta class to the models querying the API, voil\u00e0 !\n\nStable branch\n\n.. image:: https://img.shields.io/travis/Yupeek/django-rest-models/master.svg\n    :target: https://travis-ci.org/Yupeek/django-rest-models\n\n.. image:: https://readthedocs.org/projects/django-rest-models/badge/?version=latest\n    :target: http://django-rest-models.readthedocs.org/en/latest/\n\n.. image:: https://coveralls.io/repos/github/Yupeek/django-rest-models/badge.svg?branch=master\n    :target: https://coveralls.io/github/Yupeek/django-rest-models?branch=master\n\n.. image:: https://img.shields.io/pypi/v/django-rest-models.svg\n    :target: https://pypi.python.org/pypi/django-rest-models\n    :alt: Latest PyPI version\n\n.. image:: https://requires.io/github/Yupeek/django-rest-models/requirements.svg?branch=master\n     :target: https://requires.io/github/Yupeek/django-rest-models/requirements/?branch=master\n     :alt: Requirements Status\n\nDevelopment status\n\n.. image:: https://img.shields.io/travis/Yupeek/django-rest-models/develop.svg\n    :target: https://travis-ci.org/Yupeek/django-rest-models\n\n.. image:: https://coveralls.io/repos/github/Yupeek/django-rest-models/badge.svg?branch=develop\n    :target: https://coveralls.io/github/Yupeek/django-rest-models?branch=develop\n\n.. image:: https://requires.io/github/Yupeek/django-rest-models/requirements.svg?branch=develop\n     :target: https://requires.io/github/Yupeek/django-rest-models/requirements/?branch=develop\n     :alt: Requirements Status\n\n\nInstallation\n------------\n\n1. Install using pip:\n\n   ``pip install django-rest-models``\n\n2. Alternatively, you can download or clone this repo and install with :\n\n    ``pip install -e .``.\n\nRequirements\n------------\n\nThis database wrapper work with\n\n- python 3.9, 3.10, 3.11, 3.12\n- django 4.2, 5.0, 5.1\n\nOn the api, this is tested against\n\n- django-rest-framework 3.14, 3.15\n- dynamic-rest-bse 2.4 (It's a fork because dynamic-rest is not compatible with django 4.2 at this day)\n\n\nExamples\n--------\n\nsettings.py:\n\n.. code-block:: python\n\n    DATABASES = {\n        'default': {\n            ...\n        },\n        'api': {\n            'ENGINE': 'rest_models.backend',\n            'NAME': 'https://requestb.in/',\n            'USER': 'userapi',\n            'PASSWORD': 'passwordapi',\n            'AUTH': 'rest_models.backend.auth.BasicAuth',\n        },\n    }\n\n    DATABASE_ROUTERS = [\n        'rest_models.router.RestModelRouter',\n    ]\n\n\nmodels.py:\n\n.. code-block:: python\n\n    class MyModel(models.Model):\n        field = models.IntegerField()\n        ...\n\n        class Meta:\n            # basic django meta Stuff\n            verbose_name = 'my model'\n\n        # the only customisation that make this model special\n        class APIMeta:\n            pass\n\n\n    class MyOtherModel(models.Model):\n        other_field = models.IntegerField()\n        first_model = models.ForeignKey(MyModel, db_column='mymodel')\n        ...\n\n        class Meta:\n            # basic django meta Stuff\n            verbose_name = 'my other model'\n\n        # the only customisation that make this model special\n        class APIMeta:\n            pass\n\n\n\nTargeted API requirements\n-------------------------\n\nTo allow this database adapter to work like a relational one, the targeted API must respect some requirements :\n\n- dynamic-rest installed and all serializers/views must respectively inherit from Dynamic* (DynamicModelSerializer, etc...)\n\nEach API serializer must :\n\n- Provide the id field\n- Provide the related field (ManyToMany and ForeignKey on Models) as DynamicRelationField\n- Provide the reverse related field. We must, for each ForeignKey and ManyToMany, add a field on the related model's\n  serializer.\n\n.. code-block:: python\n\n    class MenuSerializer(DynamicModelSerializer):\n        pizzas = DynamicRelationField('PizzaSerializer', many=True)     # Menu.pizza = ManyToMany\n\n        class Meta:\n            model = Menu\n            name = 'menu'\n            fields = ('id', 'code', 'name', 'pizzas')\n            deferred_fields = ('pizza_set', )\n\n\n    class PizzaSerializer(DynamicModelSerializer):\n\n        toppings = DynamicRelationField(ToppingSerializer, many=True)\n        menu = DynamicRelationField(MenuSerializer)                     # Add this because Menu.pizza = ManyToMany\n\n        class Meta:\n            model = Pizza\n            name = 'pizza'\n            fields = ('id', 'name', 'price', 'from_date', 'to_date', 'toppings', 'menu')\n\ndjango-rest-models provide a way to check the consistency of the api with the local models via the django check framework.\nAt each startup, it will query the api with OPTIONS to check if the local models match the remote serializers.\n\n\nCaveats\n-------\n\nSince this is not a real relational database, all feature cannot be implemented. Some limitations are inherited by\ndynamic-rest filtering system too.\n\n- Aggregations : is not implemented on the api endpoint, maybe in future releases\n- Complex filtering using OR : all filter passed to dynamic-rest is ANDed together, so no OR is possible\n- Negated AND in filtering: a negated AND give a OR, so previous limitation apply\n- Negated OR in filtering: since the compitation of nested filter is complexe and error prone, we disable all OR. in\n  fact, only some nested of AND is accepted. only the final value of the Q() object can be negated\n\n    for short, you **CANNOT** :\n\n.. code-block:: python\n\n\n        Pizza.objects.aggregate()\n        Pizza.objects.annotate()\n        Pizza.objects.filter(Q(..) | Q(..))\n        Pizza.objects.exclude(Q(..) & Q(..))\n        Pizza.objects.exclude(Q(..) | Q(..))\n\n    but you can :\n\n.. code-block:: python\n\n        Pizza.objects.create\n        Pizza.objects.bulk_create\n        Pizza.objects.update\n        Pizza.objects.bulk_update\n        Pizza.objects.select_related\n        Pizza.objects.prefetch_related\n        Pizza.objects.values\n        Pizza.objects.values_list\n        Pizza.objects.delete\n        Pizza.objects.count()\n        Pizza.objects.filter(..., ..., ...)\n        Pizza.objects.filter(...).filter(...).exclude(...)\n        Pizza.objects.exclude(..., ...).exclude(...)\n        Pizza.objects.filter(Q(..) & Q(..))\n        Pizza.objects.none()\n        pizza.toppings.add(...)\n        pizza.toppings.remove(...)\n        pizza.toppings.set(...)\n        pizza.toppings.clear(...)\n\n.. note::\n\n    prefetch_related work as expected, but the performance is readly bad. As a matter of fact, a ``Pizza.objects.prefetch_related('toppings')``\n    will query the toppings for all pizzas as expected, but the query to recover the pizza will contains the linked pizza in the response.\n    If the database contains a great number of pizzas for the given toppings, the response will contains them all, even if it's\n    useless at first glance, the linked pizza for each topping is mandotary to django to glue topping <=> pizza relationships.\n\n    So, be careful when using prefetch_related.\n\n\n\nSpecific behaviour\n---------------------\n\nSome specific behaviour has been implemented to use the extra feature of a Rest API :\n\n- When inserting, the resulting model is returned by the API. the inserted model is updated with the resulting values.\n  This imply 2 things:\n\n  * If you provided default values for fields in the api, these data will be populated into your created instance if it was ommited.\n  * If the serializer have some computed data, its data will always be used as a replacement of the one you gave to your\n    models. (cf example: Pizza.cost which is the sum of the cost of the toppling. after each save, its value will be updated)\n\n\nSupport\n-------\n\nThis database api support :\n\n- select_related\n- order_by\n- only\n- defer\n- filter\n- exclude\n- delete\n- update\n- create\n- bulk create (with retrive of pk)\n- ManyToManyField\n- ForeignKey*\n\n.. note::\n\n    ForeignKey must have db_column fixed to the name of the reflected field in the api. or all update/create won't use\n    the value if this field\n\n.. note::\n\n\t\tSupport for ForeignKey is only available with models on the same database (api<->api) or (default<->default).\n\t\tIt's not possible to add a ForeignKey/ManyToMany field on a local model related to a remote model (with ApiMeta)\n\nOAuthToken auth backend\n-----------------------\n\ngrant_type is provided to the API get_token view by a GET parameter.\n\nRecent framework updates like Django OAuth Toolkit enforce that no GET parameters are used.\n\nUse ENFORCE_POST setting in OPTIONS of api's DATABASE :\n\n.. code-block:: python\n\n    DATABASES = {\n        'default': {\n            ...\n        },\n        'api': {\n            ...\n            'OPTIONS': {\n                'OAUTH_URL': '/oauth2/token/',\n                'ENFORCE_POST': True,\n            }\n        },\n    }\n\n\nDocumentation\n-------------\n\nThe full documentation is at http://django-rest-models.readthedocs.org/en/latest/.\n\n\nRequirements\n------------\n\n- Python 3.9, 3.10, 3.11, 3.12\n- Django >= 4.2\n\nContributions and pull requests are welcome.\n\n\nBugs and requests\n-----------------\n\nIf you found a bug or if you have a request for additional feature, please use the issue tracker on GitHub.\n\nhttps://github.com/Yupeek/django-rest-models/issues\n\nknown limitations\n-----------------\n\nJSONField from postgresql and mysql is supported by django-rest-models, but not by the current dynamic-rest (1.8.1)\nso you can do `MyModel.objects.filter(myjson__mydata__contains='aaa')` but it will work if drest support it\n\nsame for DateField's year,month,day lookup.\n\nLicense\n-------\n\nYou can use this under GPLv3.\n\nAuthor\n------\n\nOriginal author: `Darius BERNARD <https://github.com/ornoone>`_.\nContributor: `PaulWay <https://github.com/PaulWay>`_.\n\n\nThanks\n------\n\nThanks to django for this amazing framework.\n\n\n",
    "bugtrack_url": null,
    "license": "BSD",
    "summary": "django Fake ORM model that query an RestAPI instead of a database \u2014",
    "version": "3.0.1",
    "project_urls": {
        "Homepage": "https://github.com/Yupeek/django-rest-models"
    },
    "split_keywords": [
        "django",
        "rest",
        "models",
        "api",
        "orm"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e8169d68768e8e9cc2167c7a3dc3bc77ed7430630e454340f5512d841f2d7155",
                "md5": "c073261c77e869489aeb1c1c7eb09dfc",
                "sha256": "9c08af3e1597c59298ef7a1efb5cc4c3eafd0b26f3797d003c9f444426d1a6ea"
            },
            "downloads": -1,
            "filename": "django_rest_models-3.0.1-py2.py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "c073261c77e869489aeb1c1c7eb09dfc",
            "packagetype": "bdist_wheel",
            "python_version": "py2.py3",
            "requires_python": null,
            "size": 48725,
            "upload_time": "2024-11-27T13:14:59",
            "upload_time_iso_8601": "2024-11-27T13:14:59.422169Z",
            "url": "https://files.pythonhosted.org/packages/e8/16/9d68768e8e9cc2167c7a3dc3bc77ed7430630e454340f5512d841f2d7155/django_rest_models-3.0.1-py2.py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7e89cbc9cabdd5af501e135e4f0714c93490a75a8be5a79d982944a7fabb5dd0",
                "md5": "079e76df19ccea9c059c305710a01f49",
                "sha256": "e448af76dca11c07925ac893ac02c9a494e6ed9c66d714a27d0cf1fa8f7b7179"
            },
            "downloads": -1,
            "filename": "django-rest-models-3.0.1.tar.gz",
            "has_sig": false,
            "md5_digest": "079e76df19ccea9c059c305710a01f49",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 44144,
            "upload_time": "2024-11-27T13:15:02",
            "upload_time_iso_8601": "2024-11-27T13:15:02.207110Z",
            "url": "https://files.pythonhosted.org/packages/7e/89/cbc9cabdd5af501e135e4f0714c93490a75a8be5a79d982944a7fabb5dd0/django-rest-models-3.0.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-11-27 13:15:02",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "Yupeek",
    "github_project": "django-rest-models",
    "travis_ci": true,
    "coveralls": false,
    "github_actions": false,
    "requirements": [],
    "test_requirements": [
        {
            "name": "teamcity-messages",
            "specs": []
        },
        {
            "name": "unidecode",
            "specs": []
        }
    ],
    "tox": true,
    "lcname": "django-rest-models"
}
        
Elapsed time: 0.41971s