Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Writer api #18

Merged
merged 15 commits into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions bridgeql/django/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,18 @@ def wrap(request, *args, **kwargs):
return wrap


if bridgeql_settings.BRIDGEQL_AUTHENTICATION_DECORATOR:
auth_decorator = load_function(
bridgeql_settings.BRIDGEQL_AUTHENTICATION_DECORATOR)
reader_auth = bridgeql_settings.BRIDGEQL_AUTHENTICATION_DECORATOR.get(
'reader', None
)
writer_auth = bridgeql_settings.BRIDGEQL_AUTHENTICATION_DECORATOR.get(
'writer', None
)
if reader_auth:
read_auth_decorator = load_function(reader_auth)
else:
def auth_decorator(func): return func
def read_auth_decorator(func): return func

if writer_auth:
write_auth_decorator = load_function(writer_auth)
else:
def write_auth_decorator(func): return func
28 changes: 19 additions & 9 deletions bridgeql/django/bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@
import json

from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_exempt

from bridgeql.django import logger
from bridgeql.django.auth import auth_decorator
from bridgeql.django.auth import read_auth_decorator, write_auth_decorator
from bridgeql.django.exceptions import (
BridgeqlException
BridgeqlException,
InvalidRequest
)
from bridgeql.django.helpers import JSONResponse, get_json_request_body
from bridgeql.django.models import ModelBuilder, ModelObject

# TODO refine error handling


@auth_decorator
@require_http_methods(['GET'])
@read_auth_decorator
def read_django_model(request):
params = request.GET.get('payload', None)
try:
Expand All @@ -33,20 +34,29 @@ def read_django_model(request):
return JSONResponse(res, status=e.status_code)


@auth_decorator
# no session to ride, hence no need for csrf protection
@csrf_exempt
@require_http_methods(['POST', 'PATCH'])
@write_auth_decorator
def write_django_model(request, app_label, model_name, **kwargs):
try:
params = get_json_request_body(request.body)
db_name = params.pop('db_name', None)
pk = kwargs.pop('pk', None)
mo = ModelObject(app_label, model_name, db_name=db_name, pk=pk)
if mo.instance is None:
if mo.instance is None and request.method == 'POST':
obj = mo.create(params)
else:
msg = 'Added new %s model, pk = %s' % (
obj._meta.model.__name__,
obj.pk
)
elif mo.instance and request.method == 'PATCH':
obj = mo.update(params)
res = {'data': obj.id, 'message': 'Updated fields %s' % (
", ".join(params.keys())), 'success': True}
msg = 'Updated fields %s' % ", ".join(params.keys())
else:
raise InvalidRequest(
'Invalid request method %s for the url' % request.method)
res = {'data': obj.id, 'message': msg, 'success': True}
return JSONResponse(res)
except BridgeqlException as e:
e.log()
Expand Down
15 changes: 11 additions & 4 deletions bridgeql/django/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
)
from django.db.models import QuerySet
from django.db.models.base import ModelBase
from django.db.utils import IntegrityError

from bridgeql.django import logger
from bridgeql.django.exceptions import (
Expand Down Expand Up @@ -167,9 +168,15 @@ def update(self, params):
# TODO check if there are any restricted fields in data
try:
for key, val in params.items():
if hasattr(self.instance, key):
setattr(self.instance, key, val)
else:
raise InvalidRequest('%s does not have field %s'
% (self.instance._meta.model.__name__,
key))
setattr(self.instance, key, val)
# Perform validation
self.instance.full_clean()
self.instance.validate_unique()
except (ValidationError, AttributeError) as e:
raise InvalidRequest(str(e))
self.instance.save()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this up inside try/except and change AttributeError -> IntegrityError

Expand All @@ -184,10 +191,10 @@ def create(self, params):
self.instance = self.model_config.model(**params)
# Perform validation
try:
self.instance.full_clean()
except ValidationError as e:
self.instance.validate_unique()
self.instance.save(**save_kwargs)
except (ValidationError, IntegrityError) as e:
raise InvalidRequest(str(e))
self.instance.save(**save_kwargs)
return self.instance


Expand Down
35 changes: 26 additions & 9 deletions bridgeql/django/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@

DEFAULTS = {
'BRIDGEQL_RESTRICTED_MODELS': {},
'BRIDGEQL_AUTHENTICATION_DECORATOR': '',
'BRIDGEQL_AUTHENTICATION_DECORATOR': {
'reader': '',
'writer': ''
},
'BRIDGEQL_ALLOWED_APPS': []
}

Expand Down Expand Up @@ -65,14 +68,28 @@ def _validate_restricted_models(self):

def _validate_auth_decorator(self):
# check for the valid auth decorator
if self.BRIDGEQL_AUTHENTICATION_DECORATOR:
try:
load_function(self.BRIDGEQL_AUTHENTICATION_DECORATOR)
except (AttributeError, ImportError) as e:
raise InvalidBridgeQLSettings(
'Wrong value for settings.BRIDGEQL_AUTHENTICATION_DECORATOR %s'
% self.BRIDGEQL_AUTHENTICATION_DECORATOR
)
if not isinstance(self.BRIDGEQL_AUTHENTICATION_DECORATOR, dict):
raise InvalidBridgeQLSettings(
'Wrong type for settings.BRIDGEQL_AUTHENTICATION_DECORATOR, '
'expected dict, got %s'
% type(self.BRIDGEQL_AUTHENTICATION_DECORATOR)
)
read_auth = self.BRIDGEQL_AUTHENTICATION_DECORATOR.get(
'reader', None
)
write_auth = self.BRIDGEQL_AUTHENTICATION_DECORATOR.get(
'writer', None
)
try:
if read_auth:
load_function(read_auth)
if write_auth:
load_function(write_auth)
except (AttributeError, ImportError):
raise InvalidBridgeQLSettings(
'Wrong value for settings.BRIDGEQL_AUTHENTICATION_DECORATOR %s'
% self.BRIDGEQL_AUTHENTICATION_DECORATOR
)
return True

def validate(self):
Expand Down
15 changes: 15 additions & 0 deletions tests/server/machine/tests/test_api_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,21 @@ def test_update_machine_invalid_pk(self):
self.assertEqual(resp.status_code, 400)
self.assertFalse(resp.json()['success'])

def test_update_machine_field_not_exist(self):
url = reverse('bridgeql_django_update', kwargs={
'app_label': 'machine',
'model_name': 'Machine',
'pk': 10
})
params1 = {'ip': '10.0.0.111',
'xx': 'updated-name-1', # add datetime later
}
resp = self.client.patch(
url, json.dumps({"payload": params1}), content_type='application/json')
# self.assertEqual(resp.json()['message'], '')
self.assertEqual(resp.status_code, 400)
self.assertFalse(resp.json()['success'])

def test_update_non_existent_machine(self):
machine_object_pk = 101
url = reverse('bridgeql_django_update', kwargs={
Expand Down
17 changes: 15 additions & 2 deletions tests/server/machine/tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,25 @@ def test_invalid_restricted_models_setting(self):
def test_invalid_auth_decorator(self):
self.assertRaises(InvalidBridgeQLSettings, bridgeql_settings.validate)

@override_settings(BRIDGEQL_AUTHENTICATION_DECORATOR='server.auth.localtest1')
@override_settings(BRIDGEQL_AUTHENTICATION_DECORATOR={
'reader': 'server.auth.localtest1',
'writer': 'server.auth.localtest1'
})
def test_valid_module_invalid_auth_decorator(self):
self.assertRaises(InvalidBridgeQLSettings, bridgeql_settings.validate)

@override_settings(BRIDGEQL_AUTHENTICATION_DECORATOR='server.auth.localtest')
@override_settings(BRIDGEQL_AUTHENTICATION_DECORATOR={
'reader': 'server.auth.localtest',
'writer': 'server.auth.localtest'
})
def test_valid_auth_decorator(self):
self.assertTrue(bridgeql_settings.validate()) \


@override_settings(BRIDGEQL_AUTHENTICATION_DECORATOR={
'writer': 'server.auth.localtest'
})
def test_valid_writer_auth_decorator(self):
self.assertTrue(bridgeql_settings.validate())

def test_list_local_apps(self):
Expand Down