From 72b8557e886a56dfa879781d7558564c0c424e0c Mon Sep 17 00:00:00 2001 From: Hongwei Date: Tue, 12 Oct 2021 10:24:13 +0200 Subject: [PATCH 1/3] feature/added the apiCollection menu --- apimanager/apicollections/__init__.py | 0 apimanager/apicollections/apps.py | 10 +++ apimanager/apicollections/forms.py | 13 ++++ .../apicollections/js/apicollections.js | 30 ++++++++ .../templates/apicollections/index.html | 60 ++++++++++++++++ apimanager/apicollections/urls.py | 18 +++++ apimanager/apicollections/views.py | 71 +++++++++++++++++++ apimanager/apimanager/settings.py | 3 +- apimanager/apimanager/urls.py | 1 + apimanager/base/templates/base.html | 4 +- 10 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 apimanager/apicollections/__init__.py create mode 100644 apimanager/apicollections/apps.py create mode 100644 apimanager/apicollections/forms.py create mode 100644 apimanager/apicollections/static/apicollections/js/apicollections.js create mode 100644 apimanager/apicollections/templates/apicollections/index.html create mode 100644 apimanager/apicollections/urls.py create mode 100644 apimanager/apicollections/views.py diff --git a/apimanager/apicollections/__init__.py b/apimanager/apicollections/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apimanager/apicollections/apps.py b/apimanager/apicollections/apps.py new file mode 100644 index 0000000..c4ab07c --- /dev/null +++ b/apimanager/apicollections/apps.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" +App config for config app +""" + +from django.apps import AppConfig + +class ApiCollectionsConfig(AppConfig): + """Config for apicollections""" + name = 'apicollections' diff --git a/apimanager/apicollections/forms.py b/apimanager/apicollections/forms.py new file mode 100644 index 0000000..2f77fb2 --- /dev/null +++ b/apimanager/apicollections/forms.py @@ -0,0 +1,13 @@ +from django import forms + + +class ApiCollectionsForm(forms.Form): + api_collections_body = forms.CharField( + label='API Collections Body', + widget=forms.Textarea( + attrs={ + 'class': 'form-control', + } + ), + required=False + ) \ No newline at end of file diff --git a/apimanager/apicollections/static/apicollections/js/apicollections.js b/apimanager/apicollections/static/apicollections/js/apicollections.js new file mode 100644 index 0000000..6a1f8f8 --- /dev/null +++ b/apimanager/apicollections/static/apicollections/js/apicollections.js @@ -0,0 +1,30 @@ +$(document).ready(function($) { + $('.runner button.forSave').click(function(e) { + e.preventDefault(); + var t = $(this); + var runner = t.parent().parent().parent(); + var api_collection_body = $(runner).find('.api-collection-body').val(); + + $('.runner button.forSave').attr("disabled","disabled"); + $('.runner button.forDelete').attr("disabled","disabled"); + $.post('save/apicollection', { + 'api-collection-body': api_collection_body, + }, function (response) { + location.reload(); + }); + }); + + $('.runner button.forDelete').click(function(e) { + e.preventDefault(); + var t = $(this); + var runner = t.parent().parent().parent(); + var api_collection_id = $(runner).find('.api_collection_id').html(); + $('.runner button.forSave').attr("disabled","disabled"); + $('.runner button.forDelete').attr("disabled","disabled"); + $.post('delete/apicollection', { + 'api_collection_id': api_collection_id + }, function (response) { + location.reload(); + }); + }); +}); diff --git a/apimanager/apicollections/templates/apicollections/index.html b/apimanager/apicollections/templates/apicollections/index.html new file mode 100644 index 0000000..8bc020a --- /dev/null +++ b/apimanager/apicollections/templates/apicollections/index.html @@ -0,0 +1,60 @@ +{% extends 'base.html' %} +{% load static %} +{% block page_title %}{{ block.super }} / API Collections{% endblock page_title %} + +{% block content %} +

API Collections

+
+
+
+
+
+
+
+
+
+
+
+
+ {% csrf_token %} + {% for api_collection in api_collections %} +
+
+
+
+
{{ api_collection.api_collection_id }}
+
+
+
+
{{ api_collection.api_collection_name }}
+
+
+ +
+ {% if forloop.counter0 == 0 %} +
+
+ saved. +
+
+ {% endif %} + {% if forloop.counter0 > 0 %} +
+
+ +
+
+ {% endif %} +
+ +
+
+
+ {% endfor %} +
+{% endblock %} + + +{% block extrajs %} + +{% endblock extrajs %} diff --git a/apimanager/apicollections/urls.py b/apimanager/apicollections/urls.py new file mode 100644 index 0000000..afb0f13 --- /dev/null +++ b/apimanager/apicollections/urls.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +""" +URLs for config app +""" + +from django.conf.urls import url + +from apicollections.views import IndexView, apicollections_save, apicollections_delete + +urlpatterns = [ + url(r'^$', + IndexView.as_view(), + name='apicollections-index'), + url(r'save/apicollection', apicollections_save, + name='apicollection-save'), + url(r'delete/apicollection', apicollections_delete, + name='apicollection-delete') +] diff --git a/apimanager/apicollections/views.py b/apimanager/apicollections/views.py new file mode 100644 index 0000000..8236093 --- /dev/null +++ b/apimanager/apicollections/views.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +""" +Views of config app +""" + +import json +from django.contrib.auth.mixins import LoginRequiredMixin +from django.views.generic import FormView +from obp.api import API, APIError +from base.utils import exception_handle, error_once_only +from .forms import ApiCollectionsForm +from django.urls import reverse_lazy +from django.views.decorators.csrf import csrf_exempt + + +class IndexView(LoginRequiredMixin, FormView): + """Index view for config""" + template_name = "apicollections/index.html" + form_class = ApiCollectionsForm + success_url = reverse_lazy('apicollections-index') + + def get_context_data(self, **kwargs): + context = super(IndexView, self).get_context_data(**kwargs) + api = API(self.request.session.get('obp')) + urlpath = '/my/api-collections' + api_collections =[] + try: + response = api.get(urlpath) + if 'code' in response and response['code'] >= 400: + error_once_only(self.request, response['message']) + else: + api_collections=response['api_collections'] + except APIError as err: + error_once_only(self.request, Exception("OBP-API server is not running or do not response properly. " + "Please check OBP-API server. " + "Details: " + str(err))) + except BaseException as err: + error_once_only(self.request, (Exception("Unknown Error. Details:" + str(err)))) + else: + # set the default endpoint there, the first item will be the new endpoint. + default_api_endpoint = { + "api_collection_name": "Testing", + "is_sharable": True, + "description":"This is for testing" + } + api_collections.insert(0,json.dumps(default_api_endpoint)) + + context.update({ + 'api_collections': api_collections + }) + return context + +@exception_handle +@csrf_exempt +def apicollections_save(request): + api_collection_body = request.POST.get('api-collection-body') + api = API(request.session.get('obp')) + urlpath = '/my/api-collections' + result = api.post(urlpath, payload =json.loads( api_collection_body)) + return result + + +@exception_handle +@csrf_exempt +def apicollections_delete(request): + api_collection_id = request.POST.get('api_collection_id') + + api = API(request.session.get('obp')) + urlpath = '/my/api-collections/{}'.format(api_collection_id) + result = api.delete(urlpath) + return result diff --git a/apimanager/apimanager/settings.py b/apimanager/apimanager/settings.py index 632903d..abdcb1e 100644 --- a/apimanager/apimanager/settings.py +++ b/apimanager/apimanager/settings.py @@ -58,7 +58,8 @@ INSTALLED_APPS = [ 'config', 'webui', 'methodrouting', - 'dynamicendpoints' + 'dynamicendpoints', + 'apicollections' ] MIDDLEWARE = [ diff --git a/apimanager/apimanager/urls.py b/apimanager/apimanager/urls.py index 01e4db3..3fba52b 100644 --- a/apimanager/apimanager/urls.py +++ b/apimanager/apimanager/urls.py @@ -40,4 +40,5 @@ urlpatterns = [ url(r'^webui/', include('webui.urls')), url(r'^methodrouting/', include('methodrouting.urls')), url(r'^dynamicendpoints/', include('dynamicendpoints.urls')), + url(r'^apicollections/', include('apicollections.urls')), ] diff --git a/apimanager/base/templates/base.html b/apimanager/base/templates/base.html index 7533831..d2ff5a3 100644 --- a/apimanager/base/templates/base.html +++ b/apimanager/base/templates/base.html @@ -70,13 +70,15 @@ {% url "webui-index" as webui_props_index_url %} {% url "methodrouting-index" as methodrouting_index_url %} {% url "dynamicendpoints-index" as dynamic_endpoints_index_url %} + {% url "apicollections-index" as api_collections_index_url %} From cfa10d910202c29177905f463cdef50f54c92939 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Thu, 14 Oct 2021 10:55:41 +0200 Subject: [PATCH 2/3] feature/added the add/delete apiCollectionEndpoint --- apimanager/apicollections/forms.py | 11 +++ .../templates/apicollections/detail.html | 43 +++++++++ .../templates/apicollections/index.html | 10 +- apimanager/apicollections/urls.py | 14 ++- apimanager/apicollections/views.py | 91 ++++++++++++++++++- 5 files changed, 159 insertions(+), 10 deletions(-) create mode 100644 apimanager/apicollections/templates/apicollections/detail.html diff --git a/apimanager/apicollections/forms.py b/apimanager/apicollections/forms.py index 2f77fb2..936c630 100644 --- a/apimanager/apicollections/forms.py +++ b/apimanager/apicollections/forms.py @@ -10,4 +10,15 @@ class ApiCollectionsForm(forms.Form): } ), required=False + ) + +class ApiCollectionEndpointsForm(forms.Form): + operation_id = forms.CharField( + label='Operation Id', + widget=forms.TextInput( + attrs={ + 'class': 'form-control', + } + ), + required=True ) \ No newline at end of file diff --git a/apimanager/apicollections/templates/apicollections/detail.html b/apimanager/apicollections/templates/apicollections/detail.html new file mode 100644 index 0000000..7108758 --- /dev/null +++ b/apimanager/apicollections/templates/apicollections/detail.html @@ -0,0 +1,43 @@ +{% extends 'base.html' %} +{% load static %} +{% block content %} +
+

Add Api Collection

+
+ {% csrf_token %} +
+
+
+ {{ form.operation_id.label_tag}} + {{ form.operation_id }} +
+ + +
+
+
+ +

Existing API Collections

+
+ + + + + + {% for api_collection_endpoint in api_collection_endpoints %} + + + + + {% endfor %} + +
Operation Ids
{{ api_collection_endpoint.operation_id }} +
+ {% csrf_token %} + +
+
+
+
+{% endblock %} diff --git a/apimanager/apicollections/templates/apicollections/index.html b/apimanager/apicollections/templates/apicollections/index.html index 8bc020a..6e8d672 100644 --- a/apimanager/apicollections/templates/apicollections/index.html +++ b/apimanager/apicollections/templates/apicollections/index.html @@ -6,23 +6,25 @@

API Collections

-
+
-
+
-
+
{% csrf_token %} {% for api_collection in api_collections %} + {% url 'my-api-collection-detail' api_collection.api_collection_id as url_collection_detail %}
-
{{ api_collection.api_collection_id }}
+ {{ api_collection.api_collection_id }} +
diff --git a/apimanager/apicollections/urls.py b/apimanager/apicollections/urls.py index afb0f13..84fbe65 100644 --- a/apimanager/apicollections/urls.py +++ b/apimanager/apicollections/urls.py @@ -5,7 +5,8 @@ URLs for config app from django.conf.urls import url -from apicollections.views import IndexView, apicollections_save, apicollections_delete +from apicollections.views import IndexView, apicollections_save, \ + apicollections_delete, DetailView, DeleteCollectionEndpointView urlpatterns = [ url(r'^$', @@ -14,5 +15,14 @@ urlpatterns = [ url(r'save/apicollection', apicollections_save, name='apicollection-save'), url(r'delete/apicollection', apicollections_delete, - name='apicollection-delete') + name='apicollection-delete'), + url(r'^my-api-collection-ids/(?P[\w\@\.\+-]+)$', + DetailView.as_view(), + name='my-api-collection-detail'), + url(r'^delete/api-collections/(?P[\w-]+)/api-collection-endpoint/(?P[\w\@\.\+-]+)$', + DeleteCollectionEndpointView.as_view(), + name='delete-api-collection-endpoint'), + # url(r'^add/api-collections/(?P[\w-]+)/api-collection-endpoints/(?P[\w\@\.\+-]+)$', + # AddCollectionEndpointView.as_view(), + # name='add-api-collection-endpoint'), ] diff --git a/apimanager/apicollections/views.py b/apimanager/apicollections/views.py index 8236093..1b39dd3 100644 --- a/apimanager/apicollections/views.py +++ b/apimanager/apicollections/views.py @@ -4,12 +4,14 @@ Views of config app """ import json +from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin +from django.http import HttpResponseRedirect from django.views.generic import FormView from obp.api import API, APIError +from django.urls import reverse, reverse_lazy from base.utils import exception_handle, error_once_only -from .forms import ApiCollectionsForm -from django.urls import reverse_lazy +from .forms import ApiCollectionsForm, ApiCollectionEndpointsForm from django.views.decorators.csrf import csrf_exempt @@ -39,9 +41,9 @@ class IndexView(LoginRequiredMixin, FormView): else: # set the default endpoint there, the first item will be the new endpoint. default_api_endpoint = { - "api_collection_name": "Testing", + "api_collection_name": "Customer", "is_sharable": True, - "description":"This is for testing" + "description":"Describe the purpose of the collection" } api_collections.insert(0,json.dumps(default_api_endpoint)) @@ -50,6 +52,87 @@ class IndexView(LoginRequiredMixin, FormView): }) return context +class DetailView(LoginRequiredMixin, FormView): + """Index view for config""" + template_name = "apicollections/detail.html" + form_class = ApiCollectionEndpointsForm + success_url = reverse_lazy('my-api-collection-detail') + + def form_valid(self, form): + """Posts api collection endpoint data to API""" + try: + data = form.cleaned_data + api = API(self.request.session.get('obp')) + api_collection_id = super(DetailView, self).get_context_data()['view'].kwargs['api_collection_id'] + + urlpath = '/my/api-collection-ids/{}/api-collection-endpoints'.format(api_collection_id) + payload = { + 'operation_id': data['operation_id'] + } + api_collection_endpoint = api.post(urlpath, payload=payload) + except APIError as err: + messages.error(self.request, err) + return super(DetailView, self).form_invalid(form) + except: + messages.error(self.request, 'Unknown Error') + return super(DetailView, self).form_invalid(form) + if 'code' in api_collection_endpoint and api_collection_endpoint['code']>=400: + messages.error(self.request, api_collection_endpoint['message']) + return super(DetailView, self).form_invalid(form) + else: + msg = 'Operation Id {} has been added.'.format(data['operation_id']) + messages.success(self.request, msg) + self.success_url = self.request.path + return super(DetailView, self).form_valid(form) + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + api_collection_id = context['view'].kwargs['api_collection_id'] + + api = API(self.request.session.get('obp')) + urlpath = '/my/api-collection-ids/{}/api-collection-endpoints'.format(api_collection_id) + api_collection_endpoints =[] + try: + response = api.get(urlpath) + if 'code' in response and response['code'] >= 400: + error_once_only(self.request, response['message']) + else: + api_collection_endpoints=response['api_collection_endpoints'] + except APIError as err: + error_once_only(self.request, Exception("OBP-API server is not running or do not response properly. " + "Please check OBP-API server. " + "Details: " + str(err))) + except BaseException as err: + error_once_only(self.request, (Exception("Unknown Error. Details:" + str(err)))) + else: + context.update({ + 'api_collection_endpoints': api_collection_endpoints, + 'api_collection_id': api_collection_id + }) + return context + +class DeleteCollectionEndpointView(LoginRequiredMixin, FormView): + """View to delete an api collection endpoint""" + def post(self, request, *args, **kwargs): + """Deletes api collection endpoint from API""" + api = API(self.request.session.get('obp')) + try: + urlpath = '/my/api-collections-ids/{}/api-collection-endpoints/{}'\ + .format(kwargs['api_collection_id'],kwargs['operation_id']) + result = api.delete(urlpath) + if result is not None and 'code' in result and result['code']>=400: + messages.error(request, result['message']) + else: + msg = 'Operation Id {} has been deleted.'.format(kwargs['operation_id']) + messages.success(request, msg) + except APIError as err: + messages.error(request, err) + except: + messages.error(self.request, 'Unknown Error') + + redirect_url = reverse('my-api-collection-detail',kwargs={"api_collection_id":kwargs['api_collection_id']}) + return HttpResponseRedirect(redirect_url) + @exception_handle @csrf_exempt def apicollections_save(request): From 56b93d841d16f5d91fd630d5ccd3b53f606fb1fa Mon Sep 17 00:00:00 2001 From: Hongwei Date: Sat, 16 Oct 2021 07:59:05 +0200 Subject: [PATCH 3/3] feature/tweaked the api_collection form fields --- .../apicollections/js/apicollections.js | 8 +++- .../templates/apicollections/detail.html | 4 +- .../templates/apicollections/index.html | 47 ++++++++++++++----- apimanager/apicollections/views.py | 10 ++-- apimanager/base/templates/base.html | 2 +- 5 files changed, 51 insertions(+), 20 deletions(-) diff --git a/apimanager/apicollections/static/apicollections/js/apicollections.js b/apimanager/apicollections/static/apicollections/js/apicollections.js index 6a1f8f8..d0d9d94 100644 --- a/apimanager/apicollections/static/apicollections/js/apicollections.js +++ b/apimanager/apicollections/static/apicollections/js/apicollections.js @@ -3,12 +3,16 @@ $(document).ready(function($) { e.preventDefault(); var t = $(this); var runner = t.parent().parent().parent(); - var api_collection_body = $(runner).find('.api-collection-body').val(); + var api_collection_name = $(runner).find('.api_collection_name').val(); + var api_collection_is_sharable = $(runner).find('.api_collection_is_sharable').val(); + var api_collection_description = $(runner).find('.api_collection_description').val(); $('.runner button.forSave').attr("disabled","disabled"); $('.runner button.forDelete').attr("disabled","disabled"); $.post('save/apicollection', { - 'api-collection-body': api_collection_body, + 'api_collection_name': api_collection_name, + 'api_collection_is_sharable': api_collection_is_sharable, + 'api_collection_description': api_collection_description, }, function (response) { location.reload(); }); diff --git a/apimanager/apicollections/templates/apicollections/detail.html b/apimanager/apicollections/templates/apicollections/detail.html index 7108758..fe7bcd7 100644 --- a/apimanager/apicollections/templates/apicollections/detail.html +++ b/apimanager/apicollections/templates/apicollections/detail.html @@ -2,7 +2,7 @@ {% load static %} {% block content %}
-

Add Api Collection

+

Add Endpoint to API Collection

{% csrf_token %}
@@ -17,7 +17,7 @@
-

Existing API Collections

+

Endpoints

diff --git a/apimanager/apicollections/templates/apicollections/index.html b/apimanager/apicollections/templates/apicollections/index.html index 6e8d672..ae552b5 100644 --- a/apimanager/apicollections/templates/apicollections/index.html +++ b/apimanager/apicollections/templates/apicollections/index.html @@ -6,13 +6,16 @@

API Collections

-
+

-
-
+
+
+
+
+
@@ -23,20 +26,43 @@ + {% if api_collection.api_collection_id %} +
+
+
{{ api_collection.api_collection_name }}
-
{{ api_collection.api_collection_name }}
+
{{ api_collection.is_sharable }}
+
-
- +
+
{{api_collection.description}}
+ {% else %} +
+
+ +
+
+
+
+ +
+
+
+ +
+ {% endif %} {% if forloop.counter0 == 0 %}
- saved. +
{% endif %} @@ -47,9 +73,6 @@
{% endif %} -
- -
{% endfor %} diff --git a/apimanager/apicollections/views.py b/apimanager/apicollections/views.py index 1b39dd3..743a857 100644 --- a/apimanager/apicollections/views.py +++ b/apimanager/apicollections/views.py @@ -136,17 +136,21 @@ class DeleteCollectionEndpointView(LoginRequiredMixin, FormView): @exception_handle @csrf_exempt def apicollections_save(request): - api_collection_body = request.POST.get('api-collection-body') api = API(request.session.get('obp')) urlpath = '/my/api-collections' - result = api.post(urlpath, payload =json.loads( api_collection_body)) + payload = { + 'api_collection_name': request.POST.get('api_collection_name').strip(), + 'is_sharable': bool(request.POST.get('api_collection_is_sharable')), + 'description': request.POST.get('api_collection_description').strip() + } + result = api.post(urlpath, payload = payload) return result @exception_handle @csrf_exempt def apicollections_delete(request): - api_collection_id = request.POST.get('api_collection_id') + api_collection_id = request.POST.get('api_collection_id').strip() api = API(request.session.get('obp')) urlpath = '/my/api-collections/{}'.format(api_collection_id) diff --git a/apimanager/base/templates/base.html b/apimanager/base/templates/base.html index d2ff5a3..c28ea9e 100644 --- a/apimanager/base/templates/base.html +++ b/apimanager/base/templates/base.html @@ -78,7 +78,7 @@ Webui Props Method Routings Dynamic Endpoints - My Api Collections + My API Collections