diff --git a/README.md b/README.md
index 16bbb5d..c160af2 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,14 @@
# API Manager
-This is a Django project to manage the Open Bank Project APIs via API Calls.
+This is a Django project to manage the Open Bank Project via API Calls.
+
+You can use this project to:
+
+1. Manage API Consumers (Apps)
+2. View API Metrics (which Consumers called which endpoints)
+3. Grant / Revoke User Entitlelements
+4. Manage certain resources e.g. Branches
+5. etc. etc.
To use this app, you need to authenticate against a sandbox where you have to register an account beforehand. Currently, you can enable or disable consumers.
diff --git a/apimanager/apimanager/urls.py b/apimanager/apimanager/urls.py
index 3fba52b..eb7f13d 100644
--- a/apimanager/apimanager/urls.py
+++ b/apimanager/apimanager/urls.py
@@ -34,6 +34,7 @@ urlpatterns = [
url(r'^entitlementrequests/', include('entitlementrequests.urls')),
url(r'^users/', include('users.urls')),
url(r'^branches/', include('branches.urls')),
+ url(r'^atms/', include('atms.urls')),
url(r'^customers/', include('customers.urls')),
url(r'^metrics/', include('metrics.urls')),
url(r'^config/', include('config.urls')),
diff --git a/apimanager/atms/__init__.py b/apimanager/atms/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/apimanager/atms/admin.py b/apimanager/atms/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/apimanager/atms/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/apimanager/atms/apps.py b/apimanager/atms/apps.py
new file mode 100644
index 0000000..b3752af
--- /dev/null
+++ b/apimanager/atms/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class BranchesConfig(AppConfig):
+ name = 'atms'
diff --git a/apimanager/atms/forms.py b/apimanager/atms/forms.py
new file mode 100644
index 0000000..0305889
--- /dev/null
+++ b/apimanager/atms/forms.py
@@ -0,0 +1,189 @@
+"""
+Forms of branches app
+"""
+
+from django import forms
+
+import random
+
+
+class CreateAtmForm(forms.Form):
+
+ atm_id = forms.CharField(
+ label='ATM Id',
+ widget=forms.TextInput(
+ attrs={
+ 'placeholder': 'atm-id-{}'.format(random.randint(1,1000)),
+ 'class': 'form-control',
+ }
+ ),
+ initial='atm-id-{}'.format(random.randint(1,1000)),
+ )
+
+ bank_id = forms.ChoiceField(
+ label='Bank',
+ widget=forms.Select(
+ attrs={
+ 'class': 'form-control',
+ }
+ ),
+ choices=[],
+ )
+ name = forms.CharField(
+ label='Name',
+ widget=forms.TextInput(
+ attrs={
+ 'placeholder': 'The name of the branch',
+ 'class': 'form-control',
+ }
+ ),
+ required=True
+ )
+ address = forms.CharField(
+ label='Address',
+ widget=forms.Textarea(
+ attrs={
+ 'class': 'form-control',
+ }
+ ),
+ required=False
+ )
+
+ location_latitude = forms.FloatField(
+ label='Latitude',
+ widget=forms.TextInput(
+ attrs={
+ 'placeholder': 37.0,
+ 'class': 'form-control',
+ }
+ ),
+ required=False,
+ )
+
+ location_longitude = forms.FloatField(
+ label='Longitude',
+ widget=forms.TextInput(
+ attrs={
+ 'placeholder': 110.0,
+ 'class': 'form-control',
+ }
+ ),
+ required=False,
+ )
+
+ meta_license_id = forms.CharField(
+ label='meta_license_id',
+ widget=forms.TextInput(
+ attrs={
+ 'placeholder': 'PDDL',
+ 'class': 'form-control',
+ }
+ ),
+ required=False,
+ )
+
+ meta_license_name = forms.CharField(
+ label='meta_license_name',
+ widget=forms.TextInput(
+ attrs={
+ 'placeholder': 'Open Data Commons Public Domain Dedication and License',
+ 'class': 'form-control',
+ }
+ ),
+ required=False,
+ )
+
+ lobby = forms.CharField(
+ label=' Lobby Opening Hours',
+ widget=forms.Textarea(
+ attrs={
+ 'placeholder': 'None',
+ 'class': 'form-control',
+ }
+ ),
+ required=False,
+ )
+ drive_up = forms.CharField(
+ label='Drive Up',
+ widget=forms.Textarea(
+ attrs={
+ 'placeholder': 'None', # noqa
+ 'class': 'form-control',
+ }
+ ),
+ required=False,
+ )
+ atm_routing_scheme = forms.CharField(
+ label='Branch Routing Scheme',
+ widget=forms.TextInput(
+ attrs={
+ 'placeholder': 'OBP',
+ 'class': 'form-control',
+ }
+ ),
+ required=False,
+ )
+ atm_routing_address = forms.CharField(
+ label='Branch Routing Address',
+ widget=forms.TextInput(
+ attrs={
+ 'placeholder': '123abc',
+ 'class': 'form-control',
+ }
+ ),
+ required=False,
+ )
+ is_accessible = forms.ChoiceField(
+ label='is accessible',
+ widget=forms.Select(
+ attrs={
+ 'class': 'form-control',
+ }
+ ),
+ required=False,
+ )
+ accessibleFeatures = forms.CharField(
+ label='Accessible Features',
+ widget=forms.TextInput(
+ attrs={
+ 'placeholder': 'wheelchair, atm usuable by the visually impaired',
+ 'class': 'form-control',
+ }
+ ),
+ required=False,
+ )
+ atm_type = forms.CharField(
+ label='ATM type',
+ widget=forms.TextInput(
+ attrs={
+ 'placeholder': 'Full service store',
+ 'class': 'form-control',
+ }
+ ),
+ required=False,
+ )
+ more_info = forms.CharField(
+ label='More information',
+ widget=forms.TextInput(
+ attrs={
+ 'placeholder': 'short walk to the lake from here',
+ 'class': 'form-control',
+ }
+ ),
+ required=False,
+ )
+
+ phone_number = forms.CharField(
+ label='Mobile Phone Number',
+ widget=forms.TextInput(
+ attrs={
+ 'placeholder': 'E.g. +49 123 456 78 90 12',
+ 'class': 'form-control',
+ }
+ ),
+ required=False,
+ )
+
+ def __init__(self, *args, **kwargs):
+ kwargs.setdefault('label_suffix', '')
+ super(CreateAtmForm, self).__init__(*args, **kwargs)
diff --git a/apimanager/atms/models.py b/apimanager/atms/models.py
new file mode 100644
index 0000000..3b5ea8d
--- /dev/null
+++ b/apimanager/atms/models.py
@@ -0,0 +1,4 @@
+from django.db import models
+
+# Create your models here.
+# -*- coding: utf-8 -*-
diff --git a/apimanager/atms/static/atms/css/atms.css b/apimanager/atms/static/atms/css/atms.css
new file mode 100644
index 0000000..a2e16e3
--- /dev/null
+++ b/apimanager/atms/static/atms/css/atms.css
@@ -0,0 +1,18 @@
+#atms_list div {
+ margin: 5px 0;
+}
+
+/* The actual popup (appears on top) */
+.popuptext {
+ width: 250px;
+ background-color: #555;
+ color: #fff;
+ text-align: left;
+ border-radius: 6px;
+ padding: 8px 0;
+ z-index: 1;
+ /*bottom: 125%;*/
+ top:100%
+ left: 50%;
+ margin-left: -80px;
+}
diff --git a/apimanager/atms/static/atms/js/atms.js b/apimanager/atms/static/atms/js/atms.js
new file mode 100644
index 0000000..338fe9e
--- /dev/null
+++ b/apimanager/atms/static/atms/js/atms.js
@@ -0,0 +1,5 @@
+$(document).ready(function($) {
+ $('#info').click(function() {
+ alert("Hello World")
+ });
+});
diff --git a/apimanager/atms/templates/atms/index.html b/apimanager/atms/templates/atms/index.html
new file mode 100644
index 0000000..1d56179
--- /dev/null
+++ b/apimanager/atms/templates/atms/index.html
@@ -0,0 +1,233 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block page_title %}{{ block.super }} / Atms{% endblock page_title %}
+
+{% block content %}
+
+
ATMs
+
+
+
+
+
+
+ | Atm Id |
+ Bank Id |
+ ATM Name |
+ More_info |
+ Update Button |
+
+
+ {% for atm in atms_list %}
+ {% url 'atms_update' atm.id atm.bank_id as url_atm_update %}
+
+ | {{ atm.id }} |
+ {{ atm.bank_id }} |
+ {{ atm.name }} |
+
+
+ |
+ Update |
+
+ {% endfor %}
+
+
+
+
+{% endblock %}
+
+{% block extrajs %}
+{% comment %}
+
+
+{% endcomment %}
+{% endblock extrajs %}
+
+
+{% block extracss %}
+
+{% endblock extracss %}
diff --git a/apimanager/atms/templates/atms/update.html b/apimanager/atms/templates/atms/update.html
new file mode 100644
index 0000000..f303acc
--- /dev/null
+++ b/apimanager/atms/templates/atms/update.html
@@ -0,0 +1,179 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block page_title %}{{ block.super }} / Atms{% endblock page_title %}
+
+{% block content %}
+
+
Update atm
+
{{ bank_id }} : {{ atm_id }}
+
+
+{% endblock content %}
+
+{% block extrajs %}
+{% comment %}
+
+
+{% endcomment %}
+{% endblock extrajs %}
+
+
+{% block extracss %}
+
+{% endblock extracss %}
+
diff --git a/apimanager/atms/tests.py b/apimanager/atms/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/apimanager/atms/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/apimanager/atms/urls.py b/apimanager/atms/urls.py
new file mode 100644
index 0000000..096a144
--- /dev/null
+++ b/apimanager/atms/urls.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+"""
+URLs for metrics app
+"""
+
+from django.conf.urls import url
+
+from .views import IndexAtmView, UpdateAtmView
+
+urlpatterns = [
+ url(r'^$',
+ IndexAtmView.as_view(),
+ name='atms_list'),
+ url(r'^update/(?P[0-9\w\@\.\+-]+)/bank/(?P[0-9\w\@\.\+-]+)/$',
+ UpdateAtmView.as_view(),
+ name='atms_update')
+]
diff --git a/apimanager/atms/views.py b/apimanager/atms/views.py
new file mode 100644
index 0000000..cf898bc
--- /dev/null
+++ b/apimanager/atms/views.py
@@ -0,0 +1,325 @@
+from django.shortcuts import render
+
+# Create your views here.
+# -*- coding: utf-8 -*-
+"""
+Views of branches app
+"""
+
+from django.contrib import messages
+from django.contrib.auth.mixins import LoginRequiredMixin
+import json
+from django.urls import reverse_lazy
+from django.views.generic import FormView
+
+from obp.api import API, APIError
+
+from .forms import CreateAtmForm
+
+class IndexAtmView(LoginRequiredMixin, FormView):
+ """Index view for atm"""
+ template_name = "atms/index.html"
+ form_class = CreateAtmForm
+ success_url = reverse_lazy('atms_list')
+
+ def dispatch(self, request, *args, **kwargs):
+ self.api = API(request.session.get('obp'))
+ return super(IndexAtmView, self).dispatch(request, *args, **kwargs)
+
+ def get_form(self, *args, **kwargs):
+ form = super(IndexAtmView, self).get_form(*args, **kwargs)
+ # Cannot add api in constructor: super complains about unknown kwarg
+ form.api = self.api
+ fields = form.fields
+ try:
+ fields['bank_id'].choices = self.api.get_bank_id_choices()
+ fields['is_accessible'].choices = [('','Choose...'),(True, True), (False, False)]
+ fields['drive_up'].initial = json.dumps({
+ "monday": {
+ "opening_time": "10:00",
+ "closing_time": "18:00"
+ },
+ "tuesday": {
+ "opening_time": "10:00",
+ "closing_time": "18:00"
+ },
+ "wednesday": {
+ "opening_time": "10:00",
+ "closing_time": "18:00"
+ },
+ "thursday": {
+ "opening_time": "10:00",
+ "closing_time": "18:00"
+ },
+ "friday": {
+ "opening_time": "10:00",
+ "closing_time": "18:00"
+ },
+ "saturday": {
+ "opening_time": "10:00",
+ "closing_time": "18:00"
+ },
+ "sunday": {
+ "opening_time": "10:00",
+ "closing_time": "18:00"
+ }
+ }, indent=4)
+
+ fields['lobby'].initial = json.dumps({
+ "monday": [
+ {
+ "opening_time": "10:00",
+ "closing_time": "18:00"
+ }
+ ],
+ "tuesday": [
+ {
+ "opening_time": "10:00",
+ "closing_time": "18:00"
+ }
+ ],
+ "wednesday": [
+ {
+ "opening_time": "10:00",
+ "closing_time": "18:00"
+ }
+ ],
+ "thursday": [
+ {
+ "opening_time": "10:00",
+ "closing_time": "18:00"
+ }
+ ],
+ "friday": [
+ {
+ "opening_time": "10:00",
+ "closing_time": "18:00"
+ }
+ ],
+ "saturday": [
+ {
+ "opening_time": "10:00",
+ "closing_time": "18:00"
+ }
+ ],
+ "sunday": [
+ {
+ "opening_time": "10:00",
+ "closing_time": "18:00"
+ }
+ ]
+ }, indent=4)
+
+ fields['address'].initial = json.dumps({
+ "line_1":"No 1 the Road",
+ "line_2":"The Place",
+ "line_3":"The Hill",
+ "city":"Berlin",
+ "county":"String",
+ "state":"Brandenburg",
+ "postcode":"13359",
+ "country_code":"DE"
+ }, indent=4)
+
+ except APIError as err:
+ messages.error(self.request, err)
+ except:
+ messages.error(self.request, "Unknown Error")
+
+ return form
+
+ def form_valid(self, form):
+ try:
+ data = form.cleaned_data
+ urlpath = '/banks/{}/atms'.format(data['bank_id'])
+ payload = {
+ "id": data["atm_id"],
+ "bank_id": data["bank_id"],
+ "name": data["name"],
+ "address": json.loads(data['address']),
+ "location": {
+ "latitude": float(data["location_latitude"]) if data["location_latitude"] is not None else 37.0,
+ "longitude": float(data["location_longitude"]) if data["location_longitude"] is not None else 110.0
+ },
+ "meta": {
+ "license": {
+ "id": "PDDL",
+ "name": data["meta_license_name"] if data["meta_license_name"]!="" else "license name"
+ }
+ },
+ "lobby": json.loads(data['lobby']),
+ "drive_up": json.loads(data["drive_up"]),
+ "branch_routing": {
+ "scheme": data["branch_routing_scheme"] if data["branch_routing_scheme"]!="" else "license name",
+ "address": data["branch_routing_address"] if data["branch_routing_address"]!="" else "license name"
+ },
+ "is_accessible": data["is_accessible"] if data["is_accessible"]!="" else "false",
+ "accessibleFeatures": data["accessibleFeatures"] if data["accessibleFeatures"]!="" else "accessible features name",
+ "branch_type": data["branch_type"] if data["branch_type"]!="" else "branch type",
+ "more_info": data["more_info"] if data["more_info"]!="" else "more info",
+ "phone_number": data["phone_number"] if data["phone_number"]!="" else "phone number"
+ }
+ result = self.api.post(urlpath, payload=payload)
+ except APIError as err:
+ error_once_only(self.request, err)
+ return super(IndexAtmView, self).form_invalid(form)
+ except Exception as err:
+ error_once_only(self.request, "Unknown Error")
+ return super(IndexAtmViewView, self).form_invalid(form)
+ if 'code' in result and result['code']>=400:
+ error_once_only(self.request, result['message'])
+ return super(IndexAtmView, self).form_valid(form)
+ msg = 'Atm {} for Bank {} has been created successfully!'.format(result['id'], result['bank_id'])
+ messages.success(self.request, msg)
+ return super(IndexAtmView, self).form_valid(form)
+
+ def get_banks(self):
+ api = API(self.request.session.get('obp'))
+ try:
+ urlpath = '/banks'
+ result = api.get(urlpath)
+ if 'banks' in result:
+ return [bank['id'] for bank in sorted(result['banks'], key=lambda d: d['id'])]
+ else:
+ return []
+ except APIError as err:
+ messages.error(self.request, err)
+ return []
+
+ def get_atms(self, context):
+
+ api = API(self.request.session.get('obp'))
+ try:
+ self.bankids = self.get_banks()
+ atms_list = []
+ for bank_id in self.bankids:
+ urlpath = '/banks/{}/atms'.format(bank_id)
+
+ result = api.get(urlpath)
+ if 'atms' in result:
+ atms_list.extend(result['atms'])
+ except APIError as err:
+ messages.error(self.request, err)
+ return []
+ except Exception as inst:
+ messages.error(self.request, "Unknown Error {}".format(type(inst).__name__))
+ return []
+
+ return atms_list
+
+ def get_context_data(self, **kwargs):
+ context = super(IndexAtmView, self).get_context_data(**kwargs)
+ atms_list = self.get_atms(context)
+ context.update({
+ 'atms_list': atms_list,
+ 'bankids': self.bankids
+ })
+ return context
+
+
+class UpdateAtmView(LoginRequiredMixin, FormView):
+ template_name = "atms/update.html"
+ success_url = '/atms/'
+ form_class = CreateAtmForm
+
+ def dispatch(self, request, *args, **kwargs):
+ self.api = API(request.session.get('obp'))
+ return super(UpdateAtmView, self).dispatch(request, *args, **kwargs)
+
+ def get_form(self, *args, **kwargs):
+ form = super(UpdateAtmView, self).get_form(*args, **kwargs)
+ # Cannot add api in constructor: super complains about unknown kwarg
+ form.api = self.api
+ fields = form.fields
+ urlpath = "/banks/{}/atms/{}".format(self.kwargs['bank_id'], self.kwargs['atm_id'])
+ try:
+ fields['bank_id'].choices = self.api.get_bank_id_choices()
+
+ except APIError as err:
+ messages.error(self.request, err)
+ except:
+ messages.error(self.request, "Unknown Error")
+ try:
+ result = self.api.get(urlpath)
+ fields['bank_id'].initial = self.kwargs['bank_id']
+ fields['atm_id'].initial = self.kwargs['atm_id']
+ fields['name'].initial = result['name']
+ fields['address'].initial = json.dumps(result['address'], indent=4)
+ fields['location_latitude'].initial = result['location']['latitude']
+ fields['location_longitude'].initial = result['location']['longitude']
+ fields['meta_license_id'].initial = result['meta']['license']['id']
+ fields['meta_license_name'].initial = result['meta']['license']['name']
+ fields['atm_routing_scheme'].initial = result['atm_routing']['scheme']
+ fields['atm_routing_address'].initial = result['atm_routing']['address']
+ if result['is_accessible'].lower()=='true':
+ fields['is_accessible'].choices = [(True, True), (False, False)]
+ else:
+ fields['is_accessible'].choices = [(False, False), (True, True)]
+ fields['accessibleFeatures'].initial = result['accessibleFeatures']
+ fields['atm_type'].initial = result['atm_type']
+ fields['more_info'].initial = result['more_info']
+ fields['phone_number'].initial = result['phone_number']
+ fields['lobby'].initial = json.dumps(result['lobby'], indent=4)
+ fields['drive_up'].initial = json.dumps(result['drive_up'], indent=4)
+ except APIError as err:
+ messages.error(self.request, err)
+ except Exception as err:
+ messages.error(self.request, "Unknown Error {}".format(err))
+
+ return form
+
+ def form_valid(self, form):
+ data = form.cleaned_data
+ urlpath = '/banks/{}/atms/{}'.format(data["bank_id"], data["atm_id"])
+ payload = {
+ #"id": data["branch_id"],
+ "bank_id": data["bank_id"],
+ "name": data["name"],
+ "address": json.loads(data['address']),
+ "location": {
+ "latitude": float(data["location_latitude"]),
+ "longitude": float(data["location_longitude"])
+ },
+ "meta": {
+ "license": {
+ "id": data["meta_license_id"],
+ "name": data["meta_license_name"]
+ }
+ },
+ "lobby": json.loads(data["lobby"]),
+ "drive_up": json.loads(data["drive_up"]),
+ "branch_routing": {
+ "scheme": data["atm_routing_scheme"] if data["atm_routing_scheme"] != "" else "license name",
+ "address": data["atm_routing_address"] if data["atm_routing_address"] != "" else "license name"
+ },
+ "is_accessible": data["is_accessible"],
+ "accessibleFeatures": data["accessibleFeatures"],
+ "atm_type": data["atm_type"],
+ "more_info": data["more_info"],
+ "phone_number": data["phone_number"]
+ }
+ try:
+ result = self.api.put(urlpath, payload=payload)
+ if 'code' in result and result['code']>=400:
+ error_once_only(self.request, result['message'])
+ return super(UpdateAtmView, self).form_invalid(form)
+ except APIError as err:
+ messages.error(self.request, err)
+ return super(UpdateAtmView, self).form_invalid(form)
+ except:
+ messages.error(self.request, "Unknown Error")
+ return super(UpdateAtmView, self).form_invalid(form)
+ msg = 'Atm {} for Bank {} has been created successfully!'.format( # noqa
+ data["atm_id"], data["bank_id"])
+ messages.success(self.request, msg)
+ return super(UpdateAtmView, self).form_valid(form)
+
+ def get_context_data(self, **kwargs):
+ context = super(UpdateAtmView, self).get_context_data(**kwargs)
+ self.bank_id = self.kwargs['bank_id']
+ self.atm_id = self.kwargs['atm_id']
+ context.update({
+ 'atm_id': self.atm_id,
+ 'bank_id': self.bank_id
+ })
+ return context
diff --git a/apimanager/base/templates/base.html b/apimanager/base/templates/base.html
index 2060bab..11f26db 100644
--- a/apimanager/base/templates/base.html
+++ b/apimanager/base/templates/base.html
@@ -73,11 +73,13 @@
{% url "branches_list" as branches_list_url %}
{% url "customers-create" as customers_create_url %}
+ {% url "atms_list" as atms_list_url %}
Resources
{% url "config-index" as config_index_url %}