diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fb382e2..4a5c94c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,7 +2,11 @@ ChangeLog =========================== PLEASE NOTE THIS IS ONLY A BETA RELEASE. THE OPENNPL API IS STILL UNSTABLE -v0.6.3 (17-05-2025) +v0.6.4 (20-06-2024) +------------------- +* Bugs: REST API for both NPL and SFLP templates + +v0.6.3 (17-05-2024) ------------------- * Functionality: Update to 2023 SFLP Template diff --git a/npl_portfolio/CHANGELOG.rst b/npl_portfolio/CHANGELOG.rst index f88ccd6..bcaf6a7 100644 --- a/npl_portfolio/CHANGELOG.rst +++ b/npl_portfolio/CHANGELOG.rst @@ -1,6 +1,11 @@ ChangeLog =========================== +v0.6.1 +------------------ +* Redesign REST API + + v0.6.0 (Starting App ChangeLog) ------------------ * First commit of changelog / readme diff --git a/npl_portfolio/urls.py b/npl_portfolio/urls.py index 6949d90..1f9bff6 100644 --- a/npl_portfolio/urls.py +++ b/npl_portfolio/urls.py @@ -20,30 +20,30 @@ from django.urls import re_path -from openNPL import npl_views as views +from . import views as api_views app_name = 'npl_portfolio' urlpatterns = [ - # re_path(r'^$', views.npl_api_root, name='npl_api_root'), - re_path(r'^counterparty_groups$', views.npl_counterparty_group_api, name='npl_counterparty_group_api'), - re_path(r'^counterparty_groups/(?P[0-9]+)/$', views.npl_counterparty_group_detail, + re_path(r'^', api_views.npl_api_root, name='npl_api_root'), + re_path(r'^counterparty_groups$', api_views.npl_counterparty_group_api, name='npl_counterparty_group_api'), + re_path(r'^counterparty_groups/(?P[0-9]+)/$', api_views.npl_counterparty_group_detail, name='npl_counterparty_group_detail'), - re_path(r'^counterparties$', views.npl_counterparty_api, name='npl_counterparty_api'), - re_path(r'^counterparties/(?P[0-9]+)/$', views.npl_counterparty_detail, name='npl_counterparty_detail'), - re_path(r'^property_collateral$', views.npl_property_collateral_api, name='npl_property_collateral_api'), - re_path(r'^property_collateral/(?P[0-9]+)/$', views.npl_property_collateral_detail, + re_path(r'^counterparties$', api_views.npl_counterparty_api, name='npl_counterparty_api'), + re_path(r'^counterparties/(?P[0-9]+)/$', api_views.npl_counterparty_detail, name='npl_counterparty_detail'), + re_path(r'^property_collateral$', api_views.npl_property_collateral_api, name='npl_property_collateral_api'), + re_path(r'^property_collateral/(?P[0-9]+)/$', api_views.npl_property_collateral_detail, name='npl_property_collateral_detail'), - re_path(r'^loans$', views.npl_loan_api, name='npl_loan_api'), - re_path(r'^loans/(?P[0-9]+)/$', views.npl_loan_detail, name='npl_loan_detail'), - re_path(r'^enforcement$', views.npl_enforcement_api, name='npl_enforcement_api'), - re_path(r'^enforcement/(?P[0-9]+)/$', views.npl_enforcement_detail, name='npl_enforcement_detail'), - re_path(r'^forbearance$', views.npl_forbearance_api, name='npl_forbearance_api'), - re_path(r'^forbearance/(?P[0-9]+)/$', views.npl_forbearance_detail, name='npl_forbearance_detail'), - re_path(r'^nonproperty_collateral$', views.npl_nonproperty_collateral_api, name='npl_nonproperty_collateral_api'), - re_path(r'^nonproperty_collateral/(?P[0-9]+)/$', views.npl_nonproperty_collateral_detail, + re_path(r'^loans$', api_views.npl_loan_api, name='npl_loan_api'), + re_path(r'^loans/(?P[0-9]+)/$', api_views.npl_loan_detail, name='npl_loan_detail'), + re_path(r'^enforcement$', api_views.npl_enforcement_api, name='npl_enforcement_api'), + re_path(r'^enforcement/(?P[0-9]+)/$', api_views.npl_enforcement_detail, name='npl_enforcement_detail'), + re_path(r'^forbearance$', api_views.npl_forbearance_api, name='npl_forbearance_api'), + re_path(r'^forbearance/(?P[0-9]+)/$', api_views.npl_forbearance_detail, name='npl_forbearance_detail'), + re_path(r'^nonproperty_collateral$', api_views.npl_nonproperty_collateral_api, name='npl_nonproperty_collateral_api'), + re_path(r'^nonproperty_collateral/(?P[0-9]+)/$', api_views.npl_nonproperty_collateral_detail, name='npl_nonproperty_collateral_detail'), - re_path(r'^external_collection$', views.npl_external_collection_api, name='npl_external_collection_api'), - re_path(r'^external_collection/(?P[0-9]+)/$', views.npl_external_collection_detail, + re_path(r'^external_collection$', api_views.npl_external_collection_api, name='npl_external_collection_api'), + re_path(r'^external_collection/(?P[0-9]+)/$', api_views.npl_external_collection_detail, name='npl_external_collection_detail'), ] diff --git a/sflp_portfolio/CHANGELOG.rst b/sflp_portfolio/CHANGELOG.rst index 32b3784..c30ab26 100644 --- a/sflp_portfolio/CHANGELOG.rst +++ b/sflp_portfolio/CHANGELOG.rst @@ -1,6 +1,10 @@ ChangeLog =========================== +v0.6.4 +------------------ +* Redesign REST API + v0.6.3 ------------------ * Added two new attributes as per 2023 SFLP template update diff --git a/sflp_portfolio/urls.py b/sflp_portfolio/urls.py index 54bf6bb..605b4eb 100644 --- a/sflp_portfolio/urls.py +++ b/sflp_portfolio/urls.py @@ -20,11 +20,12 @@ from django.urls import re_path -from openNPL import sflp_views as api_views +from . import views as api_views app_name = 'sflp_portfolio' urlpatterns = [ + re_path(r'^', api_views.sflp_api_root, name='sflp_api_root'), re_path(r'^counterparties$', api_views.sflp_counterparty_api, name='sflp_counterparty_api'), diff --git a/sflp_portfolio/views.py b/sflp_portfolio/views.py new file mode 100644 index 0000000..05f9aff --- /dev/null +++ b/sflp_portfolio/views.py @@ -0,0 +1,247 @@ +# Copyright (c) 2020 - 2024 Open Risk (https://www.openriskmanagement.com) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +from rest_framework import permissions +from rest_framework import status +from rest_framework.decorators import api_view, permission_classes +from rest_framework.parsers import JSONParser +from rest_framework.response import Response +from rest_framework.reverse import reverse + +from openNPL.sflp_serializers import SFLP_CounterpartySerializer, SFLP_CounterpartyDetailSerializer +from openNPL.sflp_serializers import SFLP_EnforcementSerializer, SFLP_EnforcementDetailSerializer +from openNPL.sflp_serializers import SFLP_ForbearanceSerializer, SFLP_ForbearanceDetailSerializer +from openNPL.sflp_serializers import SFLP_LoanSerializer, SFLP_LoanDetailSerializer +from openNPL.sflp_serializers import SFLP_PropertyCollateralSerializer, SFLP_PropertyCollateralDetailSerializer +from sflp_portfolio.models.counterparty import Counterparty +from sflp_portfolio.models.loan import Loan +from sflp_portfolio.models.enforcement import Enforcement +from sflp_portfolio.models.forbearance import Forbearance +from sflp_portfolio.models.property_collateral import PropertyCollateral + + + + +# +# API ENDPOINTS +# + +@api_view(['GET']) +def sflp_api_root(request, format=None): + """ + Returns a list of all active API endpoints for SFLP Template Data + + """ + + data = [ + {'SFLP Template Endpoints': + [ + {'sflp_counterparty': reverse('sflp_portfolio:sflp_counterparty_api', request=request, format=format)}, + {'sflp_loan': reverse('sflp_portfolio:sflp_loan_api', request=request, format=format)}, + {'sflp_enforcement': reverse('sflp_portfolio:sflp_enforcement_api', request=request, format=format)}, + {'sflp_forbearance': reverse('sflp_portfolio:sflp_forbearance_api', request=request, format=format)}, + {'sflp_property_collateral': reverse('sflp_portfolio:sflp_property_collateral_api', request=request, format=format)}, + # {'sflp_repayment_schedule': reverse('sflp_portfolio:sflp_repayment_schedule_api', request=request, format=format)}, + ]} + ] + return Response(data) + + +@api_view(['GET', 'POST']) +@permission_classes((permissions.AllowAny,)) +def sflp_counterparty_api(request): + """ + List Counterparties (EBA Template) + """ + if request.method == 'GET': + counterparty = Counterparty.objects.all() + serializer = SFLP_CounterpartySerializer(counterparty, many=True, context={'request': request}) + return Response(serializer.data) + elif request.method == 'POST': + """ + Create a new Counterparty + """ + data = JSONParser().parse(request) + serializer = SFLP_CounterpartyDetailSerializer(data=data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['GET']) +def sflp_counterparty_detail(request, pk): + if request.method == 'GET': + """ + List the data a specific Counterparty + """ + try: + counterparty = Counterparty.objects.get(pk=pk) + except Counterparty.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) + + serializer = SFLP_CounterpartyDetailSerializer(counterparty) + return Response(serializer.data) + + +@api_view(['GET', 'POST']) +@permission_classes((permissions.AllowAny,)) +def sflp_loan_api(request): + """ + List Loans (EBA Template) + """ + if request.method == 'GET': + loan = Loan.objects.all() + serializer = SFLP_LoanSerializer(loan, many=True, context={'request': request}) + return Response(serializer.data) + elif request.method == 'POST': + """ + Create a new Loan + """ + data = JSONParser().parse(request) + serializer = SFLP_LoanDetailSerializer(data=data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['GET']) +def sflp_loan_detail(request, pk): + """ + List the data a specific EBA Loan + """ + try: + loan = Loan.objects.get(pk=pk) + except Loan.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) + + serializer = SFLP_LoanDetailSerializer(loan) + return Response(serializer.data) + + +@api_view(['GET', 'POST']) +@permission_classes((permissions.AllowAny,)) +def sflp_property_collateral_api(request): + """ + List Property Collateral (EBA Template) + """ + if request.method == 'GET': + property_collateral = PropertyCollateral.objects.all() + serializer = SFLP_PropertyCollateralSerializer(property_collateral, many=True, + context={'request': request}) + return Response(serializer.data) + elif request.method == 'POST': + """ + Create new property collateral + """ + data = JSONParser().parse(request) + serializer = SFLP_PropertyCollateralDetailSerializer(data=data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['GET']) +def sflp_property_collateral_detail(request, pk): + """ + List the data a specific EBA Property Collateral + """ + try: + property_collateral = PropertyCollateral.objects.get(pk=pk) + except PropertyCollateral.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) + + serializer = SFLP_PropertyCollateralDetailSerializer(property_collateral) + return Response(serializer.data) + + +@api_view(['GET', 'POST']) +@permission_classes((permissions.AllowAny,)) +def sflp_enforcement_api(request): + """ + List Enforcements (EBA Template) + """ + if request.method == 'GET': + enforcement = Enforcement.objects.all() + serializer = SFLP_EnforcementSerializer(enforcement, many=True, context={'request': request}) + return Response(serializer.data) + elif request.method == 'POST': + """ + Create new Enforcement entry + """ + data = JSONParser().parse(request) + serializer = SFLP_EnforcementDetailSerializer(data=data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['GET']) +def sflp_enforcement_detail(request, pk): + """ + List the data a specific EBA Enforcement entry + """ + try: + enforcement = Enforcement.objects.get(pk=pk) + except Enforcement.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) + + serializer = SFLP_EnforcementDetailSerializer(enforcement) + return Response(serializer.data) + + +@api_view(['GET', 'POST']) +@permission_classes((permissions.AllowAny,)) +def sflp_forbearance_api(request): + """ + List Forbearance (EBA Template) + """ + if request.method == 'GET': + forbearance = Forbearance.objects.all() + serializer = SFLP_ForbearanceSerializer(forbearance, many=True, context={'request': request}) + return Response(serializer.data) + elif request.method == 'POST': + """ + Create new Forbearance entry + """ + data = JSONParser().parse(request) + serializer = SFLP_ForbearanceDetailSerializer(data=data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +@api_view(['GET']) +def sflp_forbearance_detail(request, pk): + """ + List the data a specific EBA Forbearance entry + """ + try: + forbearance = Forbearance.objects.get(pk=pk) + except Forbearance.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) + + serializer = SFLP_ForbearanceDetailSerializer(forbearance) + return Response(serializer.data) diff --git a/start/templates/start/front.html b/start/templates/start/front.html index 5c0f692..0d545b8 100644 --- a/start/templates/start/front.html +++ b/start/templates/start/front.html @@ -22,95 +22,112 @@ {% block content %} -

Welcome to openNPL

- -

- openNPL is an open source platform for the management of non-performing loans. - It implements detailed European Banking Authority loan templates for NPL data. Click on Home - to get started! -

- -

The challenge of Non-Performing Loans

-

Non-Performing loans is a serious and ongoing issue that affects many economies. Developing - tools and methodologies that will reduce the burden and improve the ability to manage problem - loans is thus an important objective. For more background and previous work - see the Open - Risk Blog Post. -

- -

What exactly is openNPL?

-

openNPL is a web application that works together with a tailored data schema to provide easy access - to NPL data adhering to the regulatory recommendation. Running the application creates a server that - can then be accessed via any regular browser. For easy installation and testing the current release uses - an sqlite database instead of a production database. -

- -

Once the platform is up and running a wealth of functionality is available. Just - as a sample:

- - - - - - - - - - - - - - - - - - - - - - -
User Functionality
A user can log-in into the application with their credentials
Inspect the available data sets (tables or relations) that capture the EBA Template recommendations
Insert, Update or Delete records (e.g. new counterparty or loan data)
Consult the documentation as to the meaning and requirements of each data element
- - - - - - - - - - - - - - - - -
Administrator Functionality
An administrator can create users and user groups and assign permissions
Perform bulk insertion or deletion of data sets
- - - - - - - - - - - - - - - - -
Developer Functionality
openNPL is based on the well known and loved Python web framework Django
Developers can customize and adapt the platform in unlimited ways
- -

Join the openNPL community

-

- The project aims to be driven by community needs. We welcome code contributions and feature - requests via github. -

- +
+
+

Welcome to openNPL

+
+
+

+ openNPL is an open source platform for the management of non-performing loans. It implements detailed European Banking Authority and US Fannie Mae (Data Dynamics) loan templates for NPL data. Click on Hometo get started! +

+ +
+
+ +
+
+

The challenge of Non-Performing Loans

+
+
+

Non-Performing loans is a serious and ongoing issue that affects many economies. Developing tools and methodologies that will reduce the burden and improve the ability to manage problem loans is thus an important objective. For more background and previous work see the Open Risk Blog Post.

+
+
+ + +
+
+

What exactly is openNPL?

+
+
+

openNPL is a web application that works together with a tailored data schema to provide easy + access to NPL data adhering to the regulatory recommendation. Running the application creates a serverthat can then be accessed via any regular browser. For easy installation and testing the current release uses an sqlite database instead of a production database. +

+ +

Once the platform is up and running a wealth of functionality is available. Just + as a sample:

+ + + + + + + + + + + + + + + + + + + + + + +
User Functionality
A user can log-in into the application with their credentials
Inspect the available data sets (tables or relations) that capture the EBA Template + recommendations +
Insert, Update or Delete records (e.g. new counterparty or loan data)
Consult the documentation as to the meaning and requirements of each data element
+ + + + + + + + + + + + + + + + +
Administrator Functionality
An administrator can create users and user groups and assign permissions
Perform bulk insertion or deletion of data sets
+ + + + + + + + + + + + + + + + +
Developer Functionality
openNPL is based on the well known and loved Python web framework Django
Developers can customize and adapt the platform in unlimited ways
+
+
+ +
+
+

Join the openNPL community

+
+
+

+ The project aims to be driven by community needs. We welcome code contributions and feature + requests via GitHub. +

+
+
+ {% endblock %}