mirror of
https://github.com/OpenBankProject/API-Manager.git
synced 2026-02-06 14:16:46 +00:00
feature/Add edit/show rate limiting feature
This commit is contained in:
parent
09d96788f5
commit
4be22830bd
@ -13,7 +13,7 @@ USER_CURRENT = "/users/current"
|
||||
|
||||
def api_version_processor(request):
|
||||
"""Returns the configured API_VERSION"""
|
||||
return {'API_VERSION': settings.API_VERSION['v500']}
|
||||
return {'API_VERSION': settings.API_VERSION['v510']}
|
||||
|
||||
|
||||
def portal_page(request):
|
||||
@ -82,7 +82,7 @@ def api_user_id(request):
|
||||
"""Returns the API user id of the logged-in user"""
|
||||
user_id = 'not authenticated'
|
||||
get_current_user_api_url = USER_CURRENT
|
||||
#Here we can not get the user from obp-api side, so we use the django auth user id here.
|
||||
#Here we can not get the user from obp-api side, so we use the django auth user id here.
|
||||
cache_key_django_user_id = request.session._session.get('_auth_user_id')
|
||||
cache_key = '{},{},{}'.format('api_user_id',get_current_user_api_url, cache_key_django_user_id)
|
||||
apicaches=None
|
||||
@ -112,4 +112,3 @@ def api_tester_url(request):
|
||||
"""Returns the URL to the API Tester for the API instance"""
|
||||
url = getattr(settings, 'API_TESTER_URL', None)
|
||||
return {'API_TESTER_URL': url}
|
||||
|
||||
|
||||
@ -12,8 +12,45 @@ class ApiConsumersForm(forms.Form):
|
||||
required=True,
|
||||
)
|
||||
|
||||
from_date = forms.DateTimeField(
|
||||
label='From Date',
|
||||
widget=forms.DateTimeInput(
|
||||
attrs={
|
||||
'class': 'form-control',
|
||||
'type': 'datetime-local',
|
||||
'value': '2024-01-01T00:00',
|
||||
}
|
||||
),
|
||||
required=False,
|
||||
initial='2024-01-01T00:00:00',
|
||||
)
|
||||
|
||||
to_date = forms.DateTimeField(
|
||||
label='To Date',
|
||||
widget=forms.DateTimeInput(
|
||||
attrs={
|
||||
'class': 'form-control',
|
||||
'type': 'datetime-local',
|
||||
'value': '2026-01-01T00:00',
|
||||
}
|
||||
),
|
||||
required=False,
|
||||
initial='2026-01-01T00:00:00',
|
||||
)
|
||||
|
||||
per_second_call_limit = forms.IntegerField(
|
||||
label='Per Second Call Limit',
|
||||
widget=forms.NumberInput(
|
||||
attrs={
|
||||
'class': 'form-control',
|
||||
}
|
||||
),
|
||||
initial=-1,
|
||||
required=False,
|
||||
)
|
||||
|
||||
per_minute_call_limit = forms.IntegerField(
|
||||
label='per_minute_call_limit',
|
||||
label='Per Minute Call Limit',
|
||||
widget=forms.NumberInput(
|
||||
attrs={
|
||||
'class': 'form-control',
|
||||
@ -24,7 +61,7 @@ class ApiConsumersForm(forms.Form):
|
||||
)
|
||||
|
||||
per_hour_call_limit = forms.IntegerField(
|
||||
label='per_hour_call_limit',
|
||||
label='Per Hour Call Limit',
|
||||
widget=forms.NumberInput(
|
||||
attrs={
|
||||
'class': 'form-control',
|
||||
@ -33,8 +70,9 @@ class ApiConsumersForm(forms.Form):
|
||||
initial=-1,
|
||||
required=False,
|
||||
)
|
||||
|
||||
per_day_call_limit = forms.IntegerField(
|
||||
label='per_day_call_limit',
|
||||
label='Per Day Call Limit',
|
||||
widget=forms.NumberInput(
|
||||
attrs={
|
||||
'class': 'form-control',
|
||||
@ -43,8 +81,9 @@ class ApiConsumersForm(forms.Form):
|
||||
initial=-1,
|
||||
required=False,
|
||||
)
|
||||
|
||||
per_week_call_limit = forms.IntegerField(
|
||||
label='per_week_call_limit',
|
||||
label='Per Week Call Limit',
|
||||
widget=forms.NumberInput(
|
||||
attrs={
|
||||
'class': 'form-control',
|
||||
@ -55,7 +94,7 @@ class ApiConsumersForm(forms.Form):
|
||||
)
|
||||
|
||||
per_month_call_limit = forms.IntegerField(
|
||||
label='per_month_call_limit',
|
||||
label='Per Month Call Limit',
|
||||
widget=forms.NumberInput(
|
||||
attrs={
|
||||
'class': 'form-control',
|
||||
|
||||
@ -1,20 +1,184 @@
|
||||
.consumers #consumer-list {
|
||||
margin-top: 20px;
|
||||
.consumers #consumer-list {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#consumers .btn-group-vertical.filter-enabled,
|
||||
#consumers .btn-group-vertical.filter-apptype {
|
||||
margin-top: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#consumers-detail div {
|
||||
margin: 5px 0;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
#consumers .filter a {
|
||||
font-size: 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#consumers .actions .btn {
|
||||
margin-bottom: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* Rate Limiting Styles */
|
||||
#consumers-detail h2 {
|
||||
color: #333;
|
||||
border-bottom: 2px solid #007bff;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#consumers-detail .panel-info {
|
||||
border-color: #bee5eb;
|
||||
background-color: #d1ecf1;
|
||||
}
|
||||
|
||||
#consumers-detail .panel-info .panel-body {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#consumers-detail .text-info {
|
||||
color: #0c5460 !important;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#consumers-detail .text-muted {
|
||||
color: #6c757d !important;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#consumers-detail .form-group label {
|
||||
font-weight: bold;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
#consumers-detail .btn-primary {
|
||||
background-color: #007bff;
|
||||
border-color: #007bff;
|
||||
padding: 10px 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#consumers-detail .btn-primary:hover {
|
||||
background-color: #0056b3;
|
||||
border-color: #0056b3;
|
||||
}
|
||||
|
||||
/* Usage statistics 6-column layout */
|
||||
#consumers-detail .panel-info .col-sm-2 {
|
||||
min-height: 80px;
|
||||
padding: 10px 5px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* Readonly fields styling */
|
||||
#consumers-detail input[readonly] {
|
||||
background-color: #f8f9fa;
|
||||
color: #6c757d;
|
||||
cursor: not-allowed;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
/* Refresh button styling */
|
||||
#refreshUsageBtn {
|
||||
transition: all 0.3s ease;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#refreshUsageBtn:hover {
|
||||
background-color: #138496;
|
||||
border-color: #117a8b;
|
||||
}
|
||||
|
||||
#refreshUsageBtn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Progress bar styling */
|
||||
#refreshProgress {
|
||||
height: 10px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
#refreshProgress .progress-bar {
|
||||
transition: width 0.3s ease;
|
||||
background-color: #17a2b8;
|
||||
}
|
||||
|
||||
/* Usage update animation */
|
||||
.usage-calls {
|
||||
transition: background-color 0.5s ease;
|
||||
}
|
||||
|
||||
.usage-calls.updating {
|
||||
background-color: #d4edda !important;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* Spinning animation for refresh icon */
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.glyphicon-spin {
|
||||
animation: spin 1s infinite linear;
|
||||
}
|
||||
|
||||
/* Panel pulse effect during refresh */
|
||||
.panel-refreshing {
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(23, 162, 184, 0.7);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px rgba(23, 162, 184, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(23, 162, 184, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Updated data highlight */
|
||||
.data-updated {
|
||||
background-color: #d4edda;
|
||||
border-left: 3px solid #28a745;
|
||||
padding-left: 10px;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
/* Responsive adjustments for usage stats */
|
||||
@media (max-width: 768px) {
|
||||
#consumers-detail .panel-info .col-xs-6 {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#consumers-detail .panel-info .col-sm-2 {
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
#refreshUsageBtn {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Timestamp fields in configuration section */
|
||||
#consumers-detail .form-group input[readonly] {
|
||||
font-size: 12px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
@ -1,2 +1,100 @@
|
||||
$(document).ready(function($) {
|
||||
$(document).ready(function ($) {
|
||||
// Handle datetime-local inputs for rate limiting
|
||||
function initializeDateTimeFields() {
|
||||
// Set default values for datetime fields if they're empty
|
||||
var fromDateField = $("#id_from_date");
|
||||
var toDateField = $("#id_to_date");
|
||||
|
||||
// If fields are empty, set default values
|
||||
if (!fromDateField.val()) {
|
||||
fromDateField.val("2024-01-01T00:00");
|
||||
}
|
||||
if (!toDateField.val()) {
|
||||
toDateField.val("2026-01-01T00:00");
|
||||
}
|
||||
}
|
||||
|
||||
// Convert ISO datetime strings to datetime-local format for form inputs
|
||||
function convertISOToLocalDateTime(isoString) {
|
||||
if (!isoString) return "";
|
||||
// Remove the 'Z' and convert to local datetime format
|
||||
return isoString.replace("Z", "").substring(0, 16);
|
||||
}
|
||||
|
||||
// Initialize datetime fields with existing values if they exist
|
||||
function setExistingDateTimeValues() {
|
||||
var fromDate = $("[data-from-date]").data("from-date");
|
||||
var toDate = $("[data-to-date]").data("to-date");
|
||||
|
||||
if (fromDate && fromDate !== "1099-12-31T23:00:00Z") {
|
||||
$("#id_from_date").val(convertISOToLocalDateTime(fromDate));
|
||||
}
|
||||
if (toDate && toDate !== "1099-12-31T23:00:00Z") {
|
||||
$("#id_to_date").val(convertISOToLocalDateTime(toDate));
|
||||
}
|
||||
}
|
||||
|
||||
// Form validation
|
||||
function validateRateLimitingForm() {
|
||||
$("form").on("submit", function (e) {
|
||||
var hasError = false;
|
||||
var errorMessage = "";
|
||||
|
||||
// Check if any limit values are negative (except -1 which means unlimited)
|
||||
$('input[type="number"]').each(function () {
|
||||
var value = parseInt($(this).val());
|
||||
if (value < -1) {
|
||||
hasError = true;
|
||||
errorMessage +=
|
||||
"Rate limit values must be -1 (unlimited) or positive numbers.\n";
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Check date range
|
||||
var fromDate = new Date($("#id_from_date").val());
|
||||
var toDate = new Date($("#id_to_date").val());
|
||||
|
||||
if (fromDate && toDate && fromDate > toDate) {
|
||||
hasError = true;
|
||||
errorMessage += "From Date must be before To Date.\n";
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
alert(errorMessage);
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add visual feedback for current usage status
|
||||
function enhanceUsageDisplay() {
|
||||
$(".text-info").each(function () {
|
||||
var callsMade = parseInt($(this).text().match(/\d+/));
|
||||
var parentDiv = $(this).closest(".col-xs-6, .col-sm-3");
|
||||
var limitText = parentDiv.find("strong").text().toLowerCase();
|
||||
|
||||
// You could add logic here to highlight usage that's approaching limits
|
||||
// For now, we'll just ensure consistent styling
|
||||
$(this).addClass("usage-indicator");
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize all functionality
|
||||
initializeDateTimeFields();
|
||||
setExistingDateTimeValues();
|
||||
validateRateLimitingForm();
|
||||
enhanceUsageDisplay();
|
||||
|
||||
// Add tooltips for better UX
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
|
||||
// Add help text for rate limiting fields
|
||||
$('input[name*="call_limit"]').each(function () {
|
||||
$(this).attr(
|
||||
"title",
|
||||
"Use -1 for unlimited, or enter a positive number for the limit",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
|
||||
<h2>{% trans "Params" %}</h2>
|
||||
<h2>{% trans "Rate Limiting Configuration" %}</h2>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.consumer_id }}
|
||||
@ -23,35 +23,59 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-2 col-sm-2">
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
{% if form.from_date.errors %}<div class="alert alert-danger">{{ form.from_date.errors }}</div>{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.from_date.label_tag }}
|
||||
{{ form.from_date }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
{% if form.to_date.errors %}<div class="alert alert-danger">{{ form.to_date.errors }}</div>{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.to_date.label_tag }}
|
||||
{{ form.to_date }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
{% if form.per_second_call_limit.errors %}<div class="alert alert-danger">{{ form.per_second_call_limit.errors }}</div>{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.per_second_call_limit.label_tag }}
|
||||
{{ form.per_second_call_limit }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
{% if form.per_minute_call_limit.errors %}<div class="alert alert-danger">{{ form.per_minute_call_limit.errors }}</div>{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.per_minute_call_limit.label_tag }}
|
||||
{{ form.per_minute_call_limit }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-2 col-sm-2">
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
{% if form.per_hour_call_limit.errors %}<div class="alert alert-danger">{{ form.per_hour_call_limit.errors }}</div>{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.per_hour_call_limit.label_tag }}
|
||||
{{ form.per_hour_call_limit }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-2 col-sm-2">
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
{% if form.per_day_call_limit.errors %}<div class="alert alert-danger">{{ form.per_day_call_limit.errors }}</div>{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.per_day_call_limit.label_tag }}
|
||||
{{ form.per_day_call_limit }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-2 col-sm-2">
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
{% if form.per_week_call_limit.errors %}<div class="alert alert-danger">{{ form.per_week_call_limit.errors }}</div>{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.per_week_call_limit.label_tag }}
|
||||
{{ form.per_week_call_limit }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-2 col-sm-2">
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
{% if form.per_month_call_limit.errors %}<div class="alert alert-danger">{{ form.per_month_call_limit.errors }}</div>{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.per_month_call_limit.label_tag }}
|
||||
@ -60,11 +84,84 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<button type="submit" class="btn btn-primary">{% trans "Update Consumer" %}</button>
|
||||
<button type="submit" class="btn btn-primary">{% trans "Update Rate Limits" %}</button>
|
||||
</form>
|
||||
|
||||
{% if consumer.created_at %}
|
||||
<div class="row" style="margin-top: 20px;">
|
||||
<div class="col-xs-6 col-sm-6">
|
||||
<div class="form-group">
|
||||
<label>{% trans "Rate Limits Created At" %}</label>
|
||||
<input type="text" class="form-control" value="{{ consumer.created_at }}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-6">
|
||||
<div class="form-group">
|
||||
<label>{% trans "Rate Limits Updated At" %}</label>
|
||||
<input type="text" class="form-control" value="{{ consumer.updated_at }}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if consumer.current_state %}
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<h2>{% trans "Current Usage" %}
|
||||
<button type="button" id="refreshUsageBtn" class="btn btn-sm btn-info pull-right" onclick="refreshUsageStats()">
|
||||
<span class="glyphicon glyphicon-refresh"></span> {% trans "Refresh (10s)" %}
|
||||
</button>
|
||||
</h2>
|
||||
<div class="panel panel-info" id="usageStatsPanel">
|
||||
<div class="panel-body" id="usageStatsContent">
|
||||
{% if consumer.current_state.per_hour %}
|
||||
<div class="row" id="usageStatsRow">
|
||||
{% if consumer.current_state.per_second %}
|
||||
<div class="col-xs-6 col-sm-2" data-period="per_second">
|
||||
<strong>{% trans "Per Second" %}</strong><br>
|
||||
<span class="text-info usage-calls">{{ consumer.current_state.per_second.calls_made }} calls made</span><br>
|
||||
<small class="text-muted usage-reset">Resets in {{ consumer.current_state.per_second.reset_in_seconds }} seconds</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if consumer.current_state.per_minute %}
|
||||
<div class="col-xs-6 col-sm-2" data-period="per_minute">
|
||||
<strong>{% trans "Per Minute" %}</strong><br>
|
||||
<span class="text-info usage-calls">{{ consumer.current_state.per_minute.calls_made }} calls made</span><br>
|
||||
<small class="text-muted usage-reset">Resets in {{ consumer.current_state.per_minute.reset_in_seconds }} seconds</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-xs-6 col-sm-2" data-period="per_hour">
|
||||
<strong>{% trans "Per Hour" %}</strong><br>
|
||||
<span class="text-info usage-calls">{{ consumer.current_state.per_hour.calls_made }} calls made</span><br>
|
||||
<small class="text-muted usage-reset">Resets in {{ consumer.current_state.per_hour.reset_in_seconds }} seconds</small>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-2" data-period="per_day">
|
||||
<strong>{% trans "Per Day" %}</strong><br>
|
||||
<span class="text-info usage-calls">{{ consumer.current_state.per_day.calls_made }} calls made</span><br>
|
||||
<small class="text-muted usage-reset">Resets in {{ consumer.current_state.per_day.reset_in_seconds }} seconds</small>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-2" data-period="per_week">
|
||||
<strong>{% trans "Per Week" %}</strong><br>
|
||||
<span class="text-info usage-calls">{{ consumer.current_state.per_week.calls_made }} calls made</span><br>
|
||||
<small class="text-muted usage-reset">Resets in {{ consumer.current_state.per_week.reset_in_seconds }} seconds</small>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-2" data-period="per_month">
|
||||
<strong>{% trans "Per Month" %}</strong><br>
|
||||
<span class="text-info usage-calls">{{ consumer.current_state.per_month.calls_made }} calls made</span><br>
|
||||
<small class="text-muted usage-reset">Resets in {{ consumer.current_state.per_month.reset_in_seconds }} seconds</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div id="refreshProgress" class="progress" style="display: none; margin-top: 15px;">
|
||||
<div class="progress-bar progress-bar-info progress-bar-striped active" id="progressBar" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div id="consumers-detail-consumer_id">
|
||||
@ -122,51 +219,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-sm-6">
|
||||
<div id="consumers-detail-redirect_url">
|
||||
<strong>{% trans "Redirect URL" %}</strong><br />
|
||||
<span>{{ consumer.redirect_url }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-6">
|
||||
<div id="consumers-per_minute_call_limit ">
|
||||
<strong>{% trans "Per minute call limit" %}</strong><br />
|
||||
<span>{{ consumer.per_minute_call_limit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-sm-6">
|
||||
<div id="consumers-per_hour_call_limit ">
|
||||
<strong>{% trans "Per hour call limit" %} </strong><br />
|
||||
<span>{{ consumer.per_hour_call_limit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-6">
|
||||
<div id="consumers-per_day_call_limit">
|
||||
<strong>{% trans "Per day call limit" %}</strong><br />
|
||||
<span>{{ consumer.per_day_call_limit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div id="consumers-detail-redirect_url">
|
||||
<strong>{% trans "Redirect URL" %}</strong><br />
|
||||
<span>{{ consumer.redirect_url }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-sm-6">
|
||||
<div id="consumers-per_week_call_limit">
|
||||
<strong>{% trans "Per week call limit" %}</strong><br />
|
||||
<span>{{ consumer.per_week_call_limit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-6">
|
||||
<div id="consumers-per_month_call_limit">
|
||||
<strong>{% trans "Per month call limit" %}</strong><br />
|
||||
<span>{{ consumer.per_month_call_limit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
@ -204,11 +266,208 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block extrajs %}
|
||||
{% comment %}
|
||||
<script type="text/javascript" src="{% static 'consumers/js/consumers.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
// Add data attributes for existing datetime values
|
||||
$(document).ready(function() {
|
||||
{% if consumer.from_date %}
|
||||
$('body').attr('data-from-date', '{{ consumer.from_date }}');
|
||||
{% endif %}
|
||||
{% if consumer.to_date %}
|
||||
$('body').attr('data-to-date', '{{ consumer.to_date }}');
|
||||
{% endif %}
|
||||
});
|
||||
|
||||
// Global variables for refresh functionality
|
||||
let refreshInterval = null;
|
||||
let refreshCount = 0;
|
||||
const MAX_REFRESH_COUNT = 10;
|
||||
|
||||
// Function to refresh usage statistics
|
||||
function refreshUsageStats() {
|
||||
const button = document.getElementById('refreshUsageBtn');
|
||||
const progressDiv = document.getElementById('refreshProgress');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
|
||||
// Disable button and show progress
|
||||
button.disabled = true;
|
||||
button.innerHTML = '<span class="glyphicon glyphicon-refresh glyphicon-spin"></span> Refreshing...';
|
||||
progressDiv.style.display = 'block';
|
||||
|
||||
// Reset counters
|
||||
refreshCount = 0;
|
||||
|
||||
// Start refresh cycle
|
||||
refreshInterval = setInterval(fetchUsageData, 1000);
|
||||
fetchUsageData(); // Initial fetch
|
||||
}
|
||||
|
||||
// Function to fetch usage data via AJAX
|
||||
function fetchUsageData() {
|
||||
const consumerId = '{{ consumer.consumer_id }}';
|
||||
const panel = document.getElementById('usageStatsPanel');
|
||||
|
||||
// Add refreshing effect to panel
|
||||
panel.classList.add('panel-refreshing');
|
||||
|
||||
$.ajax({
|
||||
url: '{% url "consumers-usage-data" consumer.consumer_id %}',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
timeout: 5000, // 5 second timeout
|
||||
success: function(data) {
|
||||
updateUsageDisplay(data);
|
||||
refreshCount++;
|
||||
|
||||
// Update progress bar
|
||||
const progress = (refreshCount / MAX_REFRESH_COUNT) * 100;
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
progressBar.style.width = progress + '%';
|
||||
progressBar.textContent = refreshCount + '/' + MAX_REFRESH_COUNT;
|
||||
|
||||
// Stop after 10 seconds
|
||||
if (refreshCount >= MAX_REFRESH_COUNT) {
|
||||
clearInterval(refreshInterval);
|
||||
resetRefreshButton();
|
||||
panel.classList.remove('panel-refreshing');
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error fetching usage data:', error);
|
||||
showRefreshError(error);
|
||||
|
||||
refreshCount++;
|
||||
const progress = (refreshCount / MAX_REFRESH_COUNT) * 100;
|
||||
document.getElementById('progressBar').style.width = progress + '%';
|
||||
|
||||
if (refreshCount >= MAX_REFRESH_COUNT) {
|
||||
clearInterval(refreshInterval);
|
||||
resetRefreshButton();
|
||||
panel.classList.remove('panel-refreshing');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to show refresh error
|
||||
function showRefreshError(error) {
|
||||
const errorDiv = document.getElementById('refreshError');
|
||||
if (!errorDiv) {
|
||||
const newErrorDiv = document.createElement('div');
|
||||
newErrorDiv.id = 'refreshError';
|
||||
newErrorDiv.className = 'alert alert-warning alert-dismissible';
|
||||
newErrorDiv.style.marginTop = '10px';
|
||||
newErrorDiv.innerHTML = '<button type="button" class="close" data-dismiss="alert">×</button><strong>Warning:</strong> Failed to fetch latest data. Continuing...';
|
||||
document.getElementById('usageStatsContent').appendChild(newErrorDiv);
|
||||
|
||||
// Auto-hide after 3 seconds
|
||||
setTimeout(function() {
|
||||
if (newErrorDiv.parentNode) {
|
||||
newErrorDiv.parentNode.removeChild(newErrorDiv);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update usage display with new data
|
||||
function updateUsageDisplay(data) {
|
||||
if (data && data.current_state) {
|
||||
const currentState = data.current_state;
|
||||
const periods = ['per_second', 'per_minute', 'per_hour', 'per_day', 'per_week', 'per_month'];
|
||||
|
||||
periods.forEach(function(period) {
|
||||
const periodData = currentState[period];
|
||||
if (periodData) {
|
||||
const periodDiv = document.querySelector('[data-period="' + period + '"]');
|
||||
if (periodDiv) {
|
||||
const callsSpan = periodDiv.querySelector('.usage-calls');
|
||||
const resetSpan = periodDiv.querySelector('.usage-reset');
|
||||
|
||||
if (callsSpan) {
|
||||
const oldCalls = callsSpan.textContent.match(/\d+/);
|
||||
const newCalls = periodData.calls_made;
|
||||
|
||||
// Check if calls increased
|
||||
const callsIncreased = oldCalls && parseInt(oldCalls[0]) < newCalls;
|
||||
|
||||
callsSpan.textContent = newCalls + ' calls made';
|
||||
|
||||
// Add visual feedback
|
||||
callsSpan.classList.add('updating');
|
||||
if (callsIncreased) {
|
||||
periodDiv.classList.add('data-updated');
|
||||
// Flash effect for increased calls
|
||||
callsSpan.style.backgroundColor = '#28a745';
|
||||
callsSpan.style.color = 'white';
|
||||
setTimeout(function() {
|
||||
callsSpan.style.backgroundColor = '#d4edda';
|
||||
callsSpan.style.color = '';
|
||||
}, 200);
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
callsSpan.classList.remove('updating');
|
||||
periodDiv.classList.remove('data-updated');
|
||||
callsSpan.style.backgroundColor = '';
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
if (resetSpan) {
|
||||
resetSpan.textContent = 'Resets in ' + periodData.reset_in_seconds + ' seconds';
|
||||
// Add subtle animation to reset timer
|
||||
resetSpan.style.opacity = '0.7';
|
||||
setTimeout(function() {
|
||||
resetSpan.style.opacity = '1';
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Show last updated time
|
||||
updateLastRefreshTime();
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update last refresh time
|
||||
function updateLastRefreshTime() {
|
||||
let timeDiv = document.getElementById('lastRefreshTime');
|
||||
if (!timeDiv) {
|
||||
timeDiv = document.createElement('small');
|
||||
timeDiv.id = 'lastRefreshTime';
|
||||
timeDiv.className = 'text-muted';
|
||||
timeDiv.style.display = 'block';
|
||||
timeDiv.style.textAlign = 'center';
|
||||
timeDiv.style.marginTop = '10px';
|
||||
document.getElementById('usageStatsContent').appendChild(timeDiv);
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
timeDiv.textContent = 'Last updated: ' + now.toLocaleTimeString();
|
||||
}
|
||||
|
||||
// Function to reset refresh button
|
||||
function resetRefreshButton() {
|
||||
const button = document.getElementById('refreshUsageBtn');
|
||||
const progressDiv = document.getElementById('refreshProgress');
|
||||
const panel = document.getElementById('usageStatsPanel');
|
||||
|
||||
button.disabled = false;
|
||||
button.innerHTML = '<span class="glyphicon glyphicon-refresh"></span> {% trans "Refresh (10s)" %}';
|
||||
progressDiv.style.display = 'none';
|
||||
document.getElementById('progressBar').style.width = '0%';
|
||||
document.getElementById('progressBar').textContent = '';
|
||||
|
||||
// Remove any error messages
|
||||
const errorDiv = document.getElementById('refreshError');
|
||||
if (errorDiv && errorDiv.parentNode) {
|
||||
errorDiv.parentNode.removeChild(errorDiv);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endcomment %}
|
||||
{% endblock extrajs %}
|
||||
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ URLs for consumers app
|
||||
|
||||
from django.urls import re_path
|
||||
|
||||
from .views import IndexView, DetailView, EnableView, DisableView
|
||||
from .views import IndexView, DetailView, EnableView, DisableView, UsageDataAjaxView
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r'^$',
|
||||
@ -20,4 +20,7 @@ urlpatterns = [
|
||||
re_path(r'^(?P<consumer_id>[0-9a-z\-]+)/disable$',
|
||||
DisableView.as_view(),
|
||||
name='consumers-disable'),
|
||||
re_path(r'^(?P<consumer_id>[0-9a-z\-]+)/usage-data$',
|
||||
UsageDataAjaxView.as_view(),
|
||||
name='consumers-usage-data'),
|
||||
]
|
||||
|
||||
@ -4,12 +4,15 @@ Views of consumers app
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
import datetime as dt_module
|
||||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.urls import reverse
|
||||
from django.views.generic import TemplateView, RedirectView, FormView
|
||||
from django.http import JsonResponse
|
||||
|
||||
from obp.api import API, APIError
|
||||
from base.filters import BaseFilter, FilterTime
|
||||
@ -110,6 +113,44 @@ class DetailView(LoginRequiredMixin, FormView):
|
||||
def get_form(self, *args, **kwargs):
|
||||
form = super(DetailView, self).get_form(*args, **kwargs)
|
||||
form.fields['consumer_id'].initial = self.kwargs['consumer_id']
|
||||
|
||||
# Get call limits data to populate form
|
||||
api = API(self.request.session.get('obp'))
|
||||
try:
|
||||
call_limits_urlpath = '/management/consumers/{}/consumer/call-limits'.format(self.kwargs['consumer_id'])
|
||||
call_limits = api.get(call_limits_urlpath)
|
||||
|
||||
if not ('code' in call_limits and call_limits['code'] >= 400):
|
||||
# Populate form with existing rate limiting data
|
||||
if 'from_date' in call_limits and call_limits['from_date']:
|
||||
try:
|
||||
from_date_str = call_limits['from_date'].replace('Z', '')
|
||||
# Parse and ensure no timezone info for form field
|
||||
dt = datetime.fromisoformat(from_date_str)
|
||||
if dt.tzinfo:
|
||||
dt = dt.replace(tzinfo=None)
|
||||
form.fields['from_date'].initial = dt
|
||||
except:
|
||||
pass
|
||||
if 'to_date' in call_limits and call_limits['to_date']:
|
||||
try:
|
||||
to_date_str = call_limits['to_date'].replace('Z', '')
|
||||
# Parse and ensure no timezone info for form field
|
||||
dt = datetime.fromisoformat(to_date_str)
|
||||
if dt.tzinfo:
|
||||
dt = dt.replace(tzinfo=None)
|
||||
form.fields['to_date'].initial = dt
|
||||
except:
|
||||
pass
|
||||
form.fields['per_second_call_limit'].initial = call_limits.get('per_second_call_limit', '-1')
|
||||
form.fields['per_minute_call_limit'].initial = call_limits.get('per_minute_call_limit', '-1')
|
||||
form.fields['per_hour_call_limit'].initial = call_limits.get('per_hour_call_limit', '-1')
|
||||
form.fields['per_day_call_limit'].initial = call_limits.get('per_day_call_limit', '-1')
|
||||
form.fields['per_week_call_limit'].initial = call_limits.get('per_week_call_limit', '-1')
|
||||
form.fields['per_month_call_limit'].initial = call_limits.get('per_month_call_limit', '-1')
|
||||
except:
|
||||
pass
|
||||
|
||||
return form
|
||||
|
||||
def form_valid(self, form):
|
||||
@ -121,15 +162,33 @@ class DetailView(LoginRequiredMixin, FormView):
|
||||
if api_consumers_form.is_valid():
|
||||
data = api_consumers_form.cleaned_data
|
||||
|
||||
urlpath = '/management/consumers/{}/consumer/calls_limit'.format(data['consumer_id'])
|
||||
urlpath = '/management/consumers/{}/consumer/call-limits'.format(data['consumer_id'])
|
||||
|
||||
# Helper function to format datetime to UTC
|
||||
def format_datetime_utc(dt):
|
||||
if not dt:
|
||||
return "2024-01-01T00:00:00Z"
|
||||
# Convert to UTC and format as required by API
|
||||
if dt.tzinfo:
|
||||
dt = dt.astimezone(dt_module.timezone.utc).replace(tzinfo=None)
|
||||
return dt.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
|
||||
payload = {
|
||||
'per_minute_call_limit': data['per_minute_call_limit'],
|
||||
'per_hour_call_limit': data['per_hour_call_limit'],
|
||||
'per_day_call_limit': data['per_day_call_limit'],
|
||||
'per_week_call_limit': data['per_week_call_limit'],
|
||||
'per_month_call_limit': data['per_month_call_limit']
|
||||
'from_date': format_datetime_utc(data['from_date']),
|
||||
'to_date': format_datetime_utc(data['to_date']),
|
||||
'per_second_call_limit': str(data['per_second_call_limit']) if data['per_second_call_limit'] is not None else "-1",
|
||||
'per_minute_call_limit': str(data['per_minute_call_limit']) if data['per_minute_call_limit'] is not None else "-1",
|
||||
'per_hour_call_limit': str(data['per_hour_call_limit']) if data['per_hour_call_limit'] is not None else "-1",
|
||||
'per_day_call_limit': str(data['per_day_call_limit']) if data['per_day_call_limit'] is not None else "-1",
|
||||
'per_week_call_limit': str(data['per_week_call_limit']) if data['per_week_call_limit'] is not None else "-1",
|
||||
'per_month_call_limit': str(data['per_month_call_limit']) if data['per_month_call_limit'] is not None else "-1"
|
||||
}
|
||||
|
||||
response = self.api.put(urlpath, payload)
|
||||
if 'code' in response and response['code'] >= 400:
|
||||
messages.error(self.request, response['message'])
|
||||
return super(DetailView, self).form_invalid(api_consumers_form)
|
||||
|
||||
except APIError as err:
|
||||
messages.error(self.request, err)
|
||||
return super(DetailView, self).form_invalid(api_consumers_form)
|
||||
@ -137,31 +196,72 @@ class DetailView(LoginRequiredMixin, FormView):
|
||||
messages.error(self.request, "{}".format(err))
|
||||
return super(DetailView, self).form_invalid(api_consumers_form)
|
||||
|
||||
msg = 'calls limit of consumer {} has been updated successfully.'.format(
|
||||
msg = 'Rate limits for consumer {} have been updated successfully.'.format(
|
||||
data['consumer_id'])
|
||||
messages.success(self.request, msg)
|
||||
self.success_url = self.request.path
|
||||
return super(DetailView, self).form_valid(api_consumers_form)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# Check if this is an AJAX request for usage data
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return self.get_usage_data_ajax()
|
||||
return super(DetailView, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_usage_data_ajax(self):
|
||||
"""Return usage data as JSON for AJAX refresh"""
|
||||
api = API(self.request.session.get('obp'))
|
||||
try:
|
||||
call_limits_urlpath = '/management/consumers/{}/consumer/call-limits'.format(self.kwargs['consumer_id'])
|
||||
call_limits = api.get(call_limits_urlpath)
|
||||
|
||||
if 'code' in call_limits and call_limits['code'] >= 400:
|
||||
return JsonResponse({'error': call_limits['message']}, status=400)
|
||||
|
||||
return JsonResponse(call_limits)
|
||||
except APIError as err:
|
||||
return JsonResponse({'error': str(err)}, status=500)
|
||||
except Exception as err:
|
||||
return JsonResponse({'error': str(err)}, status=500)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
api = API(self.request.session.get('obp'))
|
||||
consumer = {}
|
||||
call_limits = {}
|
||||
|
||||
try:
|
||||
urlpath = '/management/consumers/{}'.format(self.kwargs['consumer_id'])
|
||||
consumer = api.get(urlpath)
|
||||
consumer['created'] = datetime.strptime(
|
||||
consumer['created'], settings.API_DATE_FORMAT_WITH_SECONDS )
|
||||
|
||||
call_limits_urlpath = '/management/consumers/{}/consumer/call-limits'.format(self.kwargs['consumer_id'])
|
||||
consumer_call_limtis = api.get(call_limits_urlpath)
|
||||
if 'code' in consumer_call_limtis and consumer_call_limtis['code'] >= 400:
|
||||
messages.error(self.request, "{}".format(consumer_call_limtis['message']))
|
||||
if 'code' in consumer and consumer['code'] >= 400:
|
||||
messages.error(self.request, consumer['message'])
|
||||
consumer = {}
|
||||
else:
|
||||
consumer['per_minute_call_limit'] = consumer_call_limtis['per_minute_call_limit']
|
||||
consumer['per_hour_call_limit'] = consumer_call_limtis['per_hour_call_limit']
|
||||
consumer['per_day_call_limit'] = consumer_call_limtis['per_day_call_limit']
|
||||
consumer['per_week_call_limit'] = consumer_call_limtis['per_week_call_limit']
|
||||
consumer['per_month_call_limit'] = consumer_call_limtis['per_month_call_limit']
|
||||
consumer['created'] = datetime.strptime(
|
||||
consumer['created'], settings.API_DATE_FORMAT_WITH_SECONDS )
|
||||
|
||||
# Get call limits using the correct API endpoint
|
||||
call_limits_urlpath = '/management/consumers/{}/consumer/call-limits'.format(self.kwargs['consumer_id'])
|
||||
call_limits = api.get(call_limits_urlpath)
|
||||
|
||||
if 'code' in call_limits and call_limits['code'] >= 400:
|
||||
messages.error(self.request, "{}".format(call_limits['message']))
|
||||
call_limits = {}
|
||||
else:
|
||||
# Merge call limits data into consumer object
|
||||
consumer.update({
|
||||
'from_date': call_limits.get('from_date', ''),
|
||||
'to_date': call_limits.get('to_date', ''),
|
||||
'per_second_call_limit': call_limits.get('per_second_call_limit', '-1'),
|
||||
'per_minute_call_limit': call_limits.get('per_minute_call_limit', '-1'),
|
||||
'per_hour_call_limit': call_limits.get('per_hour_call_limit', '-1'),
|
||||
'per_day_call_limit': call_limits.get('per_day_call_limit', '-1'),
|
||||
'per_week_call_limit': call_limits.get('per_week_call_limit', '-1'),
|
||||
'per_month_call_limit': call_limits.get('per_month_call_limit', '-1'),
|
||||
'current_state': call_limits.get('current_state', {}),
|
||||
'created_at': call_limits.get('created_at', ''),
|
||||
'updated_at': call_limits.get('updated_at', ''),
|
||||
})
|
||||
|
||||
except APIError as err:
|
||||
messages.error(self.request, err)
|
||||
@ -169,11 +269,31 @@ class DetailView(LoginRequiredMixin, FormView):
|
||||
messages.error(self.request, "{}".format(err))
|
||||
finally:
|
||||
context.update({
|
||||
'consumer': consumer
|
||||
'consumer': consumer,
|
||||
'call_limits': call_limits
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class UsageDataAjaxView(LoginRequiredMixin, TemplateView):
|
||||
"""AJAX view to return usage data for real-time updates"""
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
api = API(self.request.session.get('obp'))
|
||||
try:
|
||||
call_limits_urlpath = '/management/consumers/{}/consumer/call-limits'.format(self.kwargs['consumer_id'])
|
||||
call_limits = api.get(call_limits_urlpath)
|
||||
|
||||
if 'code' in call_limits and call_limits['code'] >= 400:
|
||||
return JsonResponse({'error': call_limits['message']}, status=400)
|
||||
|
||||
return JsonResponse(call_limits)
|
||||
except APIError as err:
|
||||
return JsonResponse({'error': str(err)}, status=500)
|
||||
except Exception as err:
|
||||
return JsonResponse({'error': str(err)}, status=500)
|
||||
|
||||
|
||||
class EnableDisableView(LoginRequiredMixin, RedirectView):
|
||||
"""View to enable or disable a consumer"""
|
||||
enabled = False
|
||||
|
||||
@ -43,7 +43,7 @@ class API(object):
|
||||
self.start_session(session_data)
|
||||
self.session_data = session_data
|
||||
|
||||
def call(self, method='GET', url='', payload=None, version=settings.API_VERSION['v500']):
|
||||
def call(self, method='GET', url='', payload=None, version=settings.API_VERSION['v510']):
|
||||
"""Workhorse which actually calls the API"""
|
||||
log(logging.INFO, '{} {}'.format(method, url))
|
||||
if payload:
|
||||
@ -64,7 +64,7 @@ class API(object):
|
||||
response.execution_time = elapsed
|
||||
return response
|
||||
|
||||
def get(self, urlpath='', version=settings.API_VERSION['v500']):
|
||||
def get(self, urlpath='', version=settings.API_VERSION['v510']):
|
||||
"""
|
||||
Gets data from the API
|
||||
|
||||
@ -77,7 +77,7 @@ class API(object):
|
||||
else:
|
||||
return response
|
||||
|
||||
def delete(self, urlpath, version=settings.API_VERSION['v500']):
|
||||
def delete(self, urlpath, version=settings.API_VERSION['v510']):
|
||||
"""
|
||||
Deletes data from the API
|
||||
|
||||
@ -87,7 +87,7 @@ class API(object):
|
||||
response = self.call('DELETE', url)
|
||||
return self.handle_response(response)
|
||||
|
||||
def post(self, urlpath, payload, version=settings.API_VERSION['v500']):
|
||||
def post(self, urlpath, payload, version=settings.API_VERSION['v510']):
|
||||
"""
|
||||
Posts data to given urlpath with given payload
|
||||
|
||||
@ -97,7 +97,7 @@ class API(object):
|
||||
response = self.call('POST', url, payload)
|
||||
return self.handle_response(response)
|
||||
|
||||
def put(self, urlpath, payload, version=settings.API_VERSION['v500']):
|
||||
def put(self, urlpath, payload, version=settings.API_VERSION['v510']):
|
||||
"""
|
||||
Puts data on given urlpath with given payload
|
||||
|
||||
@ -175,4 +175,4 @@ class API(object):
|
||||
result = self.get('/users')
|
||||
for user in result['users']:
|
||||
choices.append((user['user_id'], user['username']))
|
||||
return choices
|
||||
return choices
|
||||
|
||||
Loading…
Reference in New Issue
Block a user