forked from uncloud/uncloud
LDAP Integration
This commit is contained in:
parent
8ca5b2d104
commit
02ad7a9441
26 changed files with 424 additions and 190 deletions
|
@ -3490,3 +3490,19 @@ body, html {
|
|||
#card-errors {
|
||||
color: #e41d25;
|
||||
}
|
||||
|
||||
.bill-cancelled {
|
||||
background-color: #e41d25 !important;
|
||||
}
|
||||
.bill-paid {
|
||||
background-color: #28a745!important;
|
||||
}
|
||||
.bill-new {
|
||||
background-color: #17a2b8!important;
|
||||
}
|
||||
.email_list .unverified {
|
||||
color: #f7656e;
|
||||
}
|
||||
.email_list .primary {
|
||||
color: #126567;
|
||||
}
|
||||
|
|
|
@ -3,10 +3,15 @@ function fetch_pricing() {
|
|||
var cores = $('#cores').val();
|
||||
var memory = $('#memory').val();
|
||||
var storage = $('#storage').val();
|
||||
var country = $('select[name="country"]').val();
|
||||
var data = { cores: cores, memory: memory, storage: storage};
|
||||
if (country != undefined) {
|
||||
data['country'] = country;
|
||||
}
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: url,
|
||||
data: { cores: cores, memory: memory, storage: storage},
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
success: function (data) {
|
||||
if (data && data['total']) {
|
||||
|
@ -27,6 +32,20 @@ function fetch_pricing() {
|
|||
});
|
||||
};
|
||||
|
||||
function init_checkout_btn() {
|
||||
var selected_opt = $('input[name="payment_card"]:checked').val();
|
||||
if( selected_opt == 'new') {
|
||||
$('#checkout-btn').hide();
|
||||
$('#newcard').show();
|
||||
} else if(selected_opt == undefined) {
|
||||
$('#newcard').hide();
|
||||
$('#checkout-btn').hide();
|
||||
} else {
|
||||
$('#newcard').hide();
|
||||
$('#checkout-btn').show();
|
||||
}
|
||||
}
|
||||
|
||||
function incrementValue(e) {
|
||||
var valueElement = $(e.target).parent().parent().find('input');
|
||||
var step = $(valueElement).attr('step');
|
||||
|
@ -147,15 +166,13 @@ $(document).ready(function () {
|
|||
submitBillingForm();
|
||||
});
|
||||
|
||||
init_checkout_btn();
|
||||
|
||||
$('input[name="payment_card"]').change(function(e) {
|
||||
if($('input[name="payment_card"]:checked').val() == 'new') {
|
||||
$('#checkout-btn').hide();
|
||||
$('#newcard').show();
|
||||
} else {
|
||||
$('#newcard').hide();
|
||||
$('#checkout-btn').show();
|
||||
}
|
||||
init_checkout_btn();
|
||||
});
|
||||
$('select[name="country"]').change(function(e) {
|
||||
fetch_pricing();
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@ def send_warning_email(bill, html_message):
|
|||
next_run=timezone.now() + timedelta(hours=1))
|
||||
|
||||
def charge_open_bills():
|
||||
un_paid_bills = Bill.objects.filter(is_closed=False)
|
||||
un_paid_bills = Bill.objects.filter(status="new")
|
||||
for bill in un_paid_bills:
|
||||
date_diff = (date.today() - bill.due_date.date()).days
|
||||
# If there is not enough money in the account 7 days before renewal, the system sends a warning
|
||||
|
@ -47,8 +47,8 @@ def charge_open_bills():
|
|||
if balance < 0:
|
||||
payment = Payment.objects.create(owner=bill.owner, amount=balance, source='stripe')
|
||||
if payment:
|
||||
bill.close()
|
||||
bill.close()
|
||||
bill.close(status="paid")
|
||||
bill.close(status="cancelled")
|
||||
except Exception as e:
|
||||
log.error(f"It seems that there is issue in payment for {bill.owner.name}", e)
|
||||
# do nothing
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% load static i18n %} {% get_current_language as LANGUAGE_CODE %}
|
||||
{% load static compress i18n %} {% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
@ -19,7 +19,9 @@
|
|||
============================================= -->
|
||||
<link href="{% static 'matrixhosting/css/bootstrap.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'matrixhosting/css/fontawesome-all.min.css' %}" rel="stylesheet"type="text/css"/>
|
||||
<link href="{% static 'matrixhosting/css/theme.css' %}" rel="stylesheet" type="text/css"/>
|
||||
{% compress css %}
|
||||
<link href="{% static 'matrixhosting/css/theme.css' %}" rel="stylesheet" type="text/css"/>
|
||||
{% endcompress %}
|
||||
<!-- Colors Css -->
|
||||
</head>
|
||||
<body>
|
||||
|
@ -60,18 +62,31 @@
|
|||
<div class="container my-4">
|
||||
<div class="row">
|
||||
<div class="col-11 col-lg-9 col-xl-8 mx-auto">
|
||||
{% if messages %}
|
||||
<div class="row">
|
||||
{% for message in messages %}
|
||||
{% if 'error' in message.tags %}
|
||||
<div class="col-lg-12 alert alert-danger" role="alert">
|
||||
<h4 class="alert-heading">{%trans "Error!" %}</h4>
|
||||
<p class="mb-0">
|
||||
{{ message|safe }}
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-lg-12 alert alert-success" role="alert">
|
||||
<p class="mb-0 float-left">
|
||||
{{ message|safe }}
|
||||
</p>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
{% if messages %}
|
||||
<div>
|
||||
<strong>Messages:</strong>
|
||||
<ul>
|
||||
{% for message in messages %}
|
||||
<li>{{message}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
74
matrixhosting/templates/account/email.html
Normal file
74
matrixhosting/templates/account/email.html
Normal file
|
@ -0,0 +1,74 @@
|
|||
{% extends "account/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_title %}{% trans "E-mail Addresses" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "E-mail Addresses" %}</h1>
|
||||
{% if user.emailaddress_set.all %}
|
||||
<p>{% trans 'The following e-mail addresses are associated with your account:' %}</p>
|
||||
|
||||
<form action="{% url 'account_email' %}" class="email_list" method="post">
|
||||
{% csrf_token %}
|
||||
<fieldset class="blockLabels">
|
||||
|
||||
{% for emailaddress in user.emailaddress_set.all %}
|
||||
<div class="ctrlHolder">
|
||||
<label for="email_radio_{{forloop.counter}}" class="{% if emailaddress.primary %}primary_email{%endif%}">
|
||||
|
||||
<input id="email_radio_{{forloop.counter}}" type="radio" name="email" {% if emailaddress.primary or user.emailaddress_set.count == 1 %}checked="checked"{%endif %} value="{{emailaddress.email}}"/>
|
||||
|
||||
{{ emailaddress.email }}
|
||||
{% if emailaddress.verified %}
|
||||
<span class="pl-2 verified">{% trans "Verified" %}</span>
|
||||
{% else %}
|
||||
<span class="pl-2 unverified">{% trans "Unverified" %}</span>
|
||||
{% endif %}
|
||||
{% if emailaddress.primary %}<span class="pl-2 primary">{% trans "Primary" %}</span>{% endif %}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="buttonHolder">
|
||||
<button class="btn btn-secendary" type="submit" name="action_primary" >{% trans 'Make Primary' %}</button>
|
||||
<button class="btn btn-secendary" type="submit" name="action_send" >{% trans 'Re-send Verification' %}</button>
|
||||
<!-- <button class="btn btn-primary" type="submit" name="action_remove" >{% trans 'Remove' %}</button> -->
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
<p><strong>{% trans 'Warning:'%}</strong> {% trans "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
<!-- {% if can_add_email %}
|
||||
<h2>{% trans "Add E-mail Address" %}</h2>
|
||||
|
||||
<form method="post" action="{% url 'account_email' %}" class="add_email">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button class="btn btn-primary" name="action_add" type="submit">{% trans "Add E-mail" %}</button>
|
||||
</form>
|
||||
{% endif %} -->
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block extra_body %}
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var message = "{% trans 'Do you really want to remove the selected e-mail address?' %}";
|
||||
var actions = document.getElementsByName('action_remove');
|
||||
if (actions.length) {
|
||||
actions[0].addEventListener("click", function(e) {
|
||||
if (! confirm(message)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
{% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Confirm E-mail Address" %}</h1>
|
||||
|
||||
|
@ -17,7 +16,7 @@
|
|||
|
||||
<form method="post" action="{% url 'account_confirm_email' confirmation.key %}">
|
||||
{% csrf_token %}
|
||||
<button type="submit">{% trans 'Confirm' %}</button>
|
||||
<button class="btn btn-primary" type="submit">{% trans 'Confirm' %}</button>
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
|
|
|
@ -13,8 +13,22 @@
|
|||
{% if form %}
|
||||
<form method="POST" action="{{ action_url }}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" name="action" value="{% trans 'change password' %}"/>
|
||||
{% if form.non_field_errors %}
|
||||
<div class="p-2 my-4">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<label for="password1">{% trans "New Password (again)" %}</label>
|
||||
<input type="password" class="form-control" name="password1" id="password1" {% if form.password1.value != None %}value="{{ form.password1.value }}"{% endif %} required placeholder="{% trans 'New Passowrd' %}">
|
||||
{{ form.login.errors }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password2">{% trans "New Password" %}</label>
|
||||
<input type="password" class="form-control" name="password2" id="password2" {% if form.password2.value != None %}value="{{ form.password2.value }}"{% endif %} required placeholder="{% trans 'New Passowrd (again)' %}">
|
||||
{{ form.login.errors }}
|
||||
</div>
|
||||
<input class="btn btn-primary" type="submit" name="action" value="{% trans 'change password' %}"/>
|
||||
</form>
|
||||
{% else %}
|
||||
<p>{% trans 'Your password is now changed.' %}</p>
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
{% load i18n %}
|
||||
{% load account socialaccount %}
|
||||
|
||||
{% block head_title %}{% trans "Sign In" %}{% endblock %}
|
||||
{% block head_title %}{% trans "Sign Up" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h3 class="font-weight-400 mb-4">Log In</h3>
|
||||
<h3 class="font-weight-400 mb-4">{% trans "Sign Up" %}</h3>
|
||||
<form id="signup_form" method="post" action="{% url 'account_signup' %}">
|
||||
{% csrf_token %}
|
||||
{% if form.non_field_errors %}
|
||||
|
@ -19,8 +19,22 @@
|
|||
<input type="text" class="form-control" name="username" id="username" {% if form.username.value != None %}value="{{ form.username.value }}"{% endif %} required placeholder="{% trans 'Enter Username' %}">
|
||||
{{ form.username.errors }}
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="col-lg-6">
|
||||
<div class="form-group">
|
||||
<label for="first_name">{% trans "First Name" %}</label>
|
||||
<input id="first_name" name="first_name" type="text" class="form-control" {% if form.first_name.value != None %}value="{{ form.first_name.value }}"{% endif %}placeholder="{% trans 'First Name' %}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="form-group">
|
||||
<label for="first_name">{% trans "Last Name" %}</label>
|
||||
<input id="last_name" name="last_name" type="text" class="form-control" {% if form.last_name.value != None %}value="{{ form.last_name.value }}"{% endif %}placeholder="{% trans 'Last Name' %}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">{% trans "E-mail (optional)" %}</label>
|
||||
<label for="email">{% trans "E-mail" %}</label>
|
||||
<input type="text" class="form-control" name="email" id="email" {% if form.email.value != None %}value="{{ form.email.value }}"{% endif %} placeholder="{% trans 'Enter Email Address' %}">
|
||||
{{ form.email.errors }}
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<ul class="nav nav-tabs flex-column" id="myTabVertical" role="tablist">
|
||||
<li class="nav-item"> <a class="nav-link" id="first-tab" href="{% url 'matrixhosting:billing' %}">{% trans "Payment Moves"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link" id="first-tab" href="{% url 'matrixhosting:billing' %}">{% trans "Transaction History"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link active" id="second-tab" href="{% url 'matrixhosting:bills' %}">{% trans "Bills"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link" id="fourth-tab" href="{% url 'matrixhosting:cards' %}">{% trans "Payment Methods"%}</a> </li>
|
||||
</ul>
|
||||
|
@ -55,8 +55,8 @@
|
|||
<div class="col-2 col-sm-2 text-center"><span class="">{% trans "Creation Date"%}</span></div>
|
||||
<div class="col-2 col-sm-2 text-center">{% trans "Amount"%}</div>
|
||||
<div class="col-2 col-sm-2 text-center">{% trans "Due Date"%}</div>
|
||||
<div class="col-1 col-sm-1 text-center">{% trans "Closed"%}</div>
|
||||
<div class="col-2 col-sm-2 text-center">{% trans "Download"%}</div>
|
||||
<div class="col-2 col-sm-2 text-center">{% trans "Status"%}</div>
|
||||
<div class="col-1 col-sm-1 text-center"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Title End -->
|
||||
|
@ -74,14 +74,10 @@
|
|||
<div class="col-2 col-sm-2 text-center"> <span class="text-2 font-weight-300">{{bill.creation_date|date:"Y-m-d"}}</span></div>
|
||||
<div class="col-2 col-sm-2 text-center"><span class="text-2 font-weight-300">{{bill.sum}}</span><span class="text-1 text-uppercase"> {{bill.currency}}</span></div>
|
||||
<div class="col-2 col-sm-2 text-center"> <span class="text-2 font-weight-300">{{bill.due_date|date:"Y-m-d"}}</span> </div>
|
||||
<div class="col-1 col-sm-1 text-center text-3">
|
||||
{% if bill.is_closed %}
|
||||
<span class="text-success" data-toggle="tooltip" data-original-title="Closed or Paid"><i class="fas fa-check-circle"></i></span>
|
||||
{%else%}
|
||||
<span class="text-danger" data-toggle="tooltip" data-original-title="Pending"><i class="text-danger fas fa-times-circle"></i></span>
|
||||
{%endif%}
|
||||
<div class="col-2 col-sm-2 text-center text-1">
|
||||
<span class="px-2 py-1 text-white bill-{{bill.status}}" data-toggle="tooltip" data-original-title="{{bill.status}}">{{bill.get_status_display}}</span>
|
||||
</div>
|
||||
<div class="col-2 col-sm-2 text-center text-3">
|
||||
<div class="col-1 col-sm-1 text-center text-3">
|
||||
<form method="get" action="{% url 'matrix:invoice_download' bill_id=bill.id %}">
|
||||
<button class="download-bill border border-primary" type="submit"><span class="text-primary" data-toggle="tooltip" data-original-title="Download"><i class="text-primary fas fa-file-download"></i></span></button>
|
||||
</form>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<ul class="nav nav-tabs flex-column" id="myTabVertical" role="tablist">
|
||||
<li class="nav-item"> <a class="nav-link" id="first-tab" href="{% url 'matrixhosting:billing' %}">{% trans "Payment Moves"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link" id="first-tab" href="{% url 'matrixhosting:billing' %}">{% trans "Transaction History"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link" id="second-tab" href="{% url 'matrixhosting:bills' %}">{% trans "Bills"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link active" id="fourth-tab" href="{% url 'matrixhosting:cards' %}">{% trans "Payment Methods"%}</a> </li>
|
||||
</ul>
|
||||
|
@ -179,7 +179,9 @@
|
|||
$.ajax({
|
||||
type: 'DELETE',
|
||||
url: url,
|
||||
data: {csrfmiddlewaretoken: '{{ csrf_token }}'},
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader("X-CSRFToken", '{{ csrf_token }}');
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function (result) {
|
||||
location.reload();
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="bg-white rounded shadow-md pt-3">
|
||||
<div class="price-text text-white bg-dark-3 text-center mt-3 p-3">
|
||||
<p class="mb-0">
|
||||
{% if matrix_vm_pricing.set_up_fees %}<span class="mr-1">Setup Fees</span>{{ matrix_vm_pricing.set_up_fees }} CHF<br>{% endif %}
|
||||
{% if matrix_vm_pricing.set_up_fees %}<span class="mr-1">Setup Fees</span>{{ matrix_vm_pricing.set_up_fees }} CHF included<br>{% endif %}
|
||||
{% if matrix_vm_pricing.discount_amount %}
|
||||
{% trans "Discount" %} <span class="text-primary ml-1">{{ matrix_vm_pricing.discount_amount }}</span> CHF
|
||||
{% endif %}
|
||||
|
@ -54,8 +54,8 @@
|
|||
{% if matrix_vm_pricing.discount_amount %}
|
||||
<p class="text-muted mb-1">{% trans "You save" %} <span class="text-dark">{{ matrix_vm_pricing.discount_amount }}</span> CHF</p>
|
||||
{% endif %}
|
||||
<p class="text-muted mb-1">{% trans "Subtotal" %} <span id="subtotal" class="text-dark"></span> CHF/{% trans "month" %}</p>
|
||||
<p class="text-muted text-3 mb-2">{% trans "Total" %}<span id="total" class="font-weight-500 text-primary pl-2"></span><span class="text-2 p-1">CHF/{% trans "month" %}</span></p>
|
||||
<p class="text-muted mb-1">{% trans "Subtotal" %} <span id="subtotal" class="text-dark"></span> CHF</p>
|
||||
<p class="text-muted text-3 mb-2">{% trans "Total" %}<span id="total" class="font-weight-500 text-primary pl-2"></span><span class="text-2 p-1">CHF</span></p>
|
||||
<input type="hidden" name="pricing_name" id="pricing_name" value="{% if matrix_vm_pricing.name %}{{matrix_vm_pricing.name}}{% else %}unknown{% endif%}"></input>
|
||||
<input type="submit" class="btn btn-primary btn-block" value="{% trans 'Continue' %}"></input>
|
||||
</form>
|
||||
|
|
|
@ -186,9 +186,9 @@
|
|||
{% with cards_len=cards|length %}
|
||||
<p class="text-muted">
|
||||
{% if cards_len > 0 %}
|
||||
{% blocktrans %}You haven't enough balance in your wallet, Please select one of the cards that you used before or fill in your credit card information below.{% endblocktrans %}
|
||||
{% blocktrans %}There is not enough balance in your account to proceed with this order. You can select a card or add a new card to fill up your account balance to proceed with the order.{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans %}You haven't enough balance in your wallet, Please fill in your credit card information below.{% endblocktrans %}
|
||||
{% blocktrans %}There is not enough balance in your account to proceed with this order. Please fill in your credit card information below.{% endblocktrans %}
|
||||
{% endif %}
|
||||
</p>
|
||||
<div>
|
||||
|
@ -229,7 +229,7 @@
|
|||
</div>
|
||||
<div id="has-enough-balance" {% if show_cards %}style="display:none;"{% endif %}>
|
||||
<p class="text-muted">
|
||||
{% blocktrans %}Your wallet has enough balance, Press Continue to fill the VM instance settings.{% endblocktrans %}
|
||||
{% blocktrans %}You can use your account balance to make the payment. Press Continue to select the domain settings. You can review and confirm your order and payment in the next page.{% endblocktrans %}
|
||||
</p>
|
||||
<div class="text-right">
|
||||
<button id="continue-btn" class="btn btn-primary btn-wide">{%trans "Continue" %}</button>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<ul class="nav nav-tabs flex-column" id="myTabVertical" role="tablist">
|
||||
<li class="nav-item"> <a class="nav-link active" id="first-tab" href="{% url 'matrixhosting:billing' %}">{% trans "Payment Moves"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link active" id="first-tab" href="{% url 'matrixhosting:billing' %}">{% trans "Transaction History"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link" id="second-tab" href="{% url 'matrixhosting:bills' %}">{% trans "Bills"%}</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link" id="fourth-tab" href="{% url 'matrixhosting:cards' %}">{% trans "Payment Methods"%}</a> </li>
|
||||
</ul>
|
||||
|
|
|
@ -24,6 +24,6 @@ def finalize_order(request, customer, billing_address,
|
|||
if payment:
|
||||
#Close the bill as the payment has been added
|
||||
VMInstance.create_instance(order)
|
||||
bill.close()
|
||||
bill.close(status="paid")
|
||||
|
||||
return order, bill
|
|
@ -30,6 +30,7 @@ import uncloud_pay.stripe as uncloud_stripe
|
|||
from .models import VMInstance
|
||||
from .serializers import *
|
||||
from .utils import *
|
||||
from ldap.ldapobject import LDAPObject
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
14
uncloud/.env
14
uncloud/.env
|
@ -1,6 +1,6 @@
|
|||
ALLOWED_HOSTS=
|
||||
STRIPE_KEY=
|
||||
STRIPE_PUBLIC_KEY=p
|
||||
STRIPE_PUBLIC_KEY=
|
||||
DATABASE_ENGINE=django.db.backends.sqlite3
|
||||
DATABASE_NAME=
|
||||
DATABASE_HOST=
|
||||
|
@ -16,4 +16,14 @@ GITLAB_PROJECT_ID=
|
|||
GITLAB_OAUTH_TOKEN=
|
||||
GITLAB_AUTHOR_EMAIL=
|
||||
GITLAB_AUTHOR_NAME=
|
||||
WKHTMLTOPDF_CMD=/usr/local/bin/wkhtmltopdf
|
||||
WKHTMLTOPDF_CMD=
|
||||
LDAP_DEFAULT_START_UID=
|
||||
AUTH_LDAP_SERVER_HOST=
|
||||
AUTH_LDAP_SERVER_URI=
|
||||
AUTH_LDAP_BIND_DN=
|
||||
AUTH_LDAP_BIND_PASSWORD=
|
||||
LDAP_ADMIN_DN=
|
||||
LDAP_ADMIN_PASSWORD=
|
||||
LDAP_CUSTOMER_GROUP_ID=
|
||||
LDAP_CUSTOMER_DN=
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
class UserDeleteForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = []
|
||||
|
||||
|
|
|
@ -19,8 +19,19 @@ import environ
|
|||
from django.core.management.utils import get_random_secret_key
|
||||
from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
|
||||
|
||||
|
||||
LOGGING = {}
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
},
|
||||
},
|
||||
'root': {
|
||||
'handlers': ['console'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
}
|
||||
|
||||
# Initialise environment variables
|
||||
env = environ.Env()
|
||||
|
@ -135,25 +146,40 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||
# Authall Settings
|
||||
ACCOUNT_AUTHENTICATION_METHOD = "username"
|
||||
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 1
|
||||
ACCOUNT_EMAIL_REQUIRED = False
|
||||
ACCOUNT_EMAIL_VERIFICATION = "optional"
|
||||
ACCOUNT_UNIQUE_EMAIL = False
|
||||
ACCOUNT_EMAIL_REQUIRED = True
|
||||
ACCOUNT_UNIQUE_EMAIL = True
|
||||
MAX_EMAIL_ADDRESSES = 1
|
||||
################################################################################
|
||||
# AUTH/LDAP
|
||||
|
||||
AUTH_LDAP_SERVER_URI = ""
|
||||
AUTH_LDAP_BIND_DN = ""
|
||||
AUTH_LDAP_BIND_PASSWORD = ""
|
||||
AUTH_LDAP_USER_SEARCH = LDAPSearch("dc=example,dc=com",
|
||||
LDAP_ENABLED = True
|
||||
AUTH_LDAP_SERVER_HOST = env('AUTH_LDAP_SERVER_HOST')
|
||||
AUTH_LDAP_SERVER_URI = env('AUTH_LDAP_SERVER_URI')
|
||||
AUTH_LDAP_BIND_DN = env('AUTH_LDAP_BIND_DN')
|
||||
AUTH_LDAP_BIND_PASSWORD = env('AUTH_LDAP_BIND_PASSWORD')
|
||||
|
||||
AUTH_LDAP_USER_DN_TEMPLATE = "uid=%(user)s,ou=customers,dc=ungleich,dc=ch"
|
||||
AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=customers,dc=ungleich,dc=ch",
|
||||
ldap.SCOPE_SUBTREE,
|
||||
"(uid=%(user)s)")
|
||||
# BIND_AS_AUTHENTICATING_USER = True
|
||||
START_TLS = True
|
||||
LDAP_ADMIN_DN = env("LDAP_ADMIN_DN")
|
||||
LDAP_ADMIN_PASSWORD = env("LDAP_ADMIN_PASSWORD")
|
||||
LDAP_CUSTOMER_GROUP_ID = env("LDAP_CUSTOMER_GROUP_ID")
|
||||
LDAP_CUSTOMER_DN=env("LDAP_CUSTOMER_DN")
|
||||
|
||||
#AUTH_LDAP_USER_QUERY_FIELD = "email"
|
||||
AUTH_LDAP_USER_ATTR_MAP = {
|
||||
"first_name": "givenName",
|
||||
"first_name": "cn",
|
||||
"last_name": "sn",
|
||||
"email": "mail"
|
||||
}
|
||||
LDAP_DEFAULT_START_UID = int(env('LDAP_DEFAULT_START_UID'))
|
||||
|
||||
LDAP_MAX_UID_FILE_PATH = os.environ.get('LDAP_MAX_UID_FILE_PATH',
|
||||
os.path.join(os.path.abspath(os.path.dirname(__file__)), 'ldap_max_uid_file')
|
||||
)
|
||||
################################################################################
|
||||
# AUTH/Django
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
|
@ -162,9 +188,16 @@ AUTHENTICATION_BACKENDS = [
|
|||
'allauth.account.auth_backends.AuthenticationBackend',
|
||||
]
|
||||
|
||||
AUTH_USER_MODEL = 'uncloud_auth.User'
|
||||
|
||||
|
||||
AUTH_USER_MODEL = 'uncloud_auth.User'
|
||||
ACCOUNT_FORMS = {
|
||||
'signup': 'uncloud_auth.forms.MySignupForm',
|
||||
'change_password': 'uncloud_auth.forms.MyChangePasswordForm',
|
||||
'set_password': 'uncloud_auth.forms.MySetPasswordForm',
|
||||
'reset_password_from_key': 'uncloud_auth.forms.MyResetPasswordKeyForm',
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# AUTH/REST
|
||||
REST_FRAMEWORK = {
|
||||
|
@ -233,26 +266,14 @@ UNCLOUD_ADMIN_NAME = "uncloud-admin"
|
|||
LOGIN_REDIRECT_URL = '/'
|
||||
LOGOUT_REDIRECT_URL = '/'
|
||||
|
||||
# replace these in local_settings.py
|
||||
AUTH_LDAP_SERVER_URI = "ldaps://ldap1.example.com,ldaps://ldap2.example.com"
|
||||
AUTH_LDAP_BIND_DN="uid=django,ou=system,dc=example,dc=com"
|
||||
AUTH_LDAP_BIND_PASSWORD="a very secure ldap password"
|
||||
AUTH_LDAP_USER_SEARCH = LDAPSearch("dc=example,dc=com",
|
||||
ldap.SCOPE_SUBTREE,
|
||||
"(uid=%(user)s)")
|
||||
|
||||
# where to create customers
|
||||
LDAP_CUSTOMER_DN="ou=customer,dc=example,dc=com"
|
||||
|
||||
EMAIL_USE_TLS = True
|
||||
EMAIL_HOST = env('EMAIL_HOST')
|
||||
|
||||
EMAIL_PORT = 25
|
||||
EMAIL_HOST_USER = DEFAULT_FROM_EMAIL = env('EMAIL_HOST_USER')
|
||||
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD')
|
||||
DEFAULT_FROM_EMAIL = 'support@ungleich.ch'
|
||||
RENEWAL_FROM_EMAIL = 'support@ungleich.ch'
|
||||
# Should be removed in production
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
|
||||
##############
|
||||
# Jobs
|
||||
|
|
69
uncloud_auth/forms.py
Normal file
69
uncloud_auth/forms.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
import logging
|
||||
|
||||
from allauth.account.forms import SignupForm, ChangePasswordForm, ResetPasswordKeyForm, SetPasswordForm
|
||||
from django import forms as d_forms
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from .ungleich_ldap import LdapManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class MySignupForm(SignupForm):
|
||||
first_name = d_forms.CharField(max_length=30)
|
||||
last_name = d_forms.CharField(max_length=30)
|
||||
|
||||
def custom_signup(self, request, user):
|
||||
user.first_name = self.cleaned_data["first_name"]
|
||||
user.last_name = self.cleaned_data["last_name"]
|
||||
user.save()
|
||||
if settings.LDAP_ENABLED:
|
||||
ldap_manager = LdapManager()
|
||||
try:
|
||||
user_exists_in_ldap, entries = ldap_manager.check_user_exists(user.username)
|
||||
except Exception:
|
||||
logger.exception("Exception occur while searching for user in LDAP")
|
||||
else:
|
||||
if not user_exists_in_ldap:
|
||||
ldap_manager.create_user(user.username, user.password, user.first_name, user.last_name, user.email)
|
||||
|
||||
class MyResetPasswordKeyForm(ResetPasswordKeyForm):
|
||||
def save(self):
|
||||
ldap_manager = LdapManager()
|
||||
try:
|
||||
user_exists_in_ldap, entries = ldap_manager.check_user_exists(self.user.username)
|
||||
if not user_exists_in_ldap:
|
||||
super(MyResetPasswordKeyForm, self).save()
|
||||
else:
|
||||
if ldap_manager.change_password(entries[0].entry_dn, self.cleaned_data["password1"]):
|
||||
super(MyResetPasswordKeyForm, self).save()
|
||||
except Exception:
|
||||
logger.exception("Exception occur while searching for user in LDAP")
|
||||
raise d_forms.ValidationError(_("An error occurred, please try again later"))
|
||||
|
||||
class MyChangePasswordForm(ChangePasswordForm):
|
||||
def save(self):
|
||||
ldap_manager = LdapManager()
|
||||
try:
|
||||
user_exists_in_ldap, entries = ldap_manager.check_user_exists(self.user.username)
|
||||
if not user_exists_in_ldap:
|
||||
super(MyChangePasswordForm, self).save()
|
||||
else:
|
||||
if ldap_manager.change_password(self.user.username, self.cleaned_data["password1"]):
|
||||
super(MyChangePasswordForm, self).save()
|
||||
except Exception:
|
||||
logger.exception("Exception occur while searching for user in LDAP")
|
||||
raise d_forms.ValidationError(_("An error occurred, please try again later"))
|
||||
|
||||
class MySetPasswordForm(SetPasswordForm):
|
||||
def save(self):
|
||||
ldap_manager = LdapManager()
|
||||
try:
|
||||
user_exists_in_ldap, entries = ldap_manager.check_user_exists(self.user.username)
|
||||
if not user_exists_in_ldap:
|
||||
super(MySetPasswordForm, self).save()
|
||||
else:
|
||||
if ldap_manager.change_password(self.user.username, self.cleaned_data["password1"]):
|
||||
super(MySetPasswordForm, self).save()
|
||||
except Exception:
|
||||
logger.exception("Exception occur while searching for user in LDAP")
|
||||
raise d_forms.ValidationError(_("An error occurred, please try again later"))
|
|
@ -1,42 +0,0 @@
|
|||
import ldap
|
||||
# from django.conf import settings
|
||||
|
||||
AUTH_LDAP_SERVER_URI = "ldaps://ldap1.ungleich.ch,ldaps://ldap2.ungleich.ch"
|
||||
AUTH_LDAP_BIND_DN="uid=django-create,ou=system,dc=ungleich,dc=ch"
|
||||
AUTH_LDAP_BIND_PASSWORD="kS#e+v\zjKn]L!,RIu2}V+DUS"
|
||||
# AUTH_LDAP_USER_SEARCH = LDAPSearch("dc=ungleich,dc=ch",
|
||||
# ldap.SCOPE_SUBTREE,
|
||||
# "(uid=%(user)s)")
|
||||
|
||||
|
||||
|
||||
ldap_object = ldap.initialize(AUTH_LDAP_SERVER_URI)
|
||||
cancelid = ldap_object.bind(AUTH_LDAP_BIND_DN, AUTH_LDAP_BIND_PASSWORD)
|
||||
|
||||
res = ldap_object.search_s("dc=ungleich,dc=ch", ldap.SCOPE_SUBTREE, "(uid=nico)")
|
||||
print(res)
|
||||
|
||||
# class LDAP(object):
|
||||
# """
|
||||
# Managing users in LDAP
|
||||
|
||||
# Requires the following settings?
|
||||
|
||||
# LDAP_USER_DN: where to create users in the tree
|
||||
|
||||
# LDAP_ADMIN_DN: which DN to use for managing users
|
||||
# LDAP_ADMIN_PASSWORD: which password to used
|
||||
|
||||
# This module will reuse information from djagno_auth_ldap, including:
|
||||
|
||||
# AUTH_LDAP_SERVER_URI
|
||||
|
||||
# """
|
||||
# def __init__(self):
|
||||
# pass
|
||||
|
||||
# def create_user(self):
|
||||
# pass
|
||||
|
||||
# def change_password(self):
|
||||
# pass
|
|
@ -4,7 +4,9 @@ import logging
|
|||
import random
|
||||
|
||||
import ldap3
|
||||
from ldap3 import ALL
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import make_password
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -21,7 +23,7 @@ class LdapManager:
|
|||
Initialize the LDAP subsystem.
|
||||
"""
|
||||
self.rng = random.SystemRandom()
|
||||
self.server = ldap3.Server(settings.AUTH_LDAP_SERVER)
|
||||
self.server = ldap3.Server(settings.AUTH_LDAP_SERVER_HOST, use_ssl=True, get_info=ALL)
|
||||
|
||||
|
||||
def get_admin_conn(self):
|
||||
|
@ -32,6 +34,7 @@ class LdapManager:
|
|||
conn = self.get_conn(user=settings.LDAP_ADMIN_DN,
|
||||
password=settings.LDAP_ADMIN_PASSWORD,
|
||||
raise_exceptions=True)
|
||||
conn.start_tls()
|
||||
conn.bind()
|
||||
return conn
|
||||
|
||||
|
@ -58,7 +61,6 @@ class LdapManager:
|
|||
base64 or decoding it to unicode from ``ascii``.
|
||||
"""
|
||||
SALT_BYTES = 15
|
||||
|
||||
sha1 = hashlib.sha1()
|
||||
salt = self.rng.getrandbits(SALT_BYTES * 8).to_bytes(SALT_BYTES,
|
||||
"little")
|
||||
|
@ -67,10 +69,11 @@ class LdapManager:
|
|||
|
||||
digest = sha1.digest()
|
||||
passwd = b"{SSHA}" + base64.b64encode(digest + salt)
|
||||
|
||||
return passwd
|
||||
|
||||
|
||||
def create_user(self, user, password, firstname, lastname, email):
|
||||
def create_user(self, username, password, firstname, lastname, email):
|
||||
conn = self.get_admin_conn()
|
||||
uidNumber = self._get_max_uid() + 1
|
||||
logger.debug("uidNumber={uidNumber}".format(uidNumber=uidNumber))
|
||||
|
@ -91,7 +94,7 @@ class LdapManager:
|
|||
logger.debug("{uid} does not exist. Using it".format(uid=uidNumber))
|
||||
self._set_max_uid(uidNumber)
|
||||
try:
|
||||
uid = user # user.encode("utf-8")
|
||||
uid = username
|
||||
conn.add("uid={uid},{customer_dn}".format(
|
||||
uid=uid, customer_dn=settings.LDAP_CUSTOMER_DN
|
||||
),
|
||||
|
@ -105,54 +108,43 @@ class LdapManager:
|
|||
"uidNumber": [str(uidNumber)],
|
||||
"gidNumber": [str(settings.LDAP_CUSTOMER_GROUP_ID)],
|
||||
"loginShell": ["/bin/bash"],
|
||||
"homeDirectory": ["/home/{}".format(user).encode("utf-8")],
|
||||
"homeDirectory": ["/home/{}".format(username).encode("utf-8")],
|
||||
"mail": email.encode("utf-8"),
|
||||
"userPassword": [self._ssha_password(
|
||||
password.encode("utf-8")
|
||||
)]
|
||||
"userPassword": [self._ssha_password(password.encode("utf-8"))]
|
||||
}
|
||||
)
|
||||
logger.debug('Created user %s %s' % (user.encode('utf-8'),
|
||||
logger.debug('Created user %s %s' % (username.encode('utf-8'),
|
||||
uidNumber))
|
||||
except Exception as ex:
|
||||
logger.debug('Could not create user %s' % user.encode('utf-8'))
|
||||
logger.debug('Could not create user %s' % username.encode('utf-8'))
|
||||
logger.error("Exception: " + str(ex))
|
||||
raise
|
||||
finally:
|
||||
conn.unbind()
|
||||
|
||||
|
||||
def change_password(self, uid, new_password):
|
||||
def change_password(self, entry_dn, new_password):
|
||||
"""
|
||||
Changes the password of the user identified by user_dn
|
||||
|
||||
:param uid: str The uid that identifies the user
|
||||
:param entry_dn: str The dn that identifies the user
|
||||
:param new_password: str The new password string
|
||||
:return: True if password was changed successfully False otherwise
|
||||
"""
|
||||
conn = self.get_admin_conn()
|
||||
|
||||
# Make sure the user exists first to change his/her details
|
||||
user_exists, entries = self.check_user_exists(
|
||||
uid=uid,
|
||||
search_base=settings.ENTIRE_SEARCH_BASE
|
||||
)
|
||||
return_val = False
|
||||
if user_exists:
|
||||
try:
|
||||
return_val = conn.modify(
|
||||
entries[0].entry_dn,
|
||||
{
|
||||
"userpassword": (
|
||||
ldap3.MODIFY_REPLACE,
|
||||
[self._ssha_password(new_password.encode("utf-8"))]
|
||||
)
|
||||
}
|
||||
)
|
||||
except Exception as ex:
|
||||
logger.error("Exception: " + str(ex))
|
||||
else:
|
||||
logger.error("User {} not found".format(uid))
|
||||
try:
|
||||
return_val = conn.modify(
|
||||
entry_dn,
|
||||
{
|
||||
"userpassword": (
|
||||
ldap3.MODIFY_REPLACE,
|
||||
[self._ssha_password(new_password.encode("utf-8"))]
|
||||
)
|
||||
}
|
||||
)
|
||||
except Exception as ex:
|
||||
logger.error("Exception: " + str(ex))
|
||||
|
||||
conn.unbind()
|
||||
return return_val
|
||||
|
@ -173,7 +165,7 @@ class LdapManager:
|
|||
# Make sure the user exists first to change his/her details
|
||||
user_exists, entries = self.check_user_exists(
|
||||
uid=uid,
|
||||
search_base=settings.ENTIRE_SEARCH_BASE
|
||||
search_base=settings.LDAP_CUSTOMER_DN
|
||||
)
|
||||
|
||||
return_val = False
|
||||
|
|
22
uncloud_pay/migrations/0031_auto_20210819_1304.py
Normal file
22
uncloud_pay/migrations/0031_auto_20210819_1304.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 3.2.4 on 2021-08-19 13:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('uncloud_pay', '0030_pricingplan_monthly_maintenance_fees'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='bill',
|
||||
name='is_closed',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='bill',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('new', 'New'), ('cancelled', 'Cancelled'), ('paid', 'Paid')], default='new', max_length=32),
|
||||
),
|
||||
]
|
|
@ -27,6 +27,12 @@ from .services import *
|
|||
# Used to generate bill due dates.
|
||||
BILL_PAYMENT_DELAY=datetime.timedelta(days=settings.BILL_PAYMENT_DELAY)
|
||||
|
||||
EU_COUNTRIES = ['at', 'be', 'bg', 'ch', 'cy', 'cz', 'hr', 'dk',
|
||||
'ee', 'fi', 'fr', 'mc', 'de', 'gr', 'hu', 'ie', 'it',
|
||||
'lv', 'lu', 'mt', 'nl', 'po', 'pt', 'ro','sk', 'si', 'es',
|
||||
'se', 'gb']
|
||||
|
||||
|
||||
# Initialize logger.
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -254,41 +260,26 @@ class VATRate(models.Model):
|
|||
description = models.TextField(blank=True, default='')
|
||||
|
||||
@staticmethod
|
||||
def get_for_country(country_code):
|
||||
vat_rate = None
|
||||
try:
|
||||
vat_rate = VATRate.objects.get(
|
||||
territory_codes=country_code, start_date__isnull=False, stop_date=None
|
||||
)
|
||||
return vat_rate.rate
|
||||
except VATRate.DoesNotExist as dne:
|
||||
logger.debug(str(dne))
|
||||
logger.debug("Did not find VAT rate for %s, returning 0" % country_code)
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def get_vat_rate(billing_address, when=None):
|
||||
def get_vat_rate_for_country(country, when=None):
|
||||
"""
|
||||
Returns the VAT rate for business to customer.
|
||||
|
||||
B2B is always 0% with the exception of trading within the own country
|
||||
"""
|
||||
|
||||
country = billing_address.country
|
||||
|
||||
# Need to have a provider country
|
||||
providers = UncloudProvider.objects.all()
|
||||
vatrate = filter_for_when(VATRate.objects.filter(territory_codes=country), when).first()
|
||||
|
||||
if not providers and not vatrate:
|
||||
return 0
|
||||
|
||||
uncloud_provider = filter_for_when(providers).get()
|
||||
|
||||
# By default we charge VAT. This affects:
|
||||
# - Same country sales (VAT applied)
|
||||
# - B2C to EU (VAT applied)
|
||||
rate = vatrate.rate if vatrate else 0
|
||||
if not country.lower().strip() in EU_COUNTRIES:
|
||||
rate = 0
|
||||
|
||||
return rate
|
||||
|
||||
@staticmethod
|
||||
def get_vat_rate(billing_address, when=None):
|
||||
rate = VATRate.get_vat_rate_for_country(billing_address.country, when)
|
||||
|
||||
# Exception: if...
|
||||
# - the billing_address is in EU,
|
||||
|
@ -296,7 +287,7 @@ class VATRate(models.Model):
|
|||
# - the vat_number has been verified
|
||||
# Then we do not charge VAT
|
||||
|
||||
if uncloud_provider.country != country and billing_address.vat_number and billing_address.vat_number_verified:
|
||||
if rate != 0 and billing_address.vat_number and billing_address.vat_number_verified:
|
||||
rate = 0
|
||||
return rate
|
||||
|
||||
|
@ -408,7 +399,7 @@ class Product(models.Model):
|
|||
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} - {self.description}"
|
||||
return f"{self.name}"
|
||||
|
||||
@property
|
||||
def recurring_orders(self):
|
||||
|
@ -1010,7 +1001,11 @@ class Bill(models.Model):
|
|||
# FIXME: editable=True -> is in the admin, but also editable in DRF
|
||||
# Maybe filter fields in the serializer?
|
||||
|
||||
is_closed = models.BooleanField(default=False)
|
||||
status = models.CharField(max_length=32, choices= (
|
||||
('new', 'New'),
|
||||
('cancelled', 'Cancelled'),
|
||||
('paid', 'Paid')
|
||||
), null=False, blank=False, default="new")
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
|
@ -1020,11 +1015,11 @@ class Bill(models.Model):
|
|||
name='one_bill_per_month_per_user')
|
||||
]
|
||||
|
||||
def close(self):
|
||||
def close(self, status):
|
||||
"""
|
||||
Close/finish a bill
|
||||
"""
|
||||
self.is_closed = True
|
||||
self.status = status
|
||||
if not self.ending_date:
|
||||
self.ending_date = timezone.now()
|
||||
self.save()
|
||||
|
@ -1120,7 +1115,7 @@ class Bill(models.Model):
|
|||
|
||||
# Get date & bill from previous bill, if it exists
|
||||
if last_bill:
|
||||
if not last_bill.is_closed:
|
||||
if last_bill.status == 'new':
|
||||
bill = last_bill
|
||||
starting_date = last_bill.starting_date
|
||||
ending_date = bill.ending_date
|
||||
|
|
|
@ -122,7 +122,7 @@ class BillSerializer(serializers.ModelSerializer):
|
|||
model = Bill
|
||||
fields = ['owner', 'sum', 'vat_rate',
|
||||
'due_date', 'creation_date', 'starting_date', 'ending_date',
|
||||
'records', 'is_closed', 'billing_address']
|
||||
'records', 'status', 'billing_address']
|
||||
|
||||
# We do not want users to mutate the country / VAT number of an address, as it
|
||||
# will change VAT on existing bills.
|
||||
|
|
|
@ -425,7 +425,7 @@ class BillTestCase(TestCase):
|
|||
self.assertEqual(record.quantity, 1)
|
||||
self.assertEqual(record.sum, 35)
|
||||
#close the bill as it has been paid
|
||||
bill.close()
|
||||
bill.close(status="paid")
|
||||
bill2 = Bill.create_next_bill_for_user_address(self.user_addr)
|
||||
self.assertNotEqual(bill.id, bill2.id)
|
||||
self.assertEqual(order.billrecord_set.count(), 2)
|
||||
|
@ -483,7 +483,7 @@ class BillTestCase(TestCase):
|
|||
|
||||
for ending_date in self.bill_dates:
|
||||
b = Bill.create_next_bill_for_user_address(self.recurring_user_addr, ending_date)
|
||||
b.close()
|
||||
b.close(status="paid")
|
||||
|
||||
bill_count = Bill.objects.filter(owner=self.recurring_user).count()
|
||||
|
||||
|
@ -519,14 +519,28 @@ class VATRatesTestCase(TestCase):
|
|||
street="unknown",
|
||||
city="unknown",
|
||||
postal_code="unknown",
|
||||
country="CH",
|
||||
active=True)
|
||||
|
||||
UncloudNetwork.populate_db_defaults()
|
||||
UncloudProvider.populate_db_defaults()
|
||||
|
||||
VATRate.objects.create(territory_codes="CH", currency_code="CHF", rate=7.7,
|
||||
starting_date=timezone.make_aware(datetime.datetime(2000,1,1)))
|
||||
|
||||
|
||||
|
||||
def test_get_rate_for_user(self):
|
||||
"""
|
||||
Raise an error, when there is no address
|
||||
"""
|
||||
rate = VATRate.get_vat_rate(self.user_addr)
|
||||
self.assertEqual(rate, 7.7)
|
||||
self.user_addr.vat_number_verified = True
|
||||
self.user_addr.vat_number = "11111"
|
||||
rate1 = VATRate.get_vat_rate(self.user_addr)
|
||||
self.assertEqual(rate1, 0)
|
||||
rate2 = VATRate.get_vat_rate_for_country('CH')
|
||||
self.assertEqual(rate, 7.7)
|
||||
rate2 = VATRate.get_vat_rate_for_country('EG')
|
||||
self.assertEqual(rate2, 0)
|
||||
|
|
|
@ -41,11 +41,16 @@ class PricingView(View):
|
|||
vat_rate = False
|
||||
vat_validation_status = False
|
||||
address = False
|
||||
selected_country = request.GET.get('country', False)
|
||||
if self.request.user and self.request.user.is_authenticated:
|
||||
address = get_billing_address_for_user(self.request.user)
|
||||
if address:
|
||||
if address and (address.country == selected_country or not selected_country):
|
||||
vat_rate = VATRate.get_vat_rate(address)
|
||||
vat_validation_status = "verified" if address.vat_number_validated_on and address.vat_number_verified else False
|
||||
elif selected_country:
|
||||
vat_rate = VATRate.get_vat_rate_for_country(selected_country)
|
||||
vat_validation_status = False
|
||||
|
||||
pricing = get_order_total_with_vat(
|
||||
request.GET.get('cores'),
|
||||
request.GET.get('memory'),
|
||||
|
|
Loading…
Reference in a new issue