Skip to content

Commit

Permalink
Merge pull request #86 from Yupeek/develop
Browse files Browse the repository at this point in the history
release-3.0.0
  • Loading branch information
seljin authored Nov 25, 2024
2 parents 22d3dca + 0728c1d commit 5f85656
Show file tree
Hide file tree
Showing 24 changed files with 239 additions and 104 deletions.
37 changes: 31 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ Requirements

This database wrapper work with

- python 3.6, 3.7
- django 2.0, 2.1, 2.2, 3.0, 3.1, 3.2
- 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.11, 3.12, 3.13
- dynamic-rest 2.1
- 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
Expand Down Expand Up @@ -259,6 +259,31 @@ This database api support :
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
-------------

Expand All @@ -268,8 +293,8 @@ The full documentation is at http://django-rest-models.readthedocs.org/en/latest
Requirements
------------

- Python 2.7, 3.5
- Django >= 1.8
- Python 3.9, 3.10, 3.11, 3.12
- Django >= 4.2

Contributions and pull requests are welcome.

Expand Down
8 changes: 4 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
Django<3.3
Django<5.2
wheel
sphinx
tox
isort
flake8
requests
djangorestframework<3.14
dynamic-rest<2.2
djangorestframework<3.16
dynamic-rest-bse<2.5
-r test_requirements.txt
twine
psycopg2-binary
Pillow<11
Pillow<12
unidecode
2 changes: 1 addition & 1 deletion rest_models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__VERSION__ = '2.1.1'
__VERSION__ = '3.0.0'

try:
from rest_models.checks import register_checks
Expand Down
18 changes: 13 additions & 5 deletions rest_models/backend/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,23 @@ def get_token(self):
:return: the token from the api
:rtype: Token
"""
conn = self.databasewrapper.cursor()
params = {'grant_type': 'client_credentials'}
if self.settings_dict.get('OPTIONS', {}).get('ENFORCE_POST', False):
kw = 'data'
else:
logger.warning("Get oauth token: Using deprecated GET parameter, will be removed in future releases."
"use ENFORCE_POST to fix this (cf. djang-rest-models README)")
kw = 'params'
_kwargs = {
'auth': (self.settings_dict['USER'], self.settings_dict['PASSWORD']),
'stream': False,
kw: {'grant_type': 'client_credentials'}
}
# Get client credentials params
conn = self.databasewrapper.cursor()
response = conn.session.request(
'POST',
self.url_token,
params=params,
auth=(self.settings_dict['USER'], self.settings_dict['PASSWORD']),
stream=False
**_kwargs
)
if response.status_code != 200:
raise ProgrammingError("unable to retrive the oauth token from %s: %s" %
Expand Down
21 changes: 14 additions & 7 deletions rest_models/backend/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from django.db.models import FileField, Transform
from django.db.models.aggregates import Count
from django.db.models.base import ModelBase
from django.db.models.expressions import Col, RawSQL
from django.db.models.expressions import Col, RawSQL, Value
from django.db.models.fields.related_lookups import RelatedExact, RelatedIn
from django.db.models.lookups import Exact, In, IsNull, Lookup, Range
from django.db.models.sql.compiler import SQLCompiler as BaseSQLCompiler
Expand Down Expand Up @@ -60,8 +60,8 @@ def extract_exact_pk_value(where):
exact, isnull = where.children

if (
isinstance(exact, Exact) and isinstance(isnull, IsNull) and
exact.lhs.target == isnull.lhs.target
isinstance(exact, Exact) and isinstance(isnull, IsNull) and
exact.lhs.target == isnull.lhs.target
):
return exact
return None
Expand Down Expand Up @@ -379,7 +379,12 @@ def get_resources_for_cols(self, cols):
:return: 2 sets, the first if the alias useds, the 2nd is the set of the full path of the resources, with the
attributes
"""
resolved = [self.resolve_path(col) for col in cols if not isinstance(col, RawSQL) or col.sql != '1']
resolved = [
self.resolve_path(col)
for col in cols
if (not isinstance(col, RawSQL) or col.sql != '1')
and (not isinstance(col, Value) or col.value != 1) # skip special cases with exists()
]

return (
set(r[0] for r in resolved), # set of tuple of Alias successives
Expand Down Expand Up @@ -646,14 +651,15 @@ class SQLCompiler(BaseSQLCompiler):

META_NAME = 'meta'

def __init__(self, query, connection, using):
def __init__(self, query, connection, using, elide_empty=True):
"""
:param django.db.models.sql.query.Query query: the query
:param rest_models.backend.base.DatabaseWrapper connection: the connection
:param str using:
"""
self.query = query
self.connection = connection
self.elide_empty = elide_empty
self.using = using
self.quote_cache = {'*': '*'}
# The select, klass_info, and annotations are needed by QuerySet.iterator()
Expand Down Expand Up @@ -948,7 +954,8 @@ def response_to_table(self, responsereader, item):
resolved = [
self.query_parser.resolve_path(col)
for col, _, _ in self.select
if not isinstance(col, RawSQL) or col.sql != '1' # skip special case with exists()
if (not isinstance(col, RawSQL) or col.sql != '1')
and (not isinstance(col, Value) or col.value != 1) # skip special cases with exists()
]
if not resolved:
# nothing in select. special case in exists()
Expand Down Expand Up @@ -1226,7 +1233,7 @@ def execute_sql(self, return_id=False, chunk_size=None):
result = [result_json[get_resource_name(query.model, many=False)][opts.pk.column]]
elif django.VERSION < (3, 0):
return result
return (result, )
return (result,)


class FakeCursor(object):
Expand Down
2 changes: 2 additions & 0 deletions rest_models/backend/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

class DatabaseOperations(BaseDatabaseOperations):
compiler_module = 'rest_models.backend.compiler'
# postgis compatibility
select = '%s'

def sql_flush(self, *args, **kwargs): # pragma: no cover
return ""
Expand Down
5 changes: 1 addition & 4 deletions rest_models/backend/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ def message_from_response(response):
try:
from django.db.models import JSONField as JSONFieldLegacy
except ImportError:
try:
from django.contrib.postgres.fields import JSONField as JSONFieldLegacy
except ImportError:
pass
pass

try:
class JSONField(JSONFieldLegacy):
Expand Down
4 changes: 2 additions & 2 deletions rest_models/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
import logging
import os
import threading
from urllib.parse import unquote

import unidecode
from django.core.files.base import ContentFile
from django.core.files.storage import Storage
from django.utils.deconstruct import deconstructible
from django.utils.http import urlunquote

from rest_models.backend.connexion import get_basic_session

Expand Down Expand Up @@ -85,7 +85,7 @@ def prepare_result_from_api(self, result, cursor):
# and store the full url for later
if result is None:
return None
name = urlunquote(os.path.basename(result))
name = unquote(os.path.basename(result))
self.result_file_pool[name] = result, cursor
return name

Expand Down
4 changes: 2 additions & 2 deletions rest_models/tests/test_clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def test_existing_db(self):
called = []

def tmp_exec(self_dc, args, env):
self.assertRegexpMatches(env['_resty_host'], r'http://localhost:[0-9]*/api/v2\*')
self.assertRegex(env['_resty_host'], r'http://localhost:[0-9]*/api/v2\*')
called.append(args)

DatabaseClient.execute_subprocess = tmp_exec
Expand All @@ -33,7 +33,7 @@ def test_to_run_db(self):
called = []

def tmp_exec(self_dc, args, env):
self.assertRegexpMatches(env['_resty_host'], r'http://localhost:[0-9]*/api/v2\*')
self.assertRegex(env['_resty_host'], r'http://localhost:[0-9]*/api/v2\*')
called.append(args)

DatabaseClient.execute_subprocess = tmp_exec
Expand Down
31 changes: 29 additions & 2 deletions rest_models/tests/test_restmodeltestcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

from rest_models.test import RestModelTestCase
from rest_models.utils import Path
from testapp.models import Menu
from testapp.models import POSTGIS, Menu

if POSTGIS:
from testapp.models import Restaurant


class TestLoadFixtureTest(RestModelTestCase):
Expand Down Expand Up @@ -44,7 +47,7 @@ def test_fixtures_loaded(self):
def test_fixtures_loaded_missing(self):
self.assertRaisesMessage(Exception,
"the query 'a' was not provided as mocked data: "
"urls was %r" % (['b', 'c', 'd'], ),
"urls was %r" % (['b', 'c', 'd'],),
self.client.get, 'a')

def test_variable_fixtures(self):
Expand Down Expand Up @@ -79,6 +82,7 @@ def test_track_queries(self):

class TestMockDataSample(RestModelTestCase):
databases = ['default', 'api']
fixtures = (['restaurant.json'] if POSTGIS else [])
database_rest_fixtures = {'api': { # api => response mocker for databasen named «api»
'menulol': [ # url menulol
{
Expand Down Expand Up @@ -192,9 +196,32 @@ class TestMockDataSample(RestModelTestCase):

}
],
'restaurant': [ # url restaurant
{
'filter': { # set of filter to match
'params': {}
},
'data': {
"restaurants": [{
"id": 1,
"name": "ShopShoy",
"location": "POINT (7.3370199999999999 47.8078000000000003)"
}],
"meta": {
"per_page": 10,
"total_pages": 1,
"page": 1,
"total_results": 0
}
}
},
]
}}

def test_multi_results_filter(self):
# postgis compatibility
if POSTGIS:
self.assertEqual(len(list(Restaurant.objects.all())), 1)
# no filter/no sort => fallback
self.assertEqual(len(list(Menu.objects.all())), 2)
# no matching filter => fallback
Expand Down
Loading

0 comments on commit 5f85656

Please sign in to comment.